🔨 批量执行日志.
This commit is contained in:
@@ -41,6 +41,8 @@ public interface FieldConst {
|
|||||||
|
|
||||||
String TARGET = "target";
|
String TARGET = "target";
|
||||||
|
|
||||||
|
String CHARSET = "charset";
|
||||||
|
|
||||||
String TOKEN = "token";
|
String TOKEN = "token";
|
||||||
|
|
||||||
String PATH = "path";
|
String PATH = "path";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.orion.ops.module.asset.config;
|
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.terminal.TerminalMessageDispatcher;
|
||||||
import com.orion.ops.module.asset.handler.host.transfer.TransferMessageDispatcher;
|
import com.orion.ops.module.asset.handler.host.transfer.TransferMessageDispatcher;
|
||||||
import com.orion.ops.module.asset.interceptor.ExecLogTailInterceptor;
|
import com.orion.ops.module.asset.interceptor.ExecLogTailInterceptor;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Content-Type: application/json
|
|||||||
Authorization: {{token}}
|
Authorization: {{token}}
|
||||||
|
|
||||||
{
|
{
|
||||||
"execId": 1
|
"execId": 56
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,11 +52,13 @@ public interface HostConfigDAO extends IMapper<HostConfigDO> {
|
|||||||
* 通过 hostId 批量查询主机配置
|
* 通过 hostId 批量查询主机配置
|
||||||
*
|
*
|
||||||
* @param hostIdList hostIdList
|
* @param hostIdList hostIdList
|
||||||
|
* @param type type
|
||||||
* @return rows
|
* @return rows
|
||||||
*/
|
*/
|
||||||
default List<HostConfigDO> getHostConfigByHostIdList(List<Long> hostIdList) {
|
default List<HostConfigDO> getHostConfigByHostIdList(List<Long> hostIdList, String type) {
|
||||||
// 条件
|
// 条件
|
||||||
LambdaQueryWrapper<HostConfigDO> wrapper = this.lambda()
|
LambdaQueryWrapper<HostConfigDO> wrapper = this.wrapper()
|
||||||
|
.eq(HostConfigDO::getType, type)
|
||||||
.in(HostConfigDO::getHostId, hostIdList);
|
.in(HostConfigDO::getHostId, hostIdList);
|
||||||
// 查询
|
// 查询
|
||||||
return this.of(wrapper).list();
|
return this.of(wrapper).list();
|
||||||
|
|||||||
@@ -75,4 +75,16 @@ public interface AssetThreadPools {
|
|||||||
.allowCoreThreadTimeout(true)
|
.allowCoreThreadTimeout(true)
|
||||||
.build();
|
.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();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,4 +31,7 @@ public class ExecHostLogTailDTO implements Serializable {
|
|||||||
@Schema(description = "文件路径")
|
@Schema(description = "文件路径")
|
||||||
private String path;
|
private String path;
|
||||||
|
|
||||||
|
@Schema(description = "输出编码")
|
||||||
|
private String charset;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ public class ExecLogTailRequest {
|
|||||||
private Long execId;
|
private Long execId;
|
||||||
|
|
||||||
@Schema(description = "执行主机id")
|
@Schema(description = "执行主机id")
|
||||||
private List<Long> execHostIdList;
|
private List<Long> hostExecIdList;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ public class HostConfigVO {
|
|||||||
@Schema(description = "id")
|
@Schema(description = "id")
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
@Schema(description = "hostId")
|
||||||
|
private Long hostId;
|
||||||
|
|
||||||
@Schema(description = "version")
|
@Schema(description = "version")
|
||||||
private Integer version;
|
private Integer version;
|
||||||
|
|
||||||
|
|||||||
@@ -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.entity.domain.ExecHostLogDO;
|
||||||
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
|
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.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.ops.module.asset.service.HostTerminalService;
|
||||||
import com.orion.spring.SpringHolder;
|
import com.orion.spring.SpringHolder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -37,6 +38,8 @@ public class ExecCommandHandler implements IExecCommandHandler {
|
|||||||
|
|
||||||
private final FileClient fileClient = SpringHolder.getBean("logsFileClient");
|
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 HostTerminalService hostTerminalService = SpringHolder.getBean(HostTerminalService.class);
|
||||||
|
|
||||||
private final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class);
|
private final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class);
|
||||||
@@ -177,7 +180,9 @@ public class ExecCommandHandler implements IExecCommandHandler {
|
|||||||
Streams.close(executor);
|
Streams.close(executor);
|
||||||
Streams.close(sessionStore);
|
Streams.close(sessionStore);
|
||||||
Streams.close(logOutputStream);
|
Streams.close(logOutputStream);
|
||||||
// TODO 关闭日志
|
// TODO TEST 异步关闭日志
|
||||||
|
execLogManager.asyncCloseTailFile(execHostCommand.getLogPath());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -133,7 +133,6 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
|||||||
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
|
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
|
||||||
Streams.close(timeoutChecker);
|
Streams.close(timeoutChecker);
|
||||||
this.handlers.forEach(Streams::close);
|
this.handlers.forEach(Streams::close);
|
||||||
// TODO 关闭日志
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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());
|
|
||||||
// 关闭会话
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -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<String, IExecLogTracker> 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<IExecLogTracker> 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -4,14 +4,11 @@ import com.orion.lang.utils.io.Streams;
|
|||||||
import com.orion.net.host.SessionStore;
|
import com.orion.net.host.SessionStore;
|
||||||
import com.orion.net.host.ssh.shell.ShellExecutor;
|
import com.orion.net.host.ssh.shell.ShellExecutor;
|
||||||
import com.orion.ops.framework.common.constant.Const;
|
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.framework.websocket.core.utils.WebSockets;
|
||||||
import com.orion.ops.module.asset.define.AssetThreadPools;
|
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.enums.OutputTypeEnum;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig;
|
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.SshOutputResponse;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalCloseResponse;
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public interface HostConfigService {
|
|||||||
*
|
*
|
||||||
* @param hostId hostId
|
* @param hostId hostId
|
||||||
* @param type type
|
* @param type type
|
||||||
* @return 配置
|
* @return config
|
||||||
*/
|
*/
|
||||||
HostConfigVO getHostConfig(Long hostId, String type);
|
HostConfigVO getHostConfig(Long hostId, String type);
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ public interface HostConfigService {
|
|||||||
*
|
*
|
||||||
* @param hostId hostId
|
* @param hostId hostId
|
||||||
* @param type type
|
* @param type type
|
||||||
* @return 配置
|
* @return config
|
||||||
*/
|
*/
|
||||||
<T extends GenericsDataModel> T getHostConfig(Long hostId, HostConfigTypeEnum type);
|
<T extends GenericsDataModel> T getHostConfig(Long hostId, HostConfigTypeEnum type);
|
||||||
|
|
||||||
@@ -39,10 +39,19 @@ public interface HostConfigService {
|
|||||||
* 获取配置
|
* 获取配置
|
||||||
*
|
*
|
||||||
* @param hostId hostId
|
* @param hostId hostId
|
||||||
* @return 配置
|
* @return config
|
||||||
*/
|
*/
|
||||||
List<HostConfigVO> getHostConfigList(Long hostId);
|
List<HostConfigVO> getHostConfigList(Long hostId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置
|
||||||
|
*
|
||||||
|
* @param hostIdList hostIdList
|
||||||
|
* @param type type
|
||||||
|
* @return config
|
||||||
|
*/
|
||||||
|
List<HostConfigVO> getHostConfigList(List<Long> hostIdList, String type);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新配置
|
* 更新配置
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.orion.lang.exception.argument.InvalidArgumentException;
|
import com.orion.lang.exception.argument.InvalidArgumentException;
|
||||||
import com.orion.lang.function.Functions;
|
import com.orion.lang.function.Functions;
|
||||||
import com.orion.lang.id.UUIds;
|
import com.orion.lang.id.UUIds;
|
||||||
|
import com.orion.lang.utils.Objects1;
|
||||||
import com.orion.lang.utils.Strings;
|
import com.orion.lang.utils.Strings;
|
||||||
import com.orion.lang.utils.collect.Lists;
|
import com.orion.lang.utils.collect.Lists;
|
||||||
import com.orion.lang.utils.collect.Maps;
|
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.biz.operator.log.core.utils.OperatorLogs;
|
||||||
import com.orion.ops.framework.common.constant.Const;
|
import com.orion.ops.framework.common.constant.Const;
|
||||||
import com.orion.ops.framework.common.constant.ErrorMessage;
|
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.file.FileClient;
|
||||||
import com.orion.ops.framework.common.security.LoginUser;
|
import com.orion.ops.framework.common.security.LoginUser;
|
||||||
import com.orion.ops.framework.common.utils.Valid;
|
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.request.exec.ExecLogTailRequest;
|
||||||
import com.orion.ops.module.asset.entity.vo.ExecCommandHostVO;
|
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.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.ExecHostStatusEnum;
|
||||||
import com.orion.ops.module.asset.enums.ExecSourceEnum;
|
import com.orion.ops.module.asset.enums.ExecSourceEnum;
|
||||||
import com.orion.ops.module.asset.enums.ExecStatusEnum;
|
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.handler.host.exec.command.manager.ExecTaskManager;
|
||||||
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
|
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
|
||||||
import com.orion.ops.module.asset.service.ExecService;
|
import com.orion.ops.module.asset.service.ExecService;
|
||||||
|
import com.orion.ops.module.asset.service.HostConfigService;
|
||||||
import com.orion.web.servlet.web.Servlets;
|
import com.orion.web.servlet.web.Servlets;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -55,10 +59,8 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.function.Function;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +94,9 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
@Resource
|
@Resource
|
||||||
private AssetAuthorizedDataService assetAuthorizedDataService;
|
private AssetAuthorizedDataService assetAuthorizedDataService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private HostConfigService hostConfigService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ExecTaskManager execTaskManager;
|
private ExecTaskManager execTaskManager;
|
||||||
|
|
||||||
@@ -167,6 +172,7 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public ExecCommandVO reExecCommand(Long logId) {
|
public ExecCommandVO reExecCommand(Long logId) {
|
||||||
log.info("ExecService.reExecCommand start logId: {}", logId);
|
log.info("ExecService.reExecCommand start logId: {}", logId);
|
||||||
// 获取执行记录
|
// 获取执行记录
|
||||||
@@ -288,24 +294,31 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
@Override
|
@Override
|
||||||
public String getExecLogTailToken(ExecLogTailRequest request) {
|
public String getExecLogTailToken(ExecLogTailRequest request) {
|
||||||
Long execId = request.getExecId();
|
Long execId = request.getExecId();
|
||||||
List<Long> execHostIdList = request.getExecHostIdList();
|
List<Long> hostExecIdList = request.getHostExecIdList();
|
||||||
log.info("ExecService.getExecLogTailToken start execId: {}, execHostIdList: {}", execId, execHostIdList);
|
log.info("ExecService.getExecLogTailToken start execId: {}, hostExecIdList: {}", execId, hostExecIdList);
|
||||||
// 查询执行日志
|
// 查询执行日志
|
||||||
ExecLogDO execLog = execLogDAO.selectById(execId);
|
ExecLogDO execLog = execLogDAO.selectById(execId);
|
||||||
Valid.notNull(execLog, ErrorMessage.LOG_ABSENT);
|
Valid.notNull(execLog, ErrorMessage.LOG_ABSENT);
|
||||||
// 查询主机日志
|
// 查询主机日志
|
||||||
List<ExecHostLogDO> hostLogs;
|
List<ExecHostLogDO> hostLogs;
|
||||||
if (execHostIdList == null) {
|
if (hostExecIdList == null) {
|
||||||
hostLogs = execHostLogDAO.selectByLogId(execId);
|
hostLogs = execHostLogDAO.selectByLogId(execId);
|
||||||
} else {
|
} else {
|
||||||
hostLogs = execHostLogDAO.of()
|
hostLogs = execHostLogDAO.of()
|
||||||
.createWrapper()
|
.createWrapper()
|
||||||
.eq(ExecHostLogDO::getLogId, execId)
|
.eq(ExecHostLogDO::getLogId, execId)
|
||||||
.in(ExecHostLogDO::getId, execHostIdList)
|
.in(ExecHostLogDO::getId, hostExecIdList)
|
||||||
.then()
|
.then()
|
||||||
.list();
|
.list();
|
||||||
}
|
}
|
||||||
Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT);
|
Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT);
|
||||||
|
// 获取编码集
|
||||||
|
List<Long> hostIdList = hostLogs.stream()
|
||||||
|
.map(ExecHostLogDO::getHostId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
Map<Long, HostConfigVO> configMap = hostConfigService.getHostConfigList(hostIdList, HostConfigTypeEnum.SSH.getType())
|
||||||
|
.stream()
|
||||||
|
.collect(Collectors.toMap(HostConfigVO::getId, Function.identity()));
|
||||||
// 生成缓存
|
// 生成缓存
|
||||||
String token = UUIds.random19();
|
String token = UUIds.random19();
|
||||||
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
|
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
|
||||||
@@ -318,12 +331,17 @@ public class ExecServiceImpl implements ExecService {
|
|||||||
.id(s.getId())
|
.id(s.getId())
|
||||||
.hostId(s.getHostId())
|
.hostId(s.getHostId())
|
||||||
.path(s.getLogPath())
|
.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())
|
.build())
|
||||||
.collect(Collectors.toList()))
|
.collect(Collectors.toList()))
|
||||||
.build();
|
.build();
|
||||||
// 设置缓存
|
// 设置缓存
|
||||||
RedisStrings.setJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL, cache);
|
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;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ public class HostConfigServiceImpl implements HostConfigService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T extends GenericsDataModel> T getHostConfig(Long hostId, HostConfigTypeEnum type) {
|
public <T extends GenericsDataModel> T getHostConfig(Long hostId, HostConfigTypeEnum type) {
|
||||||
// 查询配置
|
// 查询配置
|
||||||
HostConfigDO config = hostConfigDAO.getHostConfigByHostId(hostId, type.getType());
|
HostConfigDO config = hostConfigDAO.getHostConfigByHostId(hostId, type.getType());
|
||||||
@@ -76,20 +75,19 @@ public class HostConfigServiceImpl implements HostConfigService {
|
|||||||
public List<HostConfigVO> getHostConfigList(Long hostId) {
|
public List<HostConfigVO> getHostConfigList(Long hostId) {
|
||||||
// 查询
|
// 查询
|
||||||
List<HostConfigDO> configs = hostConfigDAO.getHostConfigByHostId(hostId);
|
List<HostConfigDO> configs = hostConfigDAO.getHostConfigByHostId(hostId);
|
||||||
|
return configs.stream()
|
||||||
|
.map(this::convertHostConfig)
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<HostConfigVO> getHostConfigList(List<Long> hostIdList, String type) {
|
||||||
|
// 查询
|
||||||
|
List<HostConfigDO> configs = hostConfigDAO.getHostConfigByHostIdList(hostIdList, type);
|
||||||
// 返回
|
// 返回
|
||||||
return configs.stream()
|
return configs.stream()
|
||||||
.map(s -> {
|
.map(this::convertHostConfig)
|
||||||
// 获取配置
|
|
||||||
HostConfigTypeEnum type = HostConfigTypeEnum.of(s.getType());
|
|
||||||
if (type == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// 转为视图
|
|
||||||
HostConfigVO vo = HostConfigConvert.MAPPER.to(s);
|
|
||||||
Map<String, Object> config = type.getStrategyBean().toView(s.getConfig());
|
|
||||||
vo.setConfig(config);
|
|
||||||
return vo;
|
|
||||||
})
|
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
@@ -219,4 +217,23 @@ public class HostConfigServiceImpl implements HostConfigService {
|
|||||||
return insert;
|
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<String, Object> config = type.getStrategyBean().toView(row.getConfig());
|
||||||
|
vo.setConfig(config);
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface HostConfigRequest {
|
|||||||
*/
|
*/
|
||||||
export interface HostConfigQueryResponse {
|
export interface HostConfigQueryResponse {
|
||||||
id: number;
|
id: number;
|
||||||
|
hostId: number;
|
||||||
type: string;
|
type: string;
|
||||||
version: number;
|
version: number;
|
||||||
status: number;
|
status: number;
|
||||||
|
|||||||
@@ -14,8 +14,8 @@
|
|||||||
<div v-for="item in hosts"
|
<div v-for="item in hosts"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
class="exec-host-item"
|
class="exec-host-item"
|
||||||
:class="[ current === item.hostId ? 'exec-host-item-selected' : '' ]"
|
:class="[ current === item.id ? 'exec-host-item-selected' : '' ]"
|
||||||
@click="emits('selected', item.hostId)">
|
@click="emits('selected', item.id)">
|
||||||
<!-- 主机名称 -->
|
<!-- 主机名称 -->
|
||||||
<div class="exec-host-item-name">
|
<div class="exec-host-item-name">
|
||||||
<span class="host-name">{{ item.hostName }}</span>
|
<span class="host-name">{{ item.hostName }}</span>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="log-panel-container">
|
<div class="log-panel-container">
|
||||||
<!-- 执行主机 -->
|
<!-- 执行主机 -->
|
||||||
<log-panel-host class="host-container"
|
<log-panel-host class="host-container"
|
||||||
:current="currentHostId"
|
:current="currentHostExecId"
|
||||||
:hosts="command.hosts"
|
:hosts="command.hosts"
|
||||||
@selected="selectedHost"
|
@selected="selectedHost"
|
||||||
@back="back" />
|
@back="back" />
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
const emits = defineEmits(['back']);
|
const emits = defineEmits(['back']);
|
||||||
|
|
||||||
const currentHostId = ref(1);
|
const currentHostExecId = ref(1);
|
||||||
const command = ref<ExecCommandResponse>({
|
const command = ref<ExecCommandResponse>({
|
||||||
id: 50,
|
id: 50,
|
||||||
hosts: [{
|
hosts: [{
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
// 打开
|
// 打开
|
||||||
const open = (record: ExecCommandResponse) => {
|
const open = (record: ExecCommandResponse) => {
|
||||||
command.value = record;
|
command.value = record;
|
||||||
currentHostId.value = record.hosts[0].hostId;
|
currentHostExecId.value = record.hosts[0].id;
|
||||||
// 打开日志
|
// 打开日志
|
||||||
openLog();
|
openLog();
|
||||||
};
|
};
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
|
|
||||||
// 选中主机
|
// 选中主机
|
||||||
const selectedHost = (hostId: number) => {
|
const selectedHost = (hostId: number) => {
|
||||||
currentHostId.value = hostId;
|
currentHostExecId.value = hostId;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 返回
|
// 返回
|
||||||
|
|||||||
Reference in New Issue
Block a user