From f8ea51eb8c331239c4b523eb4502ab38878d212b Mon Sep 17 00:00:00 2001 From: lijiahangmax Date: Tue, 19 Mar 2024 01:24:15 +0800 Subject: [PATCH] =?UTF-8?q?:hammer:=20=E6=89=B9=E9=87=8F=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=97=A5=E5=BF=97.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/common/constant/FieldConst.java | 2 + .../config/AssetWebSocketConfiguration.java | 2 +- .../asset/controller/ExecController.http | 2 +- .../ops/module/asset/dao/HostConfigDAO.java | 6 +- .../module/asset/define/AssetThreadPools.java | 12 +++ .../asset/entity/dto/ExecHostLogTailDTO.java | 3 + .../request/exec/ExecLogTailRequest.java | 2 +- .../module/asset/entity/vo/HostConfigVO.java | 3 + .../command/handler/ExecCommandHandler.java | 7 +- .../exec/command/handler/ExecTaskHandler.java | 1 - .../host/exec/log/ExecLogTailHandler.java | 93 ++++++++++++++++++ .../host/exec/log/constant/LogConst.java | 20 ++++ .../exec/log/handler/ExecLogTailHandler.java | 49 ---------- .../host/exec/log/manager/ExecLogManager.java | 80 ++++++++++++++++ .../host/exec/log/tracker/ExecLogTracker.java | 94 +++++++++++++++++++ .../exec/log/tracker/IExecLogTracker.java | 41 ++++++++ .../host/terminal/session/SshSession.java | 3 - .../asset/service/HostConfigService.java | 15 ++- .../asset/service/impl/ExecServiceImpl.java | 36 +++++-- .../service/impl/HostConfigServiceImpl.java | 43 ++++++--- orion-ops-ui/src/api/asset/host-config.ts | 1 + .../components/log-panel-host.vue | 4 +- .../exec-command/components/log-panel.vue | 8 +- 23 files changed, 437 insertions(+), 90 deletions(-) create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/ExecLogTailHandler.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/constant/LogConst.java delete mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/handler/ExecLogTailHandler.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/manager/ExecLogManager.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/IExecLogTracker.java diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/FieldConst.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/FieldConst.java index 14d1af00..2acbaf58 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/FieldConst.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/FieldConst.java @@ -41,6 +41,8 @@ public interface FieldConst { String TARGET = "target"; + String CHARSET = "charset"; + String TOKEN = "token"; String PATH = "path"; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/config/AssetWebSocketConfiguration.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/config/AssetWebSocketConfiguration.java index 197ae68f..3f6a4ec1 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/config/AssetWebSocketConfiguration.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/config/AssetWebSocketConfiguration.java @@ -1,6 +1,6 @@ package com.orion.ops.module.asset.config; -import com.orion.ops.module.asset.handler.host.exec.log.handler.ExecLogTailHandler; +import com.orion.ops.module.asset.handler.host.exec.log.ExecLogTailHandler; import com.orion.ops.module.asset.handler.host.terminal.TerminalMessageDispatcher; import com.orion.ops.module.asset.handler.host.transfer.TransferMessageDispatcher; import com.orion.ops.module.asset.interceptor.ExecLogTailInterceptor; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http index a23f102d..9e5ed9c6 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/controller/ExecController.http @@ -28,7 +28,7 @@ Content-Type: application/json Authorization: {{token}} { - "execId": 1 + "execId": 56 } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/dao/HostConfigDAO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/dao/HostConfigDAO.java index bcb2bb7b..d57c58c6 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/dao/HostConfigDAO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/dao/HostConfigDAO.java @@ -52,11 +52,13 @@ public interface HostConfigDAO extends IMapper { * 通过 hostId 批量查询主机配置 * * @param hostIdList hostIdList + * @param type type * @return rows */ - default List getHostConfigByHostIdList(List hostIdList) { + default List getHostConfigByHostIdList(List hostIdList, String type) { // 条件 - LambdaQueryWrapper wrapper = this.lambda() + LambdaQueryWrapper wrapper = this.wrapper() + .eq(HostConfigDO::getType, type) .in(HostConfigDO::getHostId, hostIdList); // 查询 return this.of(wrapper).list(); diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java index 053186a5..d4e4030f 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/define/AssetThreadPools.java @@ -75,4 +75,16 @@ public interface AssetThreadPools { .allowCoreThreadTimeout(true) .build(); + /** + * 批量执行日志查看线程池 + */ + ThreadPoolExecutor EXEC_LOG = ExecutorBuilder.create() + .namedThreadFactory("exec-log-") + .corePoolSize(1) + .maxPoolSize(Integer.MAX_VALUE) + .keepAliveTime(Const.MS_S_60) + .workQueue(new SynchronousQueue<>()) + .allowCoreThreadTimeout(true) + .build(); + } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecHostLogTailDTO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecHostLogTailDTO.java index 71bc32c6..5ea21696 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecHostLogTailDTO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/ExecHostLogTailDTO.java @@ -31,4 +31,7 @@ public class ExecHostLogTailDTO implements Serializable { @Schema(description = "文件路径") private String path; + @Schema(description = "输出编码") + private String charset; + } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogTailRequest.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogTailRequest.java index 642300e0..4f49f28c 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogTailRequest.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/exec/ExecLogTailRequest.java @@ -28,6 +28,6 @@ public class ExecLogTailRequest { private Long execId; @Schema(description = "执行主机id") - private List execHostIdList; + private List hostExecIdList; } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/HostConfigVO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/HostConfigVO.java index e63110f1..9149bc24 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/HostConfigVO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/vo/HostConfigVO.java @@ -25,6 +25,9 @@ public class HostConfigVO { @Schema(description = "id") private Long id; + @Schema(description = "hostId") + private Long hostId; + @Schema(description = "version") private Integer version; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecCommandHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecCommandHandler.java index d31e5611..80a67e43 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecCommandHandler.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecCommandHandler.java @@ -14,6 +14,7 @@ import com.orion.ops.module.asset.dao.ExecHostLogDAO; import com.orion.ops.module.asset.entity.domain.ExecHostLogDO; import com.orion.ops.module.asset.enums.ExecHostStatusEnum; import com.orion.ops.module.asset.handler.host.exec.command.dto.ExecCommandHostDTO; +import com.orion.ops.module.asset.handler.host.exec.log.manager.ExecLogManager; import com.orion.ops.module.asset.service.HostTerminalService; import com.orion.spring.SpringHolder; import lombok.Getter; @@ -37,6 +38,8 @@ public class ExecCommandHandler implements IExecCommandHandler { private final FileClient fileClient = SpringHolder.getBean("logsFileClient"); + private final ExecLogManager execLogManager = SpringHolder.getBean(ExecLogManager.class); + private final HostTerminalService hostTerminalService = SpringHolder.getBean(HostTerminalService.class); private final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class); @@ -177,7 +180,9 @@ public class ExecCommandHandler implements IExecCommandHandler { Streams.close(executor); Streams.close(sessionStore); Streams.close(logOutputStream); - // TODO 关闭日志 + // TODO TEST 异步关闭日志 + execLogManager.asyncCloseTailFile(execHostCommand.getLogPath()); + } /** diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java index 9a761a86..0b2771d3 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java @@ -133,7 +133,6 @@ public class ExecTaskHandler implements IExecTaskHandler { log.info("ExecTaskHandler-close id: {}", execCommand.getLogId()); Streams.close(timeoutChecker); this.handlers.forEach(Streams::close); - // TODO 关闭日志 } } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/ExecLogTailHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/ExecLogTailHandler.java new file mode 100644 index 00000000..9d2b2a53 --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/ExecLogTailHandler.java @@ -0,0 +1,93 @@ +package com.orion.ops.module.asset.handler.host.exec.log; + +import com.orion.ops.framework.common.constant.ExtraFieldConst; +import com.orion.ops.framework.common.file.FileClient; +import com.orion.ops.framework.websocket.core.utils.WebSockets; +import com.orion.ops.module.asset.define.AssetThreadPools; +import com.orion.ops.module.asset.entity.dto.ExecHostLogTailDTO; +import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO; +import com.orion.ops.module.asset.handler.host.exec.log.manager.ExecLogManager; +import com.orion.ops.module.asset.handler.host.exec.log.tracker.ExecLogTracker; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.AbstractWebSocketHandler; + +import javax.annotation.Resource; + +/** + * 执行日志查看处理器 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/18 18:38 + */ +@Slf4j +@Component +public class ExecLogTailHandler extends AbstractWebSocketHandler { + + @Resource + private FileClient logsFileClient; + + @Resource + private ExecLogManager execLogManager; + + @Override + public void afterConnectionEstablished(WebSocketSession session) { + String id = session.getId(); + log.info("ExecLogTailHandler-afterConnectionEstablished id: {}", id); + // 获取参数 + ExecLogTailDTO info = WebSockets.getAttr(session, ExtraFieldConst.INFO); + // 打开会话 + for (ExecHostLogTailDTO host : info.getHosts()) { + String trackerId = this.getTrackerId(id, info, host); + String absolutePath = logsFileClient.getAbsolutePath(host.getPath()); + // 追踪器 + ExecLogTracker tracker = new ExecLogTracker(trackerId, absolutePath, session, host); + // 执行 + AssetThreadPools.EXEC_LOG.execute(tracker); + // 添加追踪器 + execLogManager.addTracker(tracker); + } + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) { + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) { + log.error("ExecLogTailHandler-handleTransportError id: {}", session.getId(), exception); + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { + String id = session.getId(); + log.info("ExecLogTailHandler-afterConnectionClosed id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason()); + // 关闭会话 + ExecLogTailDTO info = WebSockets.getAttr(session, ExtraFieldConst.INFO); + // 移除追踪器 TODO TEST + for (ExecHostLogTailDTO host : info.getHosts()) { + execLogManager.removeTracker(this.getTrackerId(id, info, host)); + } + } + + // TODO ws://127.0.0.1:9200/orion/keep-alive/exec/log/ive0btemHxmEY0HyTm5 + // todo 首页元数据加载 + // todo 批量执行的 warn + + /** + * 获取追踪器 id + * + * @param id id + * @param info info + * @param host host + * @return trackerId + */ + private String getTrackerId(String id, ExecLogTailDTO info, ExecHostLogTailDTO host) { + return id + "_" + info.getId() + "_" + host.getId(); + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/constant/LogConst.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/constant/LogConst.java new file mode 100644 index 00000000..c4f95f0b --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/constant/LogConst.java @@ -0,0 +1,20 @@ +package com.orion.ops.module.asset.handler.host.exec.log.constant; + +/** + * 日志常量 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/18 23:15 + */ +public interface LogConst { + + String SEPARATOR = "|"; + + int TRACKER_OFFSET_LINE = 200; + + int TRACKER_DELAY_MS = 200; + + int TRACKER_WAIT_TIMES = 10; + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/handler/ExecLogTailHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/handler/ExecLogTailHandler.java deleted file mode 100644 index ccefa54e..00000000 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/handler/ExecLogTailHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.orion.ops.module.asset.handler.host.exec.log.handler; - -import com.orion.ops.framework.common.constant.ExtraFieldConst; -import com.orion.ops.framework.websocket.core.utils.WebSockets; -import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.TextMessage; -import org.springframework.web.socket.WebSocketSession; -import org.springframework.web.socket.handler.AbstractWebSocketHandler; - -/** - * 执行日志查看处理器 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/3/18 18:38 - */ -@Slf4j -@Component -public class ExecLogTailHandler extends AbstractWebSocketHandler { - - @Override - public void afterConnectionEstablished(WebSocketSession session) { - log.info("ExecLogTailHandler-afterConnectionEstablished id: {}", session.getId()); - // 获取参数 - ExecLogTailDTO info = WebSockets.getAttr(session, ExtraFieldConst.INFO); - - - } - - @Override - protected void handleTextMessage(WebSocketSession session, TextMessage message) { - } - - @Override - public void handleTransportError(WebSocketSession session, Throwable exception) { - log.error("ExecLogTailHandler-handleTransportError id: {}", session.getId(), exception); - } - - @Override - public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { - String id = session.getId(); - log.info("ExecLogTailHandler-afterConnectionClosed id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason()); - // 关闭会话 - } - -} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/manager/ExecLogManager.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/manager/ExecLogManager.java new file mode 100644 index 00000000..884852fc --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/manager/ExecLogManager.java @@ -0,0 +1,80 @@ +package com.orion.ops.module.asset.handler.host.exec.log.manager; + +import com.orion.lang.utils.Threads; +import com.orion.ops.framework.common.constant.Const; +import com.orion.ops.module.asset.handler.host.exec.log.tracker.IExecLogTracker; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * 执行日志管理器 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/18 23:36 + */ +@Slf4j +@Component +public class ExecLogManager { + + private final ConcurrentHashMap execTrackers = new ConcurrentHashMap<>(); + + /** + * 添加执行日志追踪器 + * + * @param tracker tracker + */ + public void addTracker(IExecLogTracker tracker) { + execTrackers.put(tracker.getTrackerId(), tracker); + } + + /** + * 获取日志追踪器 + * + * @param trackerId trackerId + * @return tracker + */ + public IExecLogTracker getTracker(String trackerId) { + return execTrackers.get(trackerId); + } + + /** + * 移除日志追踪器 + * + * @param trackerId trackerId + */ + public void removeTracker(String trackerId) { + IExecLogTracker tracker = execTrackers.remove(trackerId); + if (tracker != null) { + tracker.close(); + } + } + + /** + * 异步关闭进行中的追踪器 + * + * @param path path + */ + public void asyncCloseTailFile(String path) { + Threads.start(() -> { + try { + // 获取当前路径的全部追踪器 + List trackers = execTrackers.values() + .stream() + .filter(s -> s.getPath().equals(path)) + .collect(Collectors.toList()); + Threads.sleep(Const.MS_S_1); + trackers.forEach(IExecLogTracker::setLastModify); + Threads.sleep(Const.MS_S_5); + trackers.forEach(IExecLogTracker::close); + } catch (Exception e) { + log.error("ExecLogManager.asyncCloseTailFile error path: {}", path, e); + } + }); + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java new file mode 100644 index 00000000..229925d3 --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java @@ -0,0 +1,94 @@ +package com.orion.ops.module.asset.handler.host.exec.log.tracker; + +import com.orion.ext.tail.Tracker; +import com.orion.ext.tail.delay.DelayTrackerListener; +import com.orion.ext.tail.mode.FileNotFoundMode; +import com.orion.ext.tail.mode.FileOffsetMode; +import com.orion.ops.framework.websocket.core.utils.WebSockets; +import com.orion.ops.module.asset.entity.dto.ExecHostLogTailDTO; +import com.orion.ops.module.asset.handler.host.exec.log.constant.LogConst; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.socket.WebSocketSession; + +/** + * log tracker 实现类 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/18 23:36 + */ +@Slf4j +public class ExecLogTracker implements IExecLogTracker { + + private final WebSocketSession session; + + private final ExecHostLogTailDTO config; + + @Getter + private final String trackerId; + + @Getter + private final String absolutePath; + + private DelayTrackerListener tracker; + + private volatile boolean close; + + public ExecLogTracker(String trackerId, + String absolutePath, + WebSocketSession session, + ExecHostLogTailDTO config) { + this.trackerId = trackerId; + this.absolutePath = absolutePath; + this.session = session; + this.config = config; + } + + @Override + public void run() { + try { + this.tracker = new DelayTrackerListener(absolutePath, this); + tracker.charset(config.getCharset()); + tracker.delayMillis(LogConst.TRACKER_DELAY_MS); + tracker.offset(FileOffsetMode.LINE, LogConst.TRACKER_OFFSET_LINE); + tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, LogConst.TRACKER_WAIT_TIMES); + // 开始监听文件 + tracker.run(); + // 监听完成回调 + // TODO test + this.close = true; + } catch (Exception e) { + log.error("exec log tracker error path: {}", absolutePath, e); + } + } + + @Override + public void setLastModify() { + tracker.setFileLastModifyTime(); + } + + @Override + public String getPath() { + return config.getPath(); + } + + @Override + public void read(byte[] bytes, int len, Tracker tracker) { + WebSockets.sendText(session, config.getId() + LogConst.SEPARATOR + new String(bytes, 0, len)); + } + + @Override + public void close() { + // TODO test + log.info("ExecLogTracker.close path: {}", absolutePath); + if (close) { + return; + } + this.close = true; + if (tracker != null) { + tracker.stop(); + } + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/IExecLogTracker.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/IExecLogTracker.java new file mode 100644 index 00000000..d210562b --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/exec/log/tracker/IExecLogTracker.java @@ -0,0 +1,41 @@ +package com.orion.ops.module.asset.handler.host.exec.log.tracker; + +import com.orion.ext.tail.handler.DataHandler; +import com.orion.lang.able.SafeCloseable; + +/** + * log tracker 定义 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/18 23:00 + */ +public interface IExecLogTracker extends Runnable, DataHandler, SafeCloseable { + + /** + * 设置最后修改时间 + */ + void setLastModify(); + + /** + * 获取 id + * + * @return id + */ + String getTrackerId(); + + /** + * 获取路径 + * + * @return path + */ + String getPath(); + + /** + * 获取绝对路径 + * + * @return 绝对路径 + */ + String getAbsolutePath(); + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/SshSession.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/SshSession.java index 19ff33e2..d9044508 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/SshSession.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/SshSession.java @@ -4,14 +4,11 @@ import com.orion.lang.utils.io.Streams; import com.orion.net.host.SessionStore; import com.orion.net.host.ssh.shell.ShellExecutor; import com.orion.ops.framework.common.constant.Const; -import com.orion.ops.framework.common.enums.BooleanBit; import com.orion.ops.framework.websocket.core.utils.WebSockets; import com.orion.ops.module.asset.define.AssetThreadPools; -import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage; import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum; import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig; import com.orion.ops.module.asset.handler.host.terminal.model.response.SshOutputResponse; -import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalCloseResponse; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.WebSocketSession; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/HostConfigService.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/HostConfigService.java index 293bbea3..6023e69f 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/HostConfigService.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/HostConfigService.java @@ -22,7 +22,7 @@ public interface HostConfigService { * * @param hostId hostId * @param type type - * @return 配置 + * @return config */ HostConfigVO getHostConfig(Long hostId, String type); @@ -31,7 +31,7 @@ public interface HostConfigService { * * @param hostId hostId * @param type type - * @return 配置 + * @return config */ T getHostConfig(Long hostId, HostConfigTypeEnum type); @@ -39,10 +39,19 @@ public interface HostConfigService { * 获取配置 * * @param hostId hostId - * @return 配置 + * @return config */ List getHostConfigList(Long hostId); + /** + * 获取配置 + * + * @param hostIdList hostIdList + * @param type type + * @return config + */ + List getHostConfigList(List hostIdList, String type); + /** * 更新配置 * diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java index 370b9452..99c936d3 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/ExecServiceImpl.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.orion.lang.exception.argument.InvalidArgumentException; import com.orion.lang.function.Functions; import com.orion.lang.id.UUIds; +import com.orion.lang.utils.Objects1; import com.orion.lang.utils.Strings; import com.orion.lang.utils.collect.Lists; import com.orion.lang.utils.collect.Maps; @@ -16,6 +17,7 @@ import com.orion.lang.utils.time.Dates; import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs; import com.orion.ops.framework.common.constant.Const; import com.orion.ops.framework.common.constant.ErrorMessage; +import com.orion.ops.framework.common.constant.FieldConst; import com.orion.ops.framework.common.file.FileClient; import com.orion.ops.framework.common.security.LoginUser; import com.orion.ops.framework.common.utils.Valid; @@ -35,6 +37,7 @@ import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest; import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest; import com.orion.ops.module.asset.entity.vo.ExecCommandHostVO; import com.orion.ops.module.asset.entity.vo.ExecCommandVO; +import com.orion.ops.module.asset.entity.vo.HostConfigVO; import com.orion.ops.module.asset.enums.ExecHostStatusEnum; import com.orion.ops.module.asset.enums.ExecSourceEnum; import com.orion.ops.module.asset.enums.ExecStatusEnum; @@ -47,6 +50,7 @@ import com.orion.ops.module.asset.handler.host.exec.command.handler.IExecTaskHan import com.orion.ops.module.asset.handler.host.exec.command.manager.ExecTaskManager; import com.orion.ops.module.asset.service.AssetAuthorizedDataService; import com.orion.ops.module.asset.service.ExecService; +import com.orion.ops.module.asset.service.HostConfigService; import com.orion.web.servlet.web.Servlets; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -55,10 +59,8 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; /** @@ -92,6 +94,9 @@ public class ExecServiceImpl implements ExecService { @Resource private AssetAuthorizedDataService assetAuthorizedDataService; + @Resource + private HostConfigService hostConfigService; + @Resource private ExecTaskManager execTaskManager; @@ -167,6 +172,7 @@ public class ExecServiceImpl implements ExecService { } @Override + @Transactional(rollbackFor = Exception.class) public ExecCommandVO reExecCommand(Long logId) { log.info("ExecService.reExecCommand start logId: {}", logId); // 获取执行记录 @@ -288,24 +294,31 @@ public class ExecServiceImpl implements ExecService { @Override public String getExecLogTailToken(ExecLogTailRequest request) { Long execId = request.getExecId(); - List execHostIdList = request.getExecHostIdList(); - log.info("ExecService.getExecLogTailToken start execId: {}, execHostIdList: {}", execId, execHostIdList); + List hostExecIdList = request.getHostExecIdList(); + log.info("ExecService.getExecLogTailToken start execId: {}, hostExecIdList: {}", execId, hostExecIdList); // 查询执行日志 ExecLogDO execLog = execLogDAO.selectById(execId); Valid.notNull(execLog, ErrorMessage.LOG_ABSENT); // 查询主机日志 List hostLogs; - if (execHostIdList == null) { + if (hostExecIdList == null) { hostLogs = execHostLogDAO.selectByLogId(execId); } else { hostLogs = execHostLogDAO.of() .createWrapper() .eq(ExecHostLogDO::getLogId, execId) - .in(ExecHostLogDO::getId, execHostIdList) + .in(ExecHostLogDO::getId, hostExecIdList) .then() .list(); } Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT); + // 获取编码集 + List hostIdList = hostLogs.stream() + .map(ExecHostLogDO::getHostId) + .collect(Collectors.toList()); + Map configMap = hostConfigService.getHostConfigList(hostIdList, HostConfigTypeEnum.SSH.getType()) + .stream() + .collect(Collectors.toMap(HostConfigVO::getId, Function.identity())); // 生成缓存 String token = UUIds.random19(); String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token); @@ -318,12 +331,17 @@ public class ExecServiceImpl implements ExecService { .id(s.getId()) .hostId(s.getHostId()) .path(s.getLogPath()) + .charset(Optional.ofNullable(configMap.get(s.getHostId())) + .map(HostConfigVO::getConfig) + .map(c -> c.get(FieldConst.CHARSET)) + .map(Objects1::toString) + .orElse(Const.UTF_8)) .build()) .collect(Collectors.toList())) .build(); // 设置缓存 RedisStrings.setJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL, cache); - log.info("ExecService.getExecLogTailToken finish token: {}, execId: {}, execHostIdList: {}", token, execId, execHostIdList); + log.info("ExecService.getExecLogTailToken finish token: {}, execId: {}, hostExecIdList: {}", token, execId, hostExecIdList); return token; } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConfigServiceImpl.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConfigServiceImpl.java index 191b6088..f9c2e540 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConfigServiceImpl.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConfigServiceImpl.java @@ -58,7 +58,6 @@ public class HostConfigServiceImpl implements HostConfigService { } @Override - @SuppressWarnings("unchecked") public T getHostConfig(Long hostId, HostConfigTypeEnum type) { // 查询配置 HostConfigDO config = hostConfigDAO.getHostConfigByHostId(hostId, type.getType()); @@ -76,20 +75,19 @@ public class HostConfigServiceImpl implements HostConfigService { public List getHostConfigList(Long hostId) { // 查询 List configs = hostConfigDAO.getHostConfigByHostId(hostId); + return configs.stream() + .map(this::convertHostConfig) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + @Override + public List getHostConfigList(List hostIdList, String type) { + // 查询 + List configs = hostConfigDAO.getHostConfigByHostIdList(hostIdList, type); // 返回 return configs.stream() - .map(s -> { - // 获取配置 - HostConfigTypeEnum type = HostConfigTypeEnum.of(s.getType()); - if (type == null) { - return null; - } - // 转为视图 - HostConfigVO vo = HostConfigConvert.MAPPER.to(s); - Map config = type.getStrategyBean().toView(s.getConfig()); - vo.setConfig(config); - return vo; - }) + .map(this::convertHostConfig) .filter(Objects::nonNull) .collect(Collectors.toList()); } @@ -219,4 +217,23 @@ public class HostConfigServiceImpl implements HostConfigService { return insert; } + /** + * 转化配置 + * + * @param row row + * @return config + */ + private HostConfigVO convertHostConfig(HostConfigDO row) { + // 获取配置 + HostConfigTypeEnum type = HostConfigTypeEnum.of(row.getType()); + if (type == null) { + return null; + } + // 转为视图 + HostConfigVO vo = HostConfigConvert.MAPPER.to(row); + Map config = type.getStrategyBean().toView(row.getConfig()); + vo.setConfig(config); + return vo; + } + } diff --git a/orion-ops-ui/src/api/asset/host-config.ts b/orion-ops-ui/src/api/asset/host-config.ts index 7c69ce6f..851b1cbf 100644 --- a/orion-ops-ui/src/api/asset/host-config.ts +++ b/orion-ops-ui/src/api/asset/host-config.ts @@ -16,6 +16,7 @@ export interface HostConfigRequest { */ export interface HostConfigQueryResponse { id: number; + hostId: number; type: string; version: number; status: number; diff --git a/orion-ops-ui/src/views/exec/exec-command/components/log-panel-host.vue b/orion-ops-ui/src/views/exec/exec-command/components/log-panel-host.vue index 7c0192af..4b4ce662 100644 --- a/orion-ops-ui/src/views/exec/exec-command/components/log-panel-host.vue +++ b/orion-ops-ui/src/views/exec/exec-command/components/log-panel-host.vue @@ -14,8 +14,8 @@
+ :class="[ current === item.id ? 'exec-host-item-selected' : '' ]" + @click="emits('selected', item.id)">
{{ item.hostName }} diff --git a/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue b/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue index e660f434..c97d6abd 100644 --- a/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue +++ b/orion-ops-ui/src/views/exec/exec-command/components/log-panel.vue @@ -2,7 +2,7 @@
@@ -26,7 +26,7 @@ const emits = defineEmits(['back']); - const currentHostId = ref(1); + const currentHostExecId = ref(1); const command = ref({ id: 50, hosts: [{ @@ -65,7 +65,7 @@ // 打开 const open = (record: ExecCommandResponse) => { command.value = record; - currentHostId.value = record.hosts[0].hostId; + currentHostExecId.value = record.hosts[0].id; // 打开日志 openLog(); }; @@ -79,7 +79,7 @@ // 选中主机 const selectedHost = (hostId: number) => { - currentHostId.value = hostId; + currentHostExecId.value = hostId; }; // 返回