diff --git a/orion-visor-common/src/main/java/org/dromara/visor/common/constant/ConfigKeys.java b/orion-visor-common/src/main/java/org/dromara/visor/common/constant/ConfigKeys.java index 3ef9c0ad..b34b659d 100644 --- a/orion-visor-common/src/main/java/org/dromara/visor/common/constant/ConfigKeys.java +++ b/orion-visor-common/src/main/java/org/dromara/visor/common/constant/ConfigKeys.java @@ -31,14 +31,119 @@ package org.dromara.visor.common.constant; */ public interface ConfigKeys { + /** + * SFTP 文件预览大小 + */ + String SFTP_PREVIEW_SIZE = "sftp_previewSize"; + + /** + * SFTP 重复文件备份 + */ + String SFTP_UPLOAD_PRESENT_BACKUP = "sftp_uploadPresentBackup"; + + /** + * SFTP 备份文件名称 + */ + String SFTP_UPLOAD_BACKUP_FILE_NAME = "sftp_uploadBackupFileName"; + /** * 加密公钥 */ - String ENCRYPT_PUBLIC_KEY = "encrypt.publicKey"; + String ENCRYPT_PUBLIC_KEY = "encrypt_publicKey"; /** * 加密私钥 */ - String ENCRYPT_PRIVATE_KEY = "encrypt.privateKey"; + String ENCRYPT_PRIVATE_KEY = "encrypt_privateKey"; + + /** + * 日志前端显示行数 + */ + String LOG_WEB_SCROLL_LINES = "log_webScrollLines"; + + /** + * 日志加载偏移行 + */ + String LOG_TRACKER_LOAD_LINES = "log_trackerLoadLines"; + + /** + * 日志加载间隔毫秒 + */ + String LOG_TRACKER_LOAD_INTERVAL = "log_trackerLoadInterval"; + + /** + * 是否生成详细的执行日志 + */ + String LOG_EXEC_DETAIL_LOG = "log_execDetailLog"; + + /** + * 凭证有效期分 + */ + String LOGIN_LOGIN_SESSION_TIME = "login_loginSessionTime"; + + /** + * 允许多端登录 + */ + String LOGIN_ALLOW_MULTI_DEVICE = "login_allowMultiDevice"; + + /** + * 允许凭证续签 + */ + String LOGIN_ALLOW_REFRESH = "login_allowRefresh"; + + /** + * 凭证续签最大次数 + */ + String LOGIN_MAX_REFRESH_COUNT = "login_maxRefreshCount"; + + /** + * 凭证续签间隔分 + */ + String LOGIN_REFRESH_INTERVAL = "login_refreshInterval"; + + /** + * 登录失败锁定 + */ + String LOGIN_LOGIN_FAILED_LOCK = "login_loginFailedLock"; + + /** + * 登录失败锁定阈值分 + */ + String LOGIN_LOGIN_FAILED_LOCK_THRESHOLD = "login_loginFailedLockThreshold"; + + /** + * 登录失败锁定时间分 + */ + String LOGIN_LOGIN_FAILED_LOCK_TIME = "login_loginFailedLockTime"; + + /** + * 登录失败发信 + */ + String LOGIN_LOGIN_FAILED_SEND = "login_loginFailedSend"; + + /** + * 登录失败发信阈值 + */ + String LOGIN_LOGIN_FAILED_SEND_THRESHOLD = "login_loginFailedSendThreshold"; + + /** + * 是否开启自动清理命令记录 + */ + String AUTO_CLEAR_EXEC_LOG_ENABLED = "autoClear_execLogEnabled"; + + /** + * 自动清理命令记录保留天数 + */ + String AUTO_CLEAR_EXEC_LOG_KEEP_DAYS = "autoClear_execLogKeepDays"; + + /** + * 是否开启自动清理终端连接记录 + */ + String AUTO_CLEAR_TERMINAL_LOG_ENABLED = "autoClear_terminalLogEnabled"; + + /** + * 自动清理终端连接记录保留天数 + */ + String AUTO_CLEAR_TERMINAL_LOG_KEEP_DAYS = "autoClear_terminalLogKeepDays"; } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppAutoClearConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppAutoClearConfig.java new file mode 100644 index 00000000..62242a1d --- /dev/null +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppAutoClearConfig.java @@ -0,0 +1,83 @@ +/* + * 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.define.config; + +import org.dromara.visor.common.config.ConfigRef; +import org.dromara.visor.common.config.ConfigStore; +import org.dromara.visor.common.constant.ConfigKeys; +import org.springframework.stereotype.Component; + +/** + * 自动清理设置 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/6/24 15:01 + */ +@Component +public class AppAutoClearConfig { + + /** + * 是否开启自动清理命令记录 + */ + private final ConfigRef execLogEnabled; + + /** + * 自动清理命令记录保留天数 + */ + private final ConfigRef execLogKeepDays; + + /** + * 是否开启自动清理终端连接记录 + */ + private final ConfigRef terminalLogEnabled; + + /** + * 自动清理终端连接记录保留天数 + */ + private final ConfigRef terminalLogKeepDays; + + public AppAutoClearConfig(ConfigStore configStore) { + this.execLogEnabled = configStore.bool(ConfigKeys.AUTO_CLEAR_EXEC_LOG_ENABLED); + this.execLogKeepDays = configStore.int32(ConfigKeys.AUTO_CLEAR_EXEC_LOG_KEEP_DAYS); + this.terminalLogEnabled = configStore.bool(ConfigKeys.AUTO_CLEAR_TERMINAL_LOG_ENABLED); + this.terminalLogKeepDays = configStore.int32(ConfigKeys.AUTO_CLEAR_TERMINAL_LOG_KEEP_DAYS); + } + + public Boolean getExecLogEnabled() { + return execLogEnabled.value; + } + + public Integer getExecLogKeepDays() { + return execLogKeepDays.value; + } + + public Boolean getTerminalLogEnabled() { + return terminalLogEnabled.value; + } + + public Integer getTerminalLogKeepDays() { + return terminalLogKeepDays.value; + } + +} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppExecLogAutoClearConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppExecLogAutoClearConfig.java deleted file mode 100644 index ad5be0d3..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppExecLogAutoClearConfig.java +++ /dev/null @@ -1,44 +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.define.config; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.dromara.visor.common.entity.AutoClearConfig; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 批量执行日志自动清理配置 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/6/24 15:01 - */ -@Data -@Component -@EqualsAndHashCode(callSuper = true) -@ConfigurationProperties(prefix = "app.auto-clear.exec-log") -public class AppExecLogAutoClearConfig extends AutoClearConfig { - -} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppExecLogConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppExecLogConfig.java deleted file mode 100644 index 1bf9075a..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppExecLogConfig.java +++ /dev/null @@ -1,50 +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.define.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 应用执行日志配置 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/4/25 13:36 - */ -@Data -@Component -@ConfigurationProperties(prefix = "app.exec-log") -public class AppExecLogConfig { - - /** - * 是否拼接 ansi 执行状态日志 - */ - private Boolean appendAnsi; - - public AppExecLogConfig() { - this.appendAnsi = true; - } - -} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppLogConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppLogConfig.java new file mode 100644 index 00000000..cb0437f0 --- /dev/null +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppLogConfig.java @@ -0,0 +1,73 @@ +/* + * 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.define.config; + +import org.dromara.visor.common.config.ConfigRef; +import org.dromara.visor.common.config.ConfigStore; +import org.dromara.visor.common.constant.ConfigKeys; +import org.springframework.stereotype.Component; + +/** + * 应用日志设置 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/4/15 22:00 + */ +@Component +public class AppLogConfig { + + /** + * 日志加载偏移行 + */ + private final ConfigRef trackerLoadLines; + + /** + * 日志加载间隔毫秒 + */ + private final ConfigRef trackerLoadInterval; + + /** + * 是否生成详细的执行日志 + */ + private final ConfigRef execDetailLog; + + public AppLogConfig(ConfigStore configStore) { + this.trackerLoadLines = configStore.int32(ConfigKeys.LOG_TRACKER_LOAD_LINES); + this.trackerLoadInterval = configStore.int32(ConfigKeys.LOG_TRACKER_LOAD_INTERVAL); + this.execDetailLog = configStore.bool(ConfigKeys.LOG_EXEC_DETAIL_LOG); + } + + public Integer getTrackerLoadLines() { + return trackerLoadLines.value; + } + + public Integer getTrackerLoadInterval() { + return trackerLoadInterval.value; + } + + public Boolean getExecDetailLog() { + return execDetailLog.value; + } + +} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppSftpConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppSftpConfig.java index d4469e6a..d08bd8fc 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppSftpConfig.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppSftpConfig.java @@ -22,8 +22,9 @@ */ package org.dromara.visor.module.asset.define.config; -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; +import org.dromara.visor.common.config.ConfigRef; +import org.dromara.visor.common.config.ConfigStore; +import org.dromara.visor.common.constant.ConfigKeys; import org.springframework.stereotype.Component; /** @@ -33,24 +34,40 @@ import org.springframework.stereotype.Component; * @version 1.0.0 * @since 2024/4/15 22:00 */ -@Data @Component -@ConfigurationProperties(prefix = "app.sftp") public class AppSftpConfig { /** - * 上传文件时 文件存在是否备份 + * 文件预览大小 */ - private Boolean uploadPresentBackup; + private final ConfigRef previewSize; + + /** + * 重复文件备份 + */ + private final ConfigRef uploadPresentBackup; /** * 备份文件名称 */ - private String backupFileName; + private final ConfigRef uploadBackupFileName; - public AppSftpConfig() { - this.uploadPresentBackup = true; - this.backupFileName = "bk_${fileName}_${timestamp}"; + public AppSftpConfig(ConfigStore configStore) { + this.previewSize = configStore.int32(ConfigKeys.SFTP_PREVIEW_SIZE); + this.uploadPresentBackup = configStore.bool(ConfigKeys.SFTP_UPLOAD_PRESENT_BACKUP); + this.uploadBackupFileName = configStore.string(ConfigKeys.SFTP_UPLOAD_BACKUP_FILE_NAME); + } + + public Integer getPreviewSize() { + return previewSize.value; + } + + public Boolean getUploadPresentBackup() { + return uploadPresentBackup.value; + } + + public String getUploadBackupFileName() { + return uploadBackupFileName.value; } } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppTerminalConnectLogAutoClearConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppTerminalConnectLogAutoClearConfig.java deleted file mode 100644 index 7c9930e7..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppTerminalConnectLogAutoClearConfig.java +++ /dev/null @@ -1,44 +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.define.config; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.dromara.visor.common.entity.AutoClearConfig; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 终端连接日志自动清理配置 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/6/24 15:01 - */ -@Data -@Component -@EqualsAndHashCode(callSuper = true) -@ConfigurationProperties(prefix = "app.auto-clear.terminal-connect-log") -public class AppTerminalConnectLogAutoClearConfig extends AutoClearConfig { - -} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppTrackerConfig.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppTrackerConfig.java deleted file mode 100644 index 4bc27d17..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/define/config/AppTrackerConfig.java +++ /dev/null @@ -1,61 +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.define.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 应用 tracker 配置 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/4/15 22:00 - */ -@Data -@Component -@ConfigurationProperties(prefix = "app.tracker") -public class AppTrackerConfig { - - /** - * 加载偏移量 (行) - */ - private Integer offset; - - /** - * 延迟时间 (ms) - */ - private Integer delay; - - /** - * 文件未找到等待次数 - */ - private Integer waitTimes; - - public AppTrackerConfig() { - this.offset = 300; - this.delay = 100; - this.waitTimes = 100; - } -} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/ExecTaskExecutors.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/ExecTaskExecutors.java index aa6e539e..ce7754e6 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/ExecTaskExecutors.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/ExecTaskExecutors.java @@ -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 execHostIdList) { + AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(id, execHostIdList)); } } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/BaseExecCommandHandler.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/BaseExecCommandHandler.java index b6ae38168..79c5d078 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/BaseExecCommandHandler.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/BaseExecCommandHandler.java @@ -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 builtParams; - private final TimeoutChecker timeoutChecker; + protected final TimeoutChecker 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 builtParams, TimeoutChecker 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 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 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 getCommandParams() { + String uuid = UUIds.random(); + Map 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); } } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandAnsiHandler.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandDetailHandler.java similarity index 82% rename from orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandAnsiHandler.java rename to orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandDetailHandler.java index 8fc2ab57..c3daff88 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandAnsiHandler.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandDetailHandler.java @@ -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 timeoutChecker) { - super(execCommand, execHostCommand, timeoutChecker); + public ExecCommandDetailHandler(Long id, + ExecLogDO execLog, + Map builtParams, + TimeoutChecker 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()) diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandOriginHandler.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandOriginHandler.java index dc4e5ddc..9d9548c8 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandOriginHandler.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecCommandOriginHandler.java @@ -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 timeoutChecker) { - super(execCommand, execHostCommand, timeoutChecker); + public ExecCommandOriginHandler(Long id, + ExecLogDO execLog, + Map builtParams, + TimeoutChecker timeoutChecker) { + super(id, execLog, builtParams, timeoutChecker); } } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java index b82efddd..358baa92 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/ExecTaskHandler.java @@ -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 execHostIdList; + + private ExecLogDO execLog; + + private Map builtParams; private TimeoutChecker 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 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 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 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 getBaseBuiltinParams() { + String uuid = UUIds.random(); + Date date = new Date(); + // 输入参数 + Map 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; + } + } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/IExecCommandHandler.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/IExecCommandHandler.java index 46f535a0..0acfcdae 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/IExecCommandHandler.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/handler/IExecCommandHandler.java @@ -61,10 +61,10 @@ public interface IExecCommandHandler extends Runnable, SafeCloseable { Integer getExitCode(); /** - * 获取主机 id + * 获取任务 id * * @return hostId */ - Long getHostId(); + Long getId(); } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/model/ExecCommandDTO.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/model/ExecCommandDTO.java deleted file mode 100644 index ac7f321c..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/model/ExecCommandDTO.java +++ /dev/null @@ -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 hosts; - -} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/model/ExecCommandHostDTO.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/model/ExecCommandHostDTO.java deleted file mode 100644 index ad329882..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/command/model/ExecCommandHostDTO.java +++ /dev/null @@ -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; - -} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/manager/ExecLogManager.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/manager/ExecLogManager.java index 68de7382..6c64f821 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/manager/ExecLogManager.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/manager/ExecLogManager.java @@ -82,6 +82,9 @@ public class ExecLogManager { * @param path path */ public void asyncCloseTailFile(String path) { + if (path == null) { + return; + } Threads.start(() -> { try { // 获取当前路径的全部追踪器 diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java index 8f2c6719..eee1e746 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/handler/host/exec/log/tracker/ExecLogTracker.java @@ -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) { diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/ExecCommandService.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/ExecCommandService.java index 675b9570..6b13bbce 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/ExecCommandService.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/ExecCommandService.java @@ -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); + } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecCommandServiceImpl.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecCommandServiceImpl.java index 928eef0f..705b00eb 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecCommandServiceImpl.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecCommandServiceImpl.java @@ -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 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 hostLogs = execHostLogDAO.selectByLogId(logId); + Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT); + List 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 hostIdList = request.getHostIdList(); // 查询主机信息 List hosts = hostDAO.selectBatchIds(hostIdList); - // 查询主机配置 - // TODO 待优化 - Map 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 builtinParams = this.getBaseBuiltinParams(execId, request); // 设置主机日志 List 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 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 hostLogs = execHostLogDAO.selectByLogId(logId); - Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT); - List 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 execHostLogs, - Map hostConfigMap) { - // 执行主机 - List 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 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 getBaseBuiltinParams(Long execId, ExecCommandExecDTO request) { - String uuid = UUIds.random(); - Date date = new Date(); - // 输入参数 - Map 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 getHostParams(Map baseParams, - HostDO host, - HostSshConfigModel config, - String scriptPath) { - String uuid = UUIds.random(); - Map 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 extraSchemaParams(String parameterSchema) { - List 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); - } - } diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecHostLogServiceImpl.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecHostLogServiceImpl.java index 34285fad..43cf8018 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecHostLogServiceImpl.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecHostLogServiceImpl.java @@ -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); // 删除 diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecJobServiceImpl.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecJobServiceImpl.java index 779ad68a..fe0a56be 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecJobServiceImpl.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecJobServiceImpl.java @@ -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(); // 查询任务主机 diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecLogServiceImpl.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecLogServiceImpl.java index 59d2ef46..098cb36c 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecLogServiceImpl.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/ExecLogServiceImpl.java @@ -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); // 中断 diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/TerminalServiceImpl.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/TerminalServiceImpl.java index 3bd170d0..426e1a54 100644 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/TerminalServiceImpl.java +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/service/impl/TerminalServiceImpl.java @@ -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()); diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/utils/ExecUtils.java b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/utils/ExecUtils.java new file mode 100644 index 00000000..4f43b3da --- /dev/null +++ b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/java/org/dromara/visor/module/asset/utils/ExecUtils.java @@ -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 params) { + return Strings.replaceCRLF(FORMATTER.format(command, params)); + } + + /** + * 提取参数 + * + * @param parameterSchema parameterSchema + * @return params + */ + public static Map extraSchemaParams(String parameterSchema) { + List 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())); + } + +} diff --git a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 8db87d6b..00000000 --- a/orion-visor-modules/orion-visor-module-asset/orion-visor-module-asset-service/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "groups": [ - { - "name": "app.tracker", - "type": "org.dromara.visor.module.asset.define.config.AppTrackerConfig", - "sourceType": "org.dromara.visor.module.asset.define.config.AppTrackerConfig" - }, - { - "name": "app.sftp", - "type": "org.dromara.visor.module.asset.define.config.AppSftpConfig", - "sourceType": "org.dromara.visor.module.asset.define.config.AppSftpConfig" - }, - { - "name": "app.exec-log", - "type": "org.dromara.visor.module.asset.define.config.AppExecLogConfig", - "sourceType": "org.dromara.visor.module.asset.define.config.AppExecLogConfig" - }, - { - "name": "app.auto-clear.exec-log", - "type": "org.dromara.visor.module.asset.define.config.AppExecLogAutoClearConfig", - "sourceType": "org.dromara.visor.module.asset.define.config.AppExecLogAutoClearConfig" - }, - { - "name": "app.auto-clear.terminal-connect-log", - "type": "org.dromara.visor.module.asset.define.config.AppTerminalConnectLogAutoClearConfig", - "sourceType": "org.dromara.visor.module.asset.define.config.AppTerminalConnectLogAutoClearConfig" - } - ], - "properties": [ - { - "name": "app.tracker.offset", - "type": "java.lang.Integer", - "description": "加载偏移量 (行).", - "defaultValue": "300" - }, - { - "name": "app.tracker.delay", - "type": "java.lang.Integer", - "description": "延迟时间 (ms).", - "defaultValue": "100" - }, - { - "name": "app.tracker.wait-times", - "type": "java.lang.Integer", - "description": "文件未找到等待次数.", - "defaultValue": "100" - }, - { - "name": "app.sftp.upload-present-backup", - "type": "java.lang.Boolean", - "description": "上传文件时 文件存在是否备份.", - "defaultValue": "true" - }, - { - "name": "app.sftp.backup-file-name", - "type": "java.lang.String", - "description": "备份文件名称.", - "defaultValue": "bk_${fileName}_${timestamp}" - }, - { - "name": "app.exec-log.append-ansi", - "type": "java.lang.Boolean", - "description": "是否拼接 ansi 执行状态日志.", - "defaultValue": "true" - }, - { - "name": "app.auto-clear.exec-log.enabled", - "type": "java.lang.Boolean", - "description": "开启 批量执行日志自动清理." - }, - { - "name": "app.auto-clear.exec-log.keep-period", - "type": "java.lang.Integer", - "description": "批量执行日志自动清理 保留周期 (天)." - }, - { - "name": "app.auto-clear.terminal-connect-log.enabled", - "type": "java.lang.Boolean", - "description": "开启 终端连接日志自动清理." - }, - { - "name": "app.auto-clear.terminal-connect-log.keep-period", - "type": "java.lang.Integer", - "description": "终端连接日志自动清理 保留周期 (天)." - } - ] -} \ No newline at end of file diff --git a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/define/config/AppAuthenticationConfig.java b/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/define/config/AppAuthenticationConfig.java deleted file mode 100644 index e4de733f..00000000 --- a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/define/config/AppAuthenticationConfig.java +++ /dev/null @@ -1,71 +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.infra.define.config; - -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -/** - * 应用认证配置 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/3/5 18:26 - */ -@Data -@Component -@ConfigurationProperties("app.authentication") -public class AppAuthenticationConfig { - - /** - * 是否允许多端登录 - */ - private Boolean allowMultiDevice; - - /** - * 是否允许凭证续签 - */ - private Boolean allowRefresh; - - /** - * 凭证续签最大次数 - */ - private Integer maxRefreshCount; - - /** - * 登录失败发送站内信阈值 - */ - private Integer loginFailedSendThreshold; - - /** - * 登录失败锁定次数 - */ - private Integer loginFailedLockCount; - - /** - * 登录失败锁定时间 (分) - */ - private Integer loginFailedLockTime; - -} diff --git a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/define/config/AppLoginConfig.java b/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/define/config/AppLoginConfig.java new file mode 100644 index 00000000..153eec0c --- /dev/null +++ b/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/define/config/AppLoginConfig.java @@ -0,0 +1,143 @@ +/* + * 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.infra.define.config; + +import org.dromara.visor.common.config.ConfigRef; +import org.dromara.visor.common.config.ConfigStore; +import org.dromara.visor.common.constant.ConfigKeys; +import org.springframework.stereotype.Component; + +/** + * 应用登录配置 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2024/3/5 18:26 + */ +@Component +public class AppLoginConfig { + + /** + * 凭证有效期(分) + */ + private final ConfigRef loginSessionTime; + + /** + * 是否允许多端登录 + */ + private final ConfigRef allowMultiDevice; + + /** + * 是否允许凭证续签 + */ + private final ConfigRef allowRefresh; + + /** + * 凭证续签最大次数 + */ + private final ConfigRef maxRefreshCount; + + /** + * 凭证续签间隔分 + */ + private final ConfigRef refreshInterval; + + /** + * 登录失败是否锁定 + */ + private final ConfigRef loginFailedLock; + + /** + * 登录失败锁定阈值 + */ + private final ConfigRef loginFailedLockThreshold; + + /** + * 登录失败锁定时间 (分) + */ + private final ConfigRef loginFailedLockTime; + + /** + * 登录失败发信 + */ + private final ConfigRef loginFailedSend; + + /** + * 登录失败发送站内信阈值 + */ + private final ConfigRef loginFailedSendThreshold; + + public AppLoginConfig(ConfigStore configStore) { + this.loginSessionTime = configStore.int32(ConfigKeys.LOGIN_LOGIN_SESSION_TIME); + this.allowMultiDevice = configStore.bool(ConfigKeys.LOGIN_ALLOW_MULTI_DEVICE); + this.allowRefresh = configStore.bool(ConfigKeys.LOGIN_ALLOW_REFRESH); + this.maxRefreshCount = configStore.int32(ConfigKeys.LOGIN_MAX_REFRESH_COUNT); + this.refreshInterval = configStore.int32(ConfigKeys.LOGIN_REFRESH_INTERVAL); + this.loginFailedLock = configStore.bool(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK); + this.loginFailedLockThreshold = configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK_THRESHOLD); + this.loginFailedLockTime = configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_LOCK_TIME); + this.loginFailedSend = configStore.bool(ConfigKeys.LOGIN_LOGIN_FAILED_SEND); + this.loginFailedSendThreshold = configStore.int32(ConfigKeys.LOGIN_LOGIN_FAILED_SEND_THRESHOLD); + } + + public Integer getLoginSessionTime() { + return loginSessionTime.value; + } + + public Boolean getAllowMultiDevice() { + return allowMultiDevice.value; + } + + public Boolean getAllowRefresh() { + return allowRefresh.value; + } + + public Integer getMaxRefreshCount() { + return maxRefreshCount.value; + } + + public Integer getRefreshInterval() { + return refreshInterval.value; + } + + public Boolean getLoginFailedLock() { + return loginFailedLock.value; + } + + public Integer getLoginFailedLockThreshold() { + return loginFailedLockThreshold.value; + } + + public Integer getLoginFailedLockTime() { + return loginFailedLockTime.value; + } + + public Boolean getLoginFailedSend() { + return loginFailedSend.value; + } + + public Integer getLoginFailedSendThreshold() { + return loginFailedSendThreshold.value; + } + +} diff --git a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/handler/setting/model/SftpSystemSettingModel.java b/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/handler/setting/model/SftpSystemSettingModel.java deleted file mode 100644 index aae87049..00000000 --- a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/java/org/dromara/visor/module/infra/handler/setting/model/SftpSystemSettingModel.java +++ /dev/null @@ -1,48 +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.infra.handler.setting.model; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * SFTP 系统设置模型 - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2024/10/9 11:45 - */ -@Data -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class SftpSystemSettingModel { - - /** - * 预览大小 - */ - private Integer previewSize; - -} diff --git a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/resources/META-INF/additional-spring-configuration-metadata.json deleted file mode 100644 index 4b489df4..00000000 --- a/orion-visor-modules/orion-visor-module-infra/orion-visor-module-infra-service/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "groups": [ - { - "name": "app.authentication", - "type": "org.dromara.visor.module.infra.define.config.AppAuthenticationConfig", - "sourceType": "org.dromara.visor.module.infra.define.config.AppAuthenticationConfig" - } - ], - "properties": [ - { - "name": "app.authentication.allowMultiDevice", - "type": "java.lang.Boolean", - "description": "是否允许多端登录." - }, - { - "name": "app.authentication.allowRefresh", - "type": "java.lang.Boolean", - "description": "是否允许凭证续签." - }, - { - "name": "app.authentication.maxRefreshCount", - "type": "java.lang.Integer", - "description": "凭证续签最大次数." - }, - { - "name": "app.authentication.loginFailedSendThreshold", - "type": "java.lang.Integer", - "description": "登录失败发送站内信阈值." - }, - { - "name": "app.authentication.loginFailedLockCount", - "type": "java.lang.Integer", - "description": "登录失败锁定次数." - }, - { - "name": "app.authentication.loginFailedLockTime", - "type": "java.lang.Integer", - "description": "登录失败锁定时间 (分)." - } - ] -} \ No newline at end of file diff --git a/orion-visor-ui/src/api/system/setting.ts b/orion-visor-ui/src/api/system/setting.ts index 63056297..ca55626a 100644 --- a/orion-visor-ui/src/api/system/setting.ts +++ b/orion-visor-ui/src/api/system/setting.ts @@ -1,35 +1,15 @@ import axios from 'axios'; import { dateFormat } from '@/utils'; -/** - * 系统设置类型 - */ -export type SystemSettingType = 'SFTP' | 'ENCRYPT'; - /** * 系统设置更新请求 */ export interface SystemSettingUpdateRequest { - type?: SystemSettingType; - item?: string; - value?: any; + type?: string; + value?: string; settings?: Record; } -/** - * 应用信息查询响应 - */ -export interface SystemLicenseResponse { - userCount: number; - hostCount: number; - release: string; - releaseName: string; - issueDate: number; - expireDate: number; - expireDay: number; - uuid: string; -} - /** * 应用信息查询响应 */ @@ -47,28 +27,73 @@ export interface AppReleaseResponse { } /** - * 系统聚合设置响应 + * RSA 密钥对响应 */ -export interface SystemSettingAggregateResponse { - sftp: SftpSetting; - encrypt: EncryptSetting; -} - -/** - * SFTP 配置 - */ -export interface SftpSetting { - previewSize: number; -} - -/** - * 加密配置 - */ -export interface EncryptSetting { +export interface RsaKeyPairResponse { publicKey: string; privateKey: string; } +/** + * 系统设置 + */ +export type SystemSetting = SftpSetting + & LoginSetting & EncryptSetting + & LogSetting & AutoClearSetting; + +/** + * SFTP 设置 + */ +export interface SftpSetting { + sftp_previewSize: number; + sftp_uploadPresentBackup: string; + sftp_uploadBackupFileName: string; +} + +/** + * 登录设置 + */ +export interface LoginSetting { + login_allowMultiDevice: string; + login_allowRefresh: string; + login_maxRefreshCount: number; + login_refreshInterval: number; + login_loginFailedLock: string; + login_loginFailedLockThreshold: number; + login_loginFailedLockTime: number; + login_loginFailedSend: string; + login_loginFailedSendThreshold: number; + login_loginSessionTime: number; +} + +/** + * 加密设置 + */ +export interface EncryptSetting { + encrypt_publicKey: string; + encrypt_privateKey: string; +} + +/** + * 日志设置 + */ +export interface LogSetting { + log_webScrollLines: number; + log_trackerLoadLines: number; + log_trackerLoadInterval: number; + log_execDetailLog: string; +} + +/** + * 自动清理设置 + */ +export interface AutoClearSetting { + autoClear_execLogEnabled: string; + autoClear_execLogKeepDays: number; + autoClear_terminalLogEnabled: string; + autoClear_terminalLogKeepDays: number; +} + /** * 查询应用信息 */ @@ -80,7 +105,7 @@ export function getSystemAppInfo() { * 获取系统聚合设置 */ export function getSystemAggregateSetting() { - return axios.get('/infra/system-setting/setting'); + return axios.get>('/infra/system-setting/setting'); } /** @@ -101,26 +126,26 @@ export function getAppLatestRelease() { * 生成密钥对 */ export function generatorKeypair() { - return axios.get('/infra/system-setting/generator-keypair'); + return axios.get('/infra/system-setting/generator-keypair'); } /** * 更新系统设置-单个 */ export function updateSystemSetting(request: SystemSettingUpdateRequest) { - return axios.put('/infra/system-setting/update', request); + return axios.put('/infra/system-setting/update', request); } /** * 更新系统设置-多个 */ export function updateSystemSettingBatch(request: SystemSettingUpdateRequest) { - return axios.put('/infra/system-setting/update-batch', request); + return axios.put('/infra/system-setting/update-batch', request); } /** * 查询系统设置 */ -export function getSystemSetting(type: SystemSettingType) { - return axios.get('/infra/system-setting/get', { params: { type } }); +export function getSystemSetting(type: string) { + return axios.get>('/infra/system-setting/get', { params: { type } }); } diff --git a/orion-visor-ui/src/utils/index.ts b/orion-visor-ui/src/utils/index.ts index a8efff50..2d101056 100644 --- a/orion-visor-ui/src/utils/index.ts +++ b/orion-visor-ui/src/utils/index.ts @@ -129,6 +129,21 @@ export function formatDuration(start: number, end?: number): string { return result; } +/** + * 转为匿名数字 number | undefined + */ +export function toAnonymousNumber(value: string | undefined): number { + if (value === undefined || value === null) { + return value as unknown as number; + } + const num = Number.parseInt(value); + if (Number.isNaN(num)) { + return undefined as unknown as number; + } else { + return num; + } +} + /** * 格式化数字为 ,分割 */ diff --git a/orion-visor-ui/src/utils/rsa.ts b/orion-visor-ui/src/utils/rsa.ts index b741990f..a0185ab3 100644 --- a/orion-visor-ui/src/utils/rsa.ts +++ b/orion-visor-ui/src/utils/rsa.ts @@ -12,7 +12,7 @@ export const encrypt = async (data: string | undefined): Promise + + +

自动清理设置

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 保存 + + + +
+ + + + + + + diff --git a/orion-visor-ui/src/views/system/setting/components/encrypt-setting.vue b/orion-visor-ui/src/views/system/setting/components/encrypt-setting.vue index 5648e97b..4c214b39 100644 --- a/orion-visor-ui/src/views/system/setting/components/encrypt-setting.vue +++ b/orion-visor-ui/src/views/system/setting/components/encrypt-setting.vue @@ -13,22 +13,22 @@ 请输入 PKCS8 格式的 RSA Base64 密钥, 用于前后端传输时的数据加密 - - - - { setLoading(true); try { - const { data } = await getSystemSetting(SystemSettingTypes.ENCRYPT); - setting.value = data; + const { data } = await getSystemSetting(SystemSettingTypes.ENCRYPT); + setting.value = { + ...data + }; } catch (e) { } finally { setLoading(false); diff --git a/orion-visor-ui/src/views/system/setting/components/log-setting.vue b/orion-visor-ui/src/views/system/setting/components/log-setting.vue new file mode 100644 index 00000000..b24bce73 --- /dev/null +++ b/orion-visor-ui/src/views/system/setting/components/log-setting.vue @@ -0,0 +1,162 @@ + + + + + + + diff --git a/orion-visor-ui/src/views/system/setting/components/login-setting.vue b/orion-visor-ui/src/views/system/setting/components/login-setting.vue new file mode 100644 index 00000000..19ef5ab8 --- /dev/null +++ b/orion-visor-ui/src/views/system/setting/components/login-setting.vue @@ -0,0 +1,258 @@ + + + + + + + diff --git a/orion-visor-ui/src/views/system/setting/components/sftp-setting.vue b/orion-visor-ui/src/views/system/setting/components/sftp-setting.vue index aa5225f8..116d8da6 100644 --- a/orion-visor-ui/src/views/system/setting/components/sftp-setting.vue +++ b/orion-visor-ui/src/views/system/setting/components/sftp-setting.vue @@ -8,12 +8,40 @@ class="setting-form" label-align="right" :auto-label-width="true"> + + + + + + + + + + - - + @@ -50,6 +81,7 @@ import { getSystemSetting, updateSystemSettingBatch } from '@/api/system/setting'; import useLoading from '@/hooks/loading'; import { Message } from '@arco-design/web-vue'; + import { toAnonymousNumber } from '@/utils'; import { SystemSettingTypes } from '../types/const'; const { loading, setLoading } = useLoading(); @@ -68,7 +100,7 @@ try { await updateSystemSettingBatch({ type: SystemSettingTypes.SFTP, - settings: setting.value + settings: setting.value, }); Message.success('修改成功'); } catch (e) { @@ -81,8 +113,11 @@ onMounted(async () => { setLoading(true); try { - const { data } = await getSystemSetting(SystemSettingTypes.SFTP); - setting.value = data; + const { data } = await getSystemSetting(SystemSettingTypes.SFTP); + setting.value = { + ...data, + sftp_previewSize: toAnonymousNumber(data.sftp_previewSize), + }; } catch (e) { } finally { setLoading(false); @@ -93,6 +128,6 @@ diff --git a/orion-visor-ui/src/views/system/setting/index.vue b/orion-visor-ui/src/views/system/setting/index.vue index ee6359dc..77421866 100644 --- a/orion-visor-ui/src/views/system/setting/index.vue +++ b/orion-visor-ui/src/views/system/setting/index.vue @@ -11,12 +11,30 @@ title="SFTP"> + + + + + + + + + + + + @@ -36,7 +54,10 @@ import { useRoute } from 'vue-router'; import usePermission from '@/hooks/permission'; import SftpSetting from './components/sftp-setting.vue'; + import LoginSetting from './components/login-setting.vue'; import EncryptSetting from './components/encrypt-setting.vue'; + import LogSetting from './components/log-setting.vue'; + import AutoClearSetting from './components/auto-clear-setting.vue'; import AboutSetting from './components/about-setting.vue'; const route = useRoute(); @@ -96,6 +117,7 @@ margin-top: 0; margin-bottom: 20px; font-weight: 600; + user-select: none; } .setting-form { @@ -104,6 +126,7 @@ .arco-descriptions-title { font-weight: 600; + user-select: none; } .alert-href { diff --git a/orion-visor-ui/src/views/system/setting/types/const.ts b/orion-visor-ui/src/views/system/setting/types/const.ts index b04bccdd..5881ef91 100644 --- a/orion-visor-ui/src/views/system/setting/types/const.ts +++ b/orion-visor-ui/src/views/system/setting/types/const.ts @@ -1,7 +1,8 @@ -import type { SystemSettingType } from '@/api/system/setting'; - // 系统设置类型 -export const SystemSettingTypes: Record = { - SFTP: 'SFTP', - ENCRYPT: 'ENCRYPT', +export const SystemSettingTypes = { + SFTP: 'sftp', + ENCRYPT: 'encrypt', + LOGIN: 'login', + LOG: 'log', + AUTO_CLEAR: 'autoClear', };