🔨 优化批量执行模块.

This commit is contained in:
lijiahang
2025-01-20 09:59:45 +08:00
parent 5ecb476be5
commit a001ab3f16
17 changed files with 462 additions and 582 deletions

View File

@@ -24,7 +24,8 @@ package org.dromara.visor.module.asset.handler.host.exec.command;
import org.dromara.visor.module.asset.define.AssetThreadPools;
import org.dromara.visor.module.asset.handler.host.exec.command.handler.ExecTaskHandler;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
import java.util.List;
/**
* 批量执行命令执行器
@@ -38,10 +39,11 @@ public class ExecTaskExecutors {
/**
* 执行命令
*
* @param command command
* @param id id
* @param execHostIdList execHostIdList
*/
public static void start(ExecCommandDTO command) {
AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(command));
public static void start(Long id, List<Long> execHostIdList) {
AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(id, execHostIdList));
}
}

View File

@@ -25,12 +25,13 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
import cn.orionsec.kit.lang.exception.AuthenticationException;
import cn.orionsec.kit.lang.exception.ConnectionRuntimeException;
import cn.orionsec.kit.lang.exception.SftpException;
import cn.orionsec.kit.lang.id.UUIds;
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Exceptions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
import cn.orionsec.kit.lang.utils.collect.Maps;
import cn.orionsec.kit.lang.utils.io.Streams;
import cn.orionsec.kit.net.host.SessionStore;
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
@@ -40,22 +41,29 @@ import com.alibaba.fastjson.JSON;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.constant.FileConst;
import org.dromara.visor.common.enums.BooleanBit;
import org.dromara.visor.common.enums.EndpointDefine;
import org.dromara.visor.common.interfaces.FileClient;
import org.dromara.visor.common.utils.PathUtils;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.module.asset.dao.ExecHostLogDAO;
import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
import org.dromara.visor.module.asset.enums.HostOsTypeEnum;
import org.dromara.visor.module.asset.handler.host.exec.log.manager.ExecLogManager;
import org.dromara.visor.module.asset.handler.host.jsch.SessionStores;
import org.dromara.visor.module.asset.service.TerminalService;
import org.dromara.visor.module.asset.utils.ExecUtils;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* 命令执行器 基类
@@ -75,16 +83,21 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
private static final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class);
protected final ExecCommandDTO execCommand;
@Getter
protected final Long id;
protected final ExecCommandHostDTO execHostCommand;
protected final Map<String, Object> builtParams;
private final TimeoutChecker<TimeoutEndpoint> timeoutChecker;
protected final TimeoutChecker<TimeoutEndpoint> timeoutChecker;
protected final ExecLogDO execLog;
protected ExecHostLogDO execHostLog;
@Getter
protected ExecHostStatusEnum status;
protected ExecHostLogDO updateRecord;
private TerminalConnectDTO connect;
private OutputStream logOutputStream;
@@ -99,24 +112,28 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
private volatile boolean interrupted;
public BaseExecCommandHandler(ExecCommandDTO execCommand,
ExecCommandHostDTO execHostCommand,
public BaseExecCommandHandler(Long id,
ExecLogDO execLog,
Map<String, Object> builtParams,
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
this.status = ExecHostStatusEnum.WAITING;
this.execCommand = execCommand;
this.execHostCommand = execHostCommand;
this.id = id;
this.execLog = execLog;
this.builtParams = builtParams;
this.timeoutChecker = timeoutChecker;
this.updateRecord = new ExecHostLogDO();
this.status = ExecHostStatusEnum.WAITING;
}
@Override
public void run() {
Long id = execHostCommand.getHostLogId();
Exception ex = null;
log.info("ExecCommandHandler run start id: {}, info: {}", id, JSON.toJSONString(execHostCommand));
// 更新状态
this.updateStatus(ExecHostStatusEnum.RUNNING, null);
log.info("ExecCommandHandler run start id: {}", id);
// 初始化数据以及修改状态
if (!this.initData()) {
return;
}
try {
// 初始化日志
this.initLogOutputStream();
// 执行命令
this.execCommand();
log.info("ExecCommandHandler run complete id: {}", id);
@@ -135,13 +152,47 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
}
/**
* 初始化日志输出流
* 初始化数据
*
* @throws Exception Exception
* @return pass
*/
protected void initLogOutputStream() throws Exception {
// 打开日志流
this.logOutputStream = fileClient.getContentOutputStream(execHostCommand.getLogPath());
private boolean initData() {
Exception ex = null;
try {
// 查询任务
this.execHostLog = execHostLogDAO.selectById(id);
Valid.notNull(this.execHostLog, ErrorMessage.TASK_ABSENT);
// 检查任务状态
this.status = ExecHostStatusEnum.of(execHostLog.getStatus());
Valid.eq(this.status, ExecHostStatusEnum.WAITING, ErrorMessage.TASK_ABSENT, ErrorMessage.ILLEGAL_STATUS);
// 获取主机会话
this.connect = terminalService.getTerminalConnectInfo(execHostLog.getHostId(), execLog.getUserId());
// 获取内置参数
Map<String, Object> commandParams = this.getCommandParams();
// 获取实际命令
String command = ExecUtils.format(execLog.getCommand(), commandParams);
// 获取日志路径
String logPath = fileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(execHostLog.getLogId(), execHostLog.getHostId()));
// 获取脚本路径
String scriptPath = null;
if (BooleanBit.toBoolean(execLog.getScriptExec())) {
scriptPath = this.buildScriptPath();
}
execHostLog.setCommand(command);
execHostLog.setParameter(JSON.toJSONString(commandParams));
execHostLog.setLogPath(logPath);
execHostLog.setScriptPath(scriptPath);
} catch (Exception e) {
log.error("BaseExecCommandHandler.initData error id: {}", id, e);
ex = e;
}
boolean passed = ex == null;
// 更新状态
this.updateStatus(passed ? ExecHostStatusEnum.RUNNING : ExecHostStatusEnum.FAILED, ex, (s) -> {
// 修改其他参数
s.setCommand(execHostLog.getCommand());
});
return passed;
}
/**
@@ -150,29 +201,36 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
* @throws IOException IOException
*/
protected void execCommand() throws Exception {
// 初始化日志
this.initLogOutputStream();
// 打开会话
TerminalConnectDTO connect = terminalService.getTerminalConnectInfo(execHostCommand.getHostId(), execCommand.getUserId());
this.sessionStore = SessionStores.openSessionStore(connect);
if (Booleans.isTrue(execCommand.getScriptExec())) {
if (BooleanBit.toBoolean(execLog.getScriptExec())) {
// 上传脚本文件
this.uploadScriptFile();
// 执行脚本文件
this.executor = sessionStore.getCommandExecutor(execHostCommand.getScriptPath());
this.executor = sessionStore.getCommandExecutor(execHostLog.getScriptPath());
} else {
// 执行命令
byte[] command = Strings.replaceCRLF(execHostCommand.getCommand()).getBytes(execHostCommand.getCharset());
byte[] command = execHostLog.getCommand().getBytes(connect.getCharset());
this.executor = sessionStore.getCommandExecutor(command);
}
// 执行命令
executor.timeout(execCommand.getTimeout(), TimeUnit.SECONDS, timeoutChecker);
executor.timeout(execLog.getTimeout(), TimeUnit.SECONDS, timeoutChecker);
executor.merge();
executor.transfer(logOutputStream);
executor.connect();
executor.exec();
}
/**
* 初始化日志输出流
*
* @throws Exception Exception
*/
protected void initLogOutputStream() throws Exception {
// 打开日志流
this.logOutputStream = fileClient.getContentOutputStream(execHostLog.getLogPath());
}
/**
* 上传脚本文件
*/
@@ -180,14 +238,14 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
SftpExecutor sftpExecutor = null;
try {
// 打开 sftp
sftpExecutor = sessionStore.getSftpExecutor(execHostCommand.getFileNameCharset());
sftpExecutor = sessionStore.getSftpExecutor(connect.getFileNameCharset());
sftpExecutor.connect();
// 文件上传必须要以 / 开头
String scriptPath = PathUtils.prependSeparator(execHostCommand.getScriptPath());
String scriptPath = PathUtils.prependSeparator(execHostLog.getScriptPath());
// 创建文件
sftpExecutor.touch(scriptPath);
// 写入命令
byte[] command = Strings.replaceCRLF(execHostCommand.getCommand()).getBytes(execHostCommand.getFileContentCharset());
byte[] command = execHostLog.getCommand().getBytes(connect.getFileContentCharset());
sftpExecutor.write(scriptPath, command);
// 修改权限
sftpExecutor.changeMode(scriptPath, 777);
@@ -207,16 +265,16 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
// 执行回调
if (this.interrupted) {
// 中断执行
this.updateStatus(ExecHostStatusEnum.INTERRUPTED, null);
this.updateStatus(ExecHostStatusEnum.INTERRUPTED, null, null);
} else if (e != null) {
// 执行失败
this.updateStatus(ExecHostStatusEnum.FAILED, e);
this.updateStatus(ExecHostStatusEnum.FAILED, e, null);
} else if (executor.isTimeout()) {
// 更新执行超时
this.updateStatus(ExecHostStatusEnum.TIMEOUT, null);
this.updateStatus(ExecHostStatusEnum.TIMEOUT, null, null);
} else {
// 更新执行完成
this.updateStatus(ExecHostStatusEnum.COMPLETED, null);
this.updateStatus(ExecHostStatusEnum.COMPLETED, null, null);
}
}
@@ -239,33 +297,45 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
*
* @param status status
* @param ex ex
* @param filler filler
*/
private void updateStatus(ExecHostStatusEnum status, Exception ex) {
private void updateStatus(ExecHostStatusEnum status, Exception ex, Consumer<ExecHostLogDO> filler) {
this.status = status;
Long id = execHostCommand.getHostLogId();
String statusName = status.name();
execHostLog.setStatus(statusName);
log.info("BaseExecCommandHandler.updateStatus start id: {}, status: {}", id, statusName);
try {
updateRecord.setId(id);
updateRecord.setStatus(statusName);
if (ExecHostStatusEnum.RUNNING.equals(status)) {
// 运行中
updateRecord.setStartTime(new Date());
execHostLog.setStartTime(new Date());
} else if (ExecHostStatusEnum.COMPLETED.equals(status)) {
// 完成
updateRecord.setFinishTime(new Date());
updateRecord.setExitCode(executor.getExitCode());
execHostLog.setFinishTime(new Date());
execHostLog.setExitCode(executor.getExitCode());
this.exitCode = executor.getExitCode();
} else if (ExecHostStatusEnum.FAILED.equals(status)) {
// 失败
updateRecord.setFinishTime(new Date());
updateRecord.setErrorMessage(this.getErrorMessage(ex));
execHostLog.setFinishTime(new Date());
execHostLog.setErrorMessage(this.getErrorMessage(ex));
} else if (ExecHostStatusEnum.TIMEOUT.equals(status)) {
// 超时
updateRecord.setFinishTime(new Date());
execHostLog.setFinishTime(new Date());
} else if (ExecHostStatusEnum.INTERRUPTED.equals(status)) {
// 中断
updateRecord.setFinishTime(new Date());
execHostLog.setFinishTime(new Date());
}
// 选择性更新
ExecHostLogDO updateRecord = ExecHostLogDO.builder()
.id(execHostLog.getId())
.status(execHostLog.getStatus())
.exitCode(execHostLog.getExitCode())
.startTime(execHostLog.getStartTime())
.finishTime(execHostLog.getFinishTime())
.errorMessage(execHostLog.getErrorMessage())
.build();
// 填充参数
if (filler != null) {
filler.accept(updateRecord);
}
int effect = execHostLogDAO.updateById(updateRecord);
log.info("BaseExecCommandHandler.updateStatus finish id: {}, effect: {}", id, effect);
@@ -281,8 +351,7 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
@Override
public void interrupt() {
log.info("BaseExecCommandHandler.interrupt id: {}, interrupted: {}, closed: {}",
execHostCommand.getHostLogId(), interrupted, closed);
log.info("BaseExecCommandHandler.interrupt id: {}, interrupted: {}, closed: {}", id, interrupted, closed);
if (this.interrupted || this.closed) {
return;
}
@@ -294,8 +363,7 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
@Override
public void close() {
log.info("BaseExecCommandHandler.closed id: {}, closed: {}",
execHostCommand.getHostLogId(), closed);
log.info("BaseExecCommandHandler.closed id: {}, closed: {}", id, closed);
if (this.closed) {
return;
}
@@ -303,7 +371,7 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
Streams.close(logOutputStream);
Streams.close(executor);
Streams.close(sessionStore);
execLogManager.asyncCloseTailFile(execHostCommand.getLogPath());
execLogManager.asyncCloseTailFile(execHostLog.getLogPath());
}
/**
@@ -336,9 +404,40 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
return Strings.retain(message, 250);
}
@Override
public Long getHostId() {
return execHostCommand.getHostId();
/**
* 获取命令实际参数
*
* @return params
*/
private Map<String, Object> getCommandParams() {
String uuid = UUIds.random();
Map<String, Object> params = Maps.newMap(builtParams);
params.put("hostId", connect.getHostId());
params.put("hostName", connect.getHostName());
params.put("hostCode", connect.getHostCode());
params.put("hostAddress", connect.getHostAddress());
params.put("hostPort", connect.getHostPort());
params.put("hostUsername", connect.getUsername());
params.put("hostUuid", uuid);
params.put("hostUuidShort", uuid.replace("-", Strings.EMPTY));
params.put("osType", connect.getOsType());
params.put("charset", connect.getCharset());
params.put("scriptPath", execHostLog.getScriptPath());
return params;
}
/**
* 构建脚本路径
*
* @return scriptPath
*/
private String buildScriptPath() {
HostOsTypeEnum os = HostOsTypeEnum.of(connect.getOsType());
String name = FileConst.EXEC
+ "/" + execHostLog.getLogId()
+ "/" + id
+ os.getScriptSuffix();
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.equals(os), connect.getUsername(), FileConst.SCRIPT, name);
}
}

View File

@@ -24,15 +24,16 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
import cn.orionsec.kit.lang.utils.ansi.style.color.AnsiForeground;
import cn.orionsec.kit.lang.utils.time.Dates;
import cn.orionsec.kit.net.host.ssh.ExitCode;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.enums.BooleanBit;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
import java.util.Map;
/**
* 命令执行器 ansi 日志输出
@@ -41,10 +42,13 @@ import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecComman
* @version 1.0.0
* @since 2024/4/25 18:19
*/
public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
public class ExecCommandDetailHandler extends BaseExecCommandHandler {
public ExecCommandAnsiHandler(ExecCommandDTO execCommand, ExecCommandHostDTO execHostCommand, TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
super(execCommand, execHostCommand, timeoutChecker);
public ExecCommandDetailHandler(Long id,
ExecLogDO execLog,
Map<String, Object> builtParams,
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
super(id, execLog, builtParams, timeoutChecker);
}
@Override
@@ -56,52 +60,52 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
.append(this.getCurrentTime())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "执行记录: ")
.append(execCommand.getLogId())
.append(execLog.getId())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "执行描述: ")
.append(execCommand.getDescription())
.append(execLog.getDescription())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "执行用户: ")
.append(execCommand.getUsername());
.append(execLog.getUsername());
// 非系统用户执行添加 userId
if (Const.SYSTEM_USER_ID.equals(execCommand.getUserId())) {
if (Const.SYSTEM_USER_ID.equals(execLog.getUserId())) {
appender.newLine();
} else {
appender.append(" (")
.append(execCommand.getUserId())
.append(execLog.getUserId())
.append(")")
.newLine();
}
// 执行序列
if (execCommand.getExecSeq() != null) {
if (execLog.getExecSeq() != null) {
appender.append(AnsiForeground.BRIGHT_BLUE, "执行序列: ")
.append('#')
.append(execCommand.getExecSeq())
.append(execLog.getExecSeq())
.newLine();
}
appender.append(AnsiForeground.BRIGHT_BLUE, "执行主机: ")
.append(execHostCommand.getHostName())
.append(execHostLog.getHostName())
.append(" (")
.append(execHostCommand.getHostId())
.append(execHostLog.getHostId())
.append(")")
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "主机地址: ")
.append(execHostCommand.getHostAddress())
.append(execHostLog.getHostAddress())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "超时时间: ")
.append(execCommand.getTimeout())
.append(execLog.getTimeout())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "脚本执行: ")
.append(execCommand.getScriptExec())
.append(execLog.getScriptExec())
.newLine()
.newLine()
.append(AnsiForeground.BRIGHT_GREEN, "> 执行命令 ")
.newLine()
.append(execHostCommand.getCommand())
.append(execHostLog.getCommand())
.newLine()
.newLine();
// 非脚本执行拼接开始执行日志
if (!Booleans.isTrue(execCommand.getScriptExec())) {
if (!BooleanBit.toBoolean(execLog.getScriptExec())) {
appender.append(AnsiForeground.BRIGHT_GREEN, "> 开始执行命令 ")
.append(this.getCurrentTime())
.newLine();
@@ -119,7 +123,7 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
.append(this.getCurrentTime())
.newLine()
.append(AnsiForeground.BRIGHT_BLUE, "文件路径: ")
.append(execHostCommand.getScriptPath())
.append(execHostLog.getScriptPath())
.newLine();
this.appendLog(startAppender);
// 上传脚本文件
@@ -170,9 +174,9 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行超时 ")
.append(this.getCurrentTime())
.newLine();
} else {
long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime();
Integer exitCode = updateRecord.getExitCode();
} else if (this.status == ExecHostStatusEnum.COMPLETED) {
long ms = execHostLog.getFinishTime().getTime() - execHostLog.getStartTime().getTime();
Integer exitCode = execHostLog.getExitCode();
// 执行完成
appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ")
.append(this.getCurrentTime())

View File

@@ -25,8 +25,9 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import java.util.Map;
/**
* 命令执行器 原始日志输出
@@ -38,8 +39,11 @@ import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecComman
@Slf4j
public class ExecCommandOriginHandler extends BaseExecCommandHandler {
public ExecCommandOriginHandler(ExecCommandDTO execCommand, ExecCommandHostDTO execHostCommand, TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
super(execCommand, execHostCommand, timeoutChecker);
public ExecCommandOriginHandler(Long id,
ExecLogDO execLog,
Map<String, Object> builtParams,
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
super(id, execLog, builtParams, timeoutChecker);
}
}

View File

@@ -22,11 +22,14 @@
*/
package org.dromara.visor.module.asset.handler.host.exec.command.handler;
import cn.orionsec.kit.lang.id.UUIds;
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
import cn.orionsec.kit.lang.support.timeout.TimeoutCheckers;
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.Threads;
import cn.orionsec.kit.lang.utils.Valid;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.io.Streams;
import cn.orionsec.kit.lang.utils.time.Dates;
@@ -34,17 +37,17 @@ import cn.orionsec.kit.net.host.ssh.ExitCode;
import cn.orionsec.kit.spring.SpringHolder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.constant.ExtraFieldConst;
import org.dromara.visor.module.asset.dao.ExecLogDAO;
import org.dromara.visor.module.asset.define.AssetThreadPools;
import org.dromara.visor.module.asset.define.config.AppExecLogConfig;
import org.dromara.visor.module.asset.define.config.AppLogConfig;
import org.dromara.visor.module.asset.define.message.ExecMessageDefine;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
import org.dromara.visor.module.asset.enums.ExecStatusEnum;
import org.dromara.visor.module.asset.handler.host.exec.command.manager.ExecTaskManager;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
import org.dromara.visor.module.asset.utils.ExecUtils;
import org.dromara.visor.module.infra.api.SystemMessageApi;
import org.dromara.visor.module.infra.entity.dto.message.SystemMessageDTO;
@@ -63,15 +66,21 @@ import java.util.Map;
@Slf4j
public class ExecTaskHandler implements IExecTaskHandler {
private static final AppLogConfig appLogConfig = SpringHolder.getBean(AppLogConfig.class);
private static final ExecLogDAO execLogDAO = SpringHolder.getBean(ExecLogDAO.class);
private static final ExecTaskManager execTaskManager = SpringHolder.getBean(ExecTaskManager.class);
private static final AppExecLogConfig appExecLogConfig = SpringHolder.getBean(AppExecLogConfig.class);
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
private final ExecCommandDTO execCommand;
private final Long id;
private final List<Long> execHostIdList;
private ExecLogDO execLog;
private Map<String, Object> builtParams;
private TimeoutChecker<TimeoutEndpoint> timeoutChecker;
@@ -80,14 +89,19 @@ public class ExecTaskHandler implements IExecTaskHandler {
private Date startTime;
public ExecTaskHandler(ExecCommandDTO execCommand) {
this.execCommand = execCommand;
public ExecTaskHandler(Long id, List<Long> execHostIdList) {
this.id = id;
this.execHostIdList = execHostIdList;
this.handlers = Lists.newList();
}
@Override
public void run() {
Long id = execCommand.getLogId();
log.info("ExecTaskHandler start id: {}", id);
// 初始化数据
if (!this.initData()) {
return;
}
// 添加任务
execTaskManager.addTask(id, this);
log.info("ExecTaskHandler.run start id: {}", id);
@@ -115,17 +129,37 @@ public class ExecTaskHandler implements IExecTaskHandler {
@Override
public void interrupt() {
log.info("ExecTaskHandler-interrupt id: {}", execCommand.getLogId());
log.info("ExecTaskHandler-interrupt id: {}", id);
handlers.forEach(IExecCommandHandler::interrupt);
}
@Override
public void close() {
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
log.info("ExecTaskHandler-close id: {}", id);
Streams.close(timeoutChecker);
this.handlers.forEach(Streams::close);
}
/**
* 初始化数据
*
* @return pass
*/
private boolean initData() {
try {
// 查询任务
this.execLog = execLogDAO.selectById(id);
Valid.notNull(execLog, ErrorMessage.TASK_ABSENT);
Valid.eq(execLog.getStatus(), ExecStatusEnum.WAITING.name(), ErrorMessage.ILLEGAL_STATUS);
// 获取内置变量
this.builtParams = this.getBaseBuiltinParams();
return true;
} catch (Exception e) {
log.error("ExecTaskHandler.init error id: {}", id, e);
return false;
}
}
/**
* 执行主机命令
*
@@ -133,19 +167,18 @@ public class ExecTaskHandler implements IExecTaskHandler {
*/
private void runHostCommand() throws Exception {
// 超时检查
if (execCommand.getTimeout() != 0) {
if (execLog.getTimeout() != 0) {
this.timeoutChecker = TimeoutCheckers.create();
AssetThreadPools.TIMEOUT_CHECK.execute(this.timeoutChecker);
}
// 执行命令
List<ExecCommandHostDTO> hosts = execCommand.getHosts();
if (hosts.size() == 1) {
if (execHostIdList.size() == 1) {
// 单个主机直接执行
IExecCommandHandler handler = this.createCommandHandler(hosts.get(0));
IExecCommandHandler handler = this.createCommandHandler(execHostIdList.get(0));
handlers.add(handler);
handler.run();
} else {
hosts.stream()
execHostIdList.stream()
.map(this::createCommandHandler)
.forEach(handlers::add);
// 多个主机异步阻塞执行
@@ -156,16 +189,16 @@ public class ExecTaskHandler implements IExecTaskHandler {
/**
* 创建命令执行器
*
* @param host host
* @param execHostId execHostId
* @return handler
*/
private IExecCommandHandler createCommandHandler(ExecCommandHostDTO host) {
if (Booleans.isTrue(appExecLogConfig.getAppendAnsi())) {
// ansi 日志
return new ExecCommandAnsiHandler(execCommand, host, timeoutChecker);
private IExecCommandHandler createCommandHandler(Long execHostId) {
if (Booleans.isTrue(appLogConfig.getExecAppendAnsi())) {
// 详细日志
return new ExecCommandDetailHandler(id, execLog, builtParams, timeoutChecker);
} else {
// 原始日志
return new ExecCommandOriginHandler(execCommand, host, timeoutChecker);
return new ExecCommandOriginHandler(id, execLog, builtParams, timeoutChecker);
}
}
@@ -175,7 +208,6 @@ public class ExecTaskHandler implements IExecTaskHandler {
* @param status status
*/
private void updateStatus(ExecStatusEnum status) {
Long id = execCommand.getLogId();
try {
String statusName = status.name();
log.info("ExecTaskHandler-updateStatus start id: {}, status: {}", id, statusName);
@@ -214,16 +246,43 @@ public class ExecTaskHandler implements IExecTaskHandler {
}
// 参数
Map<String, Object> params = new HashMap<>();
params.put(ExtraFieldConst.ID, execCommand.getLogId());
params.put(ExtraFieldConst.ID, id);
params.put(ExtraFieldConst.TIME, Dates.format(this.startTime, Dates.MD_HM));
SystemMessageDTO message = SystemMessageDTO.builder()
.receiverId(execCommand.getUserId())
.receiverUsername(execCommand.getUsername())
.relKey(String.valueOf(execCommand.getLogId()))
.receiverId(execLog.getUserId())
.receiverUsername(execLog.getUsername())
.relKey(String.valueOf(id))
.params(params)
.build();
// 发送
systemMessageApi.create(ExecMessageDefine.EXEC_FAILED, message);
}
/**
* 获取基础内置参数
*
* @return params
*/
private Map<String, Object> getBaseBuiltinParams() {
String uuid = UUIds.random();
Date date = new Date();
// 输入参数
Map<String, Object> params = ExecUtils.extraSchemaParams(execLog.getParameterSchema());
// 添加内置参数
params.put("userId", execLog.getUserId());
params.put("username", execLog.getUsername());
params.put("source", execLog.getSource());
params.put("sourceId", execLog.getSourceId());
params.put("seq", execLog.getExecSeq());
params.put("execId", id);
params.put("scriptExec", execLog.getScriptExec());
params.put("uuid", uuid);
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
params.put("timestampMillis", date.getTime());
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
params.put("date", Dates.format(date, Dates.YMD));
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
return params;
}
}

View File

@@ -61,10 +61,10 @@ public interface IExecCommandHandler extends Runnable, SafeCloseable {
Integer getExitCode();
/**
* 获取主机 id
* 获取任务 id
*
* @return hostId
*/
Long getHostId();
Long getId();
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.exec.command.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 批量执行启动对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 15:46
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExecCommandDTO {
/**
* logId
*/
private Long logId;
/**
* 用户id
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 执行描述
*/
private String description;
/**
* 执行序列
*/
private Integer execSeq;
/**
* 超时时间
*/
private Integer timeout;
/**
* 是否使用脚本执行
*/
private Boolean scriptExec;
/**
* 执行主机
*/
private List<ExecCommandHostDTO> hosts;
}

View File

@@ -1,98 +0,0 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.handler.host.exec.command.model;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 批量执行启动主机对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/3/11 15:46
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExecCommandHostDTO {
/**
* hostLogId
*/
private Long hostLogId;
/**
* hostId
*/
private Long hostId;
/**
* 主机名称
*/
private String hostName;
/**
* 主机地址
*/
private String hostAddress;
/**
* 日志文件路径
*/
private String logPath;
/**
* 脚本路径
*/
private String scriptPath;
/**
* 执行命令
*/
private String command;
/**
* 主机用户
*/
private String username;
/**
* 命令编码
*/
private String charset;
/**
* 文件名称编码
*/
private String fileNameCharset;
/**
* 文件内容编码
*/
private String fileContentCharset;
}

View File

@@ -82,6 +82,9 @@ public class ExecLogManager {
* @param path path
*/
public void asyncCloseTailFile(String path) {
if (path == null) {
return;
}
Threads.start(() -> {
try {
// 获取当前路径的全部追踪器

View File

@@ -29,8 +29,9 @@ import cn.orionsec.kit.ext.tail.mode.FileOffsetMode;
import cn.orionsec.kit.spring.SpringHolder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
import org.dromara.visor.module.asset.define.config.AppTrackerConfig;
import org.dromara.visor.module.asset.define.config.AppLogConfig;
import org.dromara.visor.module.asset.entity.dto.ExecHostLogTailDTO;
import org.dromara.visor.module.asset.handler.host.exec.log.constant.LogConst;
import org.springframework.web.socket.WebSocketSession;
@@ -45,7 +46,7 @@ import org.springframework.web.socket.WebSocketSession;
@Slf4j
public class ExecLogTracker implements IExecLogTracker {
private static final AppTrackerConfig trackerConfig = SpringHolder.getBean(AppTrackerConfig.class);
private static final AppLogConfig appLogConfig = SpringHolder.getBean(AppLogConfig.class);
private final WebSocketSession session;
@@ -76,9 +77,9 @@ public class ExecLogTracker implements IExecLogTracker {
try {
this.tracker = new DelayTrackerListener(absolutePath, this);
tracker.charset(config.getCharset());
tracker.delayMillis(trackerConfig.getDelay());
tracker.offset(FileOffsetMode.LINE, trackerConfig.getOffset());
tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, trackerConfig.getWaitTimes());
tracker.delayMillis(appLogConfig.getTrackerDelay());
tracker.offset(FileOffsetMode.LINE, appLogConfig.getTrackerOffset());
tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, Const.N_10);
// 开始监听文件
tracker.run();
} catch (Exception e) {

View File

@@ -36,21 +36,13 @@ import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
public interface ExecCommandService {
/**
* 批量执行命令
* 执行命令
*
* @param request request
* @return result
*/
ExecLogVO execCommand(ExecCommandRequest request);
/**
* 批量执行命令
*
* @param request request
* @return result
*/
ExecLogVO execCommandWithSource(ExecCommandExecDTO request);
/**
* 重新执行命令
*
@@ -59,4 +51,20 @@ public interface ExecCommandService {
*/
ExecLogVO reExecCommand(Long logId);
/**
* 执行命令
*
* @param request request
* @return result
*/
ExecLogVO execCommandWithSource(ExecCommandExecDTO request);
/**
* 创建执行命令
*
* @param request request
* @return result
*/
ExecLogVO createCommandWithSource(ExecCommandExecDTO request);
}

View File

@@ -22,26 +22,15 @@
*/
package org.dromara.visor.module.asset.service.impl;
import cn.orionsec.kit.lang.annotation.Keep;
import cn.orionsec.kit.lang.function.Functions;
import cn.orionsec.kit.lang.id.UUIds;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.Valid;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.collect.Maps;
import cn.orionsec.kit.lang.utils.json.matcher.NoMatchStrategy;
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatter;
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatters;
import cn.orionsec.kit.lang.utils.time.Dates;
import cn.orionsec.kit.spring.SpringHolder;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.constant.FileConst;
import org.dromara.visor.common.enums.EndpointDefine;
import org.dromara.visor.common.interfaces.FileClient;
import org.dromara.visor.common.security.LoginUser;
import org.dromara.visor.common.utils.PathUtils;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
import org.dromara.visor.module.asset.convert.ExecConvert;
@@ -54,25 +43,19 @@ import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.dto.ExecCommandExecDTO;
import org.dromara.visor.module.asset.entity.dto.ExecParameterSchemaDTO;
import org.dromara.visor.module.asset.entity.request.exec.ExecCommandRequest;
import org.dromara.visor.module.asset.entity.vo.ExecHostLogVO;
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
import org.dromara.visor.module.asset.enums.*;
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
import org.dromara.visor.module.asset.handler.host.exec.command.ExecTaskExecutors;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
import org.dromara.visor.module.asset.service.ExecCommandService;
import org.dromara.visor.module.asset.service.HostConfigService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@@ -86,15 +69,8 @@ import java.util.stream.Collectors;
@Service
public class ExecCommandServiceImpl implements ExecCommandService {
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
.noMatchStrategy(NoMatchStrategy.KEEP);
private static final int DESC_OMIT = 60;
@Keep
@Resource
private FileClient logsFileClient;
@Resource
private ExecLogDAO execLogDAO;
@@ -104,17 +80,12 @@ public class ExecCommandServiceImpl implements ExecCommandService {
@Resource
private HostDAO hostDAO;
@Resource
private HostConfigService hostConfigService;
@Resource
private AssetAuthorizedDataService assetAuthorizedDataService;
@Override
@Transactional(rollbackFor = Exception.class)
public ExecLogVO execCommand(ExecCommandRequest request) {
log.info("ExecService.startExecCommand start params: {}", JSON.toJSONString(request));
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
log.info("ExecService.execCommand start params: {}", JSON.toJSONString(request));
LoginUser user = Valid.notNull(SecurityUtils.getLoginUser());
Long userId = user.getId();
List<Long> hostIdList = request.getHostIdList();
@@ -123,26 +94,58 @@ public class ExecCommandServiceImpl implements ExecCommandService {
hostIdList.removeIf(s -> !authorizedHostIdList.contains(s));
log.info("ExecService.startExecCommand host hostList: {}", hostIdList);
Valid.notEmpty(hostIdList, ErrorMessage.CHECK_AUTHORIZED_HOST);
// 执行命令
// 创建命令
ExecCommandExecDTO execRequest = ExecConvert.MAPPER.to(request);
execRequest.setUserId(userId);
execRequest.setUsername(user.getUsername());
execRequest.setSource(ExecSourceEnum.BATCH.name());
execRequest.setExecMode(ExecModeEnum.MANUAL.name());
// 调用执行
return this.execCommandWithSource(execRequest);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ExecLogVO reExecCommand(Long logId) {
log.info("ExecService.reExecCommand start logId: {}", logId);
// 获取执行记录
ExecLogDO execLog = execLogDAO.selectByIdSource(logId, ExecSourceEnum.BATCH.name());
Valid.notNull(execLog, ErrorMessage.DATA_ABSENT);
// 获取执行主机
List<ExecHostLogDO> hostLogs = execHostLogDAO.selectByLogId(logId);
Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT);
List<Long> hostIdList = hostLogs.stream()
.map(ExecHostLogDO::getHostId)
.collect(Collectors.toList());
// 调用创建任务
ExecCommandRequest request = ExecCommandRequest.builder()
.description(execLog.getDescription())
.timeout(execLog.getTimeout())
.scriptExec(execLog.getScriptExec())
.command(execLog.getCommand())
.parameterSchema(execLog.getParameterSchema())
.hostIdList(hostIdList)
.build();
// 调用执行
return SpringHolder.getBean(ExecCommandService.class).execCommand(request);
}
@Override
public ExecLogVO execCommandWithSource(ExecCommandExecDTO request) {
log.info("ExecService.execCommandWithSource start params: {}", JSON.toJSONString(request));
// 上下文调用执行
ExecLogVO result = SpringHolder.getBean(ExecCommandService.class).createCommandWithSource(request);
// 执行命令
ExecTaskExecutors.start(result.getId(), Lists.map(result.getHosts(), ExecHostLogVO::getId));
return result;
}
@Override
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public ExecLogVO createCommandWithSource(ExecCommandExecDTO request) {
log.info("ExecService.createCommandWithSource start params: {}", JSON.toJSONString(request));
String command = request.getCommand();
List<Long> hostIdList = request.getHostIdList();
// 查询主机信息
List<HostDO> hosts = hostDAO.selectBatchIds(hostIdList);
// 查询主机配置
// TODO 待优化
Map<Long, HostSshConfigModel> hostConfigMap = hostConfigService.buildHostConfigMap(hosts, HostTypeEnum.SSH);
// 插入日志
ExecLogDO execLog = ExecLogDO.builder()
.userId(request.getUserId())
@@ -166,17 +169,19 @@ public class ExecCommandServiceImpl implements ExecCommandService {
.build();
execLogDAO.insert(execLog);
Long execId = execLog.getId();
// 获取内置参数
Map<String, Object> builtinParams = this.getBaseBuiltinParams(execId, request);
// 设置主机日志
List<ExecHostLogDO> execHostLogs = hosts.stream()
.map(s -> this.convertExecHostLog(s, execLog, hostConfigMap.get(s.getId()), builtinParams))
.map(s -> ExecHostLogDO.builder()
.logId(execLog.getId())
.hostId(s.getId())
.hostName(s.getName())
.hostAddress(s.getAddress())
.status(ExecHostStatusEnum.WAITING.name())
.build())
.collect(Collectors.toList());
execHostLogDAO.insertBatch(execHostLogs);
// 操作日志
OperatorLogs.add(OperatorLogs.LOG_ID, execId);
// 开始执行
this.startExec(execLog, execHostLogs, hostConfigMap);
// 返回
ExecLogVO result = ExecLogConvert.MAPPER.to(execLog);
List<ExecHostLogVO> resultHosts = ExecHostLogConvert.MAPPER.to(execHostLogs);
@@ -184,218 +189,4 @@ public class ExecCommandServiceImpl implements ExecCommandService {
return result;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ExecLogVO reExecCommand(Long logId) {
log.info("ExecService.reExecCommand start logId: {}", logId);
// 获取执行记录
ExecLogDO execLog = execLogDAO.selectByIdSource(logId, ExecSourceEnum.BATCH.name());
Valid.notNull(execLog, ErrorMessage.DATA_ABSENT);
// 获取执行主机
List<ExecHostLogDO> hostLogs = execHostLogDAO.selectByLogId(logId);
Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT);
List<Long> hostIdList = hostLogs.stream()
.map(ExecHostLogDO::getHostId)
.collect(Collectors.toList());
// 调用执行方法
ExecCommandRequest request = ExecCommandRequest.builder()
.description(execLog.getDescription())
.timeout(execLog.getTimeout())
.scriptExec(execLog.getScriptExec())
.command(execLog.getCommand())
.parameterSchema(execLog.getParameterSchema())
.hostIdList(hostIdList)
.build();
return this.execCommand(request);
}
/**
* 开始执行命令
*
* @param execLog execLog
* @param execHostLogs hostLogs
* @param hostConfigMap hostConfigMap
*/
private void startExec(ExecLogDO execLog,
List<ExecHostLogDO> execHostLogs,
Map<Long, HostSshConfigModel> hostConfigMap) {
// 执行主机
List<ExecCommandHostDTO> hosts = execHostLogs.stream()
.map(s -> {
HostSshConfigModel config = hostConfigMap.get(s.getHostId());
return ExecCommandHostDTO.builder()
.hostId(s.getHostId())
.hostLogId(s.getId())
.hostName(s.getHostName())
.hostAddress(s.getHostAddress())
.command(s.getCommand())
.logPath(s.getLogPath())
.scriptPath(s.getScriptPath())
.username(config.getUsername())
.charset(config.getCharset())
.fileNameCharset(config.getFileNameCharset())
.fileContentCharset(config.getFileContentCharset())
.build();
}).collect(Collectors.toList());
// 执行信息
ExecCommandDTO exec = ExecCommandDTO.builder()
.logId(execLog.getId())
.userId(execLog.getUserId())
.username(execLog.getUsername())
.description(execLog.getDescription())
.execSeq(execLog.getExecSeq())
.timeout(execLog.getTimeout())
.scriptExec(ScriptExecEnum.isEnabled(execLog.getScriptExec()))
.hosts(hosts)
.build();
// 开始执行
ExecTaskExecutors.start(exec);
}
/**
* 转换为 execHostLog
*
* @param host host
* @param execLog execLog
* @param config config
* @param builtinParams builtinParams
* @return execHostLog
*/
private ExecHostLogDO convertExecHostLog(HostDO host,
ExecLogDO execLog,
HostSshConfigModel config,
Map<String, Object> builtinParams) {
Long execId = execLog.getId();
Long hostId = host.getId();
// 脚本路径
String scriptPath = null;
if (ScriptExecEnum.isEnabled(execLog.getScriptExec())) {
scriptPath = this.buildScriptPath(config.getUsername(), host.getOsType(), execId, hostId);
}
// 获取参数
String parameter = JSON.toJSONString(this.getHostParams(builtinParams, host, config, scriptPath));
return ExecHostLogDO.builder()
.logId(execId)
.hostId(hostId)
.hostName(host.getName())
.hostAddress(host.getAddress())
.status(ExecHostStatusEnum.WAITING.name())
.command(FORMATTER.format(execLog.getCommand(), parameter))
.parameter(parameter)
.logPath(this.buildLogPath(execId, hostId))
.scriptPath(scriptPath)
.build();
}
/**
* 获取基础内置参数
*
* @param execId execId
* @param request request
* @return params
*/
private Map<String, Object> getBaseBuiltinParams(Long execId, ExecCommandExecDTO request) {
String uuid = UUIds.random();
Date date = new Date();
// 输入参数
Map<String, Object> params = this.extraSchemaParams(request.getParameterSchema());
// 添加内置参数
params.put("userId", request.getUserId());
params.put("username", request.getUsername());
params.put("source", request.getSource());
params.put("sourceId", request.getSourceId());
params.put("seq", request.getExecSeq());
params.put("execId", execId);
params.put("scriptExec", request.getScriptExec());
params.put("uuid", uuid);
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
params.put("timestampMillis", date.getTime());
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
params.put("date", Dates.format(date, Dates.YMD));
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
return params;
}
/**
* 获取主机参数
*
* @param baseParams baseParams
* @param host host
* @param config config
* @param scriptPath scriptPath
* @return params
*/
private Map<String, Object> getHostParams(Map<String, Object> baseParams,
HostDO host,
HostSshConfigModel config,
String scriptPath) {
String uuid = UUIds.random();
Map<String, Object> params = Maps.newMap(baseParams);
params.put("hostId", host.getId());
params.put("hostName", host.getName());
params.put("hostCode", host.getCode());
params.put("hostAddress", host.getAddress());
params.put("hostPort", host.getPort());
params.put("hostUuid", uuid);
params.put("hostUuidShort", uuid.replace("-", Strings.EMPTY));
params.put("hostUsername", config.getUsername());
params.put("osType", host.getOsType());
params.put("charset", config.getCharset());
params.put("scriptPath", scriptPath);
return params;
}
/**
* 提取参数
*
* @param parameterSchema parameterSchema
* @return params
*/
private Map<String, Object> extraSchemaParams(String parameterSchema) {
List<ExecParameterSchemaDTO> schemaList = JSON.parseArray(parameterSchema, ExecParameterSchemaDTO.class);
if (Lists.isEmpty(schemaList)) {
return Maps.newMap();
}
// 解析参数
return schemaList.stream()
.collect(Collectors.toMap(ExecParameterSchemaDTO::getName,
s -> {
Object value = s.getValue();
if (value == null) {
value = Const.EMPTY;
}
return value;
},
Functions.right()));
}
/**
* 构建日志路径
*
* @param logId logId
* @param hostId hostId
* @return logPath
*/
private String buildLogPath(Long logId, Long hostId) {
return logsFileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(logId, hostId));
}
/**
* 侯建脚本路径
*
* @param username username
* @param osType osType
* @param logId logId
* @param hostId hostId
* @return scriptPath
*/
private String buildScriptPath(String username, String osType, Long logId, Long hostId) {
HostOsTypeEnum os = HostOsTypeEnum.of(osType);
String name = FileConst.EXEC
+ "/" + logId
+ "/" + hostId
+ os.getScriptSuffix();
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.equals(os), username, FileConst.SCRIPT, name);
}
}

View File

@@ -99,7 +99,7 @@ public class ExecHostLogServiceImpl implements ExecHostLogService {
.map(execTaskManager::getTask)
.map(IExecTaskHandler::getHandlers)
.flatMap(s -> s.stream()
.filter(h -> h.getHostId().equals(record.getHostId()))
.filter(h -> id.equals(h.getId()))
.findFirst())
.ifPresent(IExecCommandHandler::interrupt);
// 删除

View File

@@ -27,6 +27,7 @@ import cn.orionsec.kit.lang.utils.Booleans;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.time.cron.Cron;
import cn.orionsec.kit.spring.SpringHolder;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
@@ -49,7 +50,10 @@ import org.dromara.visor.module.asset.entity.request.exec.*;
import org.dromara.visor.module.asset.entity.vo.ExecJobVO;
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
import org.dromara.visor.module.asset.entity.vo.HostBaseVO;
import org.dromara.visor.module.asset.enums.*;
import org.dromara.visor.module.asset.enums.ExecJobStatusEnum;
import org.dromara.visor.module.asset.enums.ExecModeEnum;
import org.dromara.visor.module.asset.enums.ExecSourceEnum;
import org.dromara.visor.module.asset.enums.HostTypeEnum;
import org.dromara.visor.module.asset.handler.host.exec.job.ExecCommandJob;
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
import org.dromara.visor.module.asset.service.ExecCommandService;
@@ -57,6 +61,7 @@ import org.dromara.visor.module.asset.service.ExecJobHostService;
import org.dromara.visor.module.asset.service.ExecJobService;
import org.dromara.visor.module.infra.api.SystemUserApi;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
@@ -107,7 +112,6 @@ public class ExecJobServiceImpl implements ExecJobService {
LoginUser loginUser = SecurityUtils.getLoginUser();
// 验证表达式是否正确
Cron.of(request.getExpression());
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
// 转换
ExecJobDO record = ExecJobConvert.MAPPER.to(request);
// 查询数据是否冲突
@@ -137,7 +141,6 @@ public class ExecJobServiceImpl implements ExecJobService {
log.info("ExecJobService-updateExecJobById id: {}, request: {}", id, JSON.toJSONString(request));
// 验证表达式是否正确
Cron.of(request.getExpression());
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
// 查询
ExecJobDO record = execJobDAO.selectById(id);
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
@@ -164,7 +167,6 @@ public class ExecJobServiceImpl implements ExecJobService {
public Integer updateExecJobStatus(ExecJobUpdateStatusRequest request) {
Long id = request.getId();
ExecJobStatusEnum status = ExecJobStatusEnum.of(request.getStatus());
Valid.notNull(status, ErrorMessage.PARAM_ERROR);
log.info("ExecJobService-updateExecJobStatus id: {}, status: {}", id, status);
// 查询任务
ExecJobDO record = execJobDAO.selectById(id);
@@ -203,7 +205,7 @@ public class ExecJobServiceImpl implements ExecJobService {
// 设置日志参数
OperatorLogs.add(OperatorLogs.NAME, job.getName());
OperatorLogs.add(OperatorLogs.USERNAME, username);
log.info("ExecJobService-setExecJobExecUser effect: {}", effect);
log.info("ExecJobService-updateExecJobExecUser effect: {}", effect);
return effect;
}
@@ -320,7 +322,6 @@ public class ExecJobServiceImpl implements ExecJobService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public void manualTriggerExecJob(Long id) {
log.info("ExecJobService.manualTriggerExecJob start id: {}", id);
// 查询任务
@@ -336,12 +337,12 @@ public class ExecJobServiceImpl implements ExecJobService {
request.setUserId(user.getId());
request.setUsername(user.getUsername());
}
// 触发任务
this.triggerExecJob(request, job);
// 上下文触发任务
SpringHolder.getBean(ExecJobService.class).triggerExecJob(request, job);
}
@Override
@Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
public void triggerExecJob(ExecJobTriggerRequest request, ExecJobDO job) {
Long id = request.getId();
// 查询任务主机

View File

@@ -328,7 +328,7 @@ public class ExecLogServiceImpl implements ExecLogService {
log.info("ExecLogService.interruptHostExec interrupt logId: {}, hostLogId: {}", logId, hostLogId);
IExecCommandHandler handler = task.getHandlers()
.stream()
.filter(s -> s.getHostId().equals(hostLog.getHostId()))
.filter(s -> hostLogId.equals(s.getId()))
.findFirst()
.orElse(null);
// 中断

View File

@@ -234,6 +234,7 @@ public class TerminalServiceImpl implements TerminalService {
TerminalConnectDTO conn = new TerminalConnectDTO();
conn.setHostId(host.getId());
conn.setHostName(host.getName());
conn.setHostCode(host.getCode());
conn.setHostAddress(host.getAddress());
conn.setHostPort(host.getPort());
conn.setOsType(host.getOsType());

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2023 - present Dromara, All rights reserved.
*
* https://visor.dromara.org
* https://visor.dromara.org.cn
* https://visor.orionsec.cn
*
* Members:
* Jiahang Li - ljh1553488six@139.com - author
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.visor.module.asset.utils;
import cn.orionsec.kit.lang.function.Functions;
import cn.orionsec.kit.lang.utils.Strings;
import cn.orionsec.kit.lang.utils.collect.Lists;
import cn.orionsec.kit.lang.utils.collect.Maps;
import cn.orionsec.kit.lang.utils.json.matcher.NoMatchStrategy;
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatter;
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatters;
import com.alibaba.fastjson.JSON;
import org.dromara.visor.common.constant.Const;
import org.dromara.visor.module.asset.entity.dto.ExecParameterSchemaDTO;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 执行工具类
*
* @author Jiahang Li
* @version 1.0.0
* @since 2025/1/16 17:28
*/
public class ExecUtils {
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
.noMatchStrategy(NoMatchStrategy.KEEP);
private ExecUtils() {
}
/**
* 替换命令
*
* @param command command
* @param params params
* @return command
*/
public static String format(String command, Map<String, Object> params) {
return Strings.replaceCRLF(FORMATTER.format(command, params));
}
/**
* 提取参数
*
* @param parameterSchema parameterSchema
* @return params
*/
public static Map<String, Object> extraSchemaParams(String parameterSchema) {
List<ExecParameterSchemaDTO> schemaList = JSON.parseArray(parameterSchema, ExecParameterSchemaDTO.class);
if (Lists.isEmpty(schemaList)) {
return Maps.newMap();
}
// 解析参数
return schemaList.stream()
.collect(Collectors.toMap(ExecParameterSchemaDTO::getName,
s -> {
Object value = s.getValue();
if (value == null) {
value = Const.EMPTY;
}
return value;
},
Functions.right()));
}
}