Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c14055ba8c | ||
|
|
04aa6c9680 | ||
|
|
397bbb2657 | ||
|
|
9a68282127 | ||
|
|
dcd02acc61 | ||
|
|
1025688e9b | ||
|
|
26eeb26a75 | ||
|
|
7f76325284 | ||
|
|
de9a921c49 | ||
|
|
a9ac9d0f79 | ||
|
|
a14b28de6a | ||
|
|
ccd7430b8f | ||
|
|
6791ea5770 | ||
|
|
2d5835b150 | ||
|
|
cec11ce8c3 | ||
|
|
972103841c | ||
|
|
89f6d2cd1c | ||
|
|
d13008ce0c | ||
|
|
abf384dd3c | ||
|
|
0abd4a893b | ||
|
|
d0710fb52b | ||
|
|
81b9bacb96 | ||
|
|
dc42a31711 | ||
|
|
27e3e65ea1 | ||
|
|
a001ab3f16 |
10
README.md
10
README.md
@@ -86,11 +86,11 @@ docker compose up -d
|
||||
|
||||
## 技术栈
|
||||
|
||||
* SpringBoot 2.7.+
|
||||
* Mysql 8.0.+
|
||||
* Redis 6.0.+
|
||||
* Vue3 3.2.+
|
||||
* Arco Design 2.55.+
|
||||
* SpringBoot 2.7+
|
||||
* Mysql 8+
|
||||
* Redis 6+
|
||||
* Vue3 3+
|
||||
* Arco Design 2+
|
||||
|
||||
## 主要功能预览
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.1
|
||||
version=2.3.3
|
||||
docker build -t orion-visor-adminer:${version} .
|
||||
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
|
||||
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.1
|
||||
version=2.3.3
|
||||
cp -r ../../sql ./sql
|
||||
docker build -t orion-visor-mysql:${version} .
|
||||
rm -rf ./sql
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.1
|
||||
version=2.3.3
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
|
||||
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#/bin/bash
|
||||
version=2.3.1
|
||||
version=2.3.3
|
||||
docker build -t orion-visor-redis:${version} .
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
|
||||
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#/bin/bash
|
||||
version=2.3.1
|
||||
version=2.3.3
|
||||
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
|
||||
mv ../../orion-visor-ui/dist ./dist
|
||||
docker build --no-cache -t orion-visor-service:${version} .
|
||||
docker build -t orion-visor-service:${version} .
|
||||
rm -rf ./orion-visor-launch.jar
|
||||
rm -rf ./dist
|
||||
docker tag orion-visor-service:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-service:${version}
|
||||
|
||||
@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
|
||||
/**
|
||||
* 同 ${orion.version} 迭代时候需要手动更改
|
||||
*/
|
||||
String VERSION = "2.3.1";
|
||||
String VERSION = "2.3.3";
|
||||
|
||||
/**
|
||||
* 同 ${spring.application.name}
|
||||
|
||||
@@ -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";
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<url>https://github.com/dromara/orion-visor</url>
|
||||
|
||||
<properties>
|
||||
<revision>2.3.1</revision>
|
||||
<revision>2.3.3</revision>
|
||||
<spring.boot.version>2.7.17</spring.boot.version>
|
||||
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
|
||||
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>
|
||||
|
||||
@@ -157,51 +157,6 @@ logging:
|
||||
level:
|
||||
org.dromara.visor.launch.controller.BootstrapController: INFO
|
||||
|
||||
# 应用配置
|
||||
app:
|
||||
# 认证配置
|
||||
authentication:
|
||||
# 是否允许多端登录
|
||||
allow-multi-device: true
|
||||
# 是否允许凭证续签
|
||||
allow-refresh: true
|
||||
# 凭证续签最大次数
|
||||
max-refresh-count: 3
|
||||
# 登录失败发送站内信阈值
|
||||
login-failed-send-threshold: 3
|
||||
# 登录失败锁定次数
|
||||
login-failed-lock-count: 5
|
||||
# 登录失败锁定时间 (分)
|
||||
login-failed-lock-time: 30
|
||||
# tracker 配置
|
||||
tracker:
|
||||
# 加载偏移量 (行)
|
||||
offset: 300
|
||||
# 延迟时间 (ms)
|
||||
delay: 100
|
||||
# 文件未找到等待次数
|
||||
wait-times: 100
|
||||
# sftp 配置
|
||||
sftp:
|
||||
# 上传文件时 文件存在是否备份
|
||||
upload-present-backup: true
|
||||
# 备份文件名称
|
||||
backup-file-name: bk_${fileName}_${timestamp}
|
||||
# 执行日志
|
||||
exec-log:
|
||||
# 是否拼接 ansi 执行状态日志
|
||||
append-ansi: true
|
||||
# 自动清理配置
|
||||
auto-clear:
|
||||
# 批量执行日志
|
||||
exec-log:
|
||||
enabled: false
|
||||
keep-period: 30
|
||||
# 终端连接日志
|
||||
terminal-connect-log:
|
||||
enabled: false
|
||||
keep-period: 30
|
||||
|
||||
# orion framework config
|
||||
orion:
|
||||
# 版本
|
||||
|
||||
@@ -39,9 +39,9 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class ReplaceVersion {
|
||||
|
||||
private static final String TARGET_VERSION = "2.3.0";
|
||||
private static final String TARGET_VERSION = "2.3.2";
|
||||
|
||||
private static final String REPLACE_VERSION = "2.3.1";
|
||||
private static final String REPLACE_VERSION = "2.3.3";
|
||||
|
||||
private static final String PATH = new File("").getAbsolutePath();
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import org.dromara.visor.module.asset.define.operator.ExecCommandLogOperatorType
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecInterruptRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogClearRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogQueryRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecHostLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogStatusVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
@@ -93,6 +92,14 @@ public class ExecCommandLogController {
|
||||
return execLogService.getExecLog(id, SOURCE);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/get-host")
|
||||
@Operation(summary = "查询执行主机日志")
|
||||
@PreAuthorize("@ss.hasPermission('asset:exec-command-log:query')")
|
||||
public ExecHostLogVO getExecCommandHostLog(@RequestParam("id") Long id) {
|
||||
return execHostLogService.getExecHostLog(id);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/host-list")
|
||||
@Operation(summary = "查询全部执行主机日志")
|
||||
@@ -164,12 +171,12 @@ public class ExecCommandLogController {
|
||||
return execLogService.clearExecLog(request);
|
||||
}
|
||||
|
||||
@PostMapping("/tail")
|
||||
@GetMapping("/tail")
|
||||
@Operation(summary = "查看批量执行日志")
|
||||
@Parameter(name = "id", description = "id", required = true)
|
||||
@PreAuthorize("@ss.hasAnyPermission('asset:exec-command-log:query', 'asset:exec-command:exec')")
|
||||
public String getExecCommandLogTailToken(@Validated @RequestBody ExecLogTailRequest request) {
|
||||
request.setSource(SOURCE);
|
||||
return execLogService.getExecLogTailToken(request);
|
||||
public String getExecCommandLogTailToken(@RequestParam("id") Long id) {
|
||||
return execLogService.getExecLogTailToken(id);
|
||||
}
|
||||
|
||||
@OperatorLog(ExecCommandLogOperatorType.DOWNLOAD)
|
||||
|
||||
@@ -37,7 +37,6 @@ import org.dromara.visor.module.asset.define.operator.ExecJobLogOperatorType;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecInterruptRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogClearRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogQueryRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecHostLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogStatusVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
@@ -92,6 +91,14 @@ public class ExecJobLogController {
|
||||
return execLogService.getExecLog(id, SOURCE);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/get-host")
|
||||
@Operation(summary = "查询执行主机日志")
|
||||
@PreAuthorize("@ss.hasPermission('asset:exec-job-log:query')")
|
||||
public ExecHostLogVO getExecJobHostLog(@RequestParam("id") Long id) {
|
||||
return execHostLogService.getExecHostLog(id);
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/host-list")
|
||||
@Operation(summary = "查询全部执行主机日志")
|
||||
@@ -153,12 +160,12 @@ public class ExecJobLogController {
|
||||
return execLogService.clearExecLog(request);
|
||||
}
|
||||
|
||||
@PostMapping("/tail")
|
||||
@GetMapping("/tail")
|
||||
@Operation(summary = "查看计划任务日志")
|
||||
@Parameter(name = "id", description = "id", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('asset:exec-job-log:query')")
|
||||
public String getExecJobLogTailToken(@Validated @RequestBody ExecLogTailRequest request) {
|
||||
request.setSource(SOURCE);
|
||||
return execLogService.getExecLogTailToken(request);
|
||||
public String getExecJobLogTailToken(@RequestParam("id") Long id) {
|
||||
return execLogService.getExecLogTailToken(id);
|
||||
}
|
||||
|
||||
@OperatorLog(ExecJobLogOperatorType.DOWNLOAD)
|
||||
|
||||
@@ -81,7 +81,7 @@ public class TerminalConnectLogController {
|
||||
}
|
||||
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@PostMapping("/session")
|
||||
@PostMapping("/sessions")
|
||||
@Operation(summary = "查询全部终端连接会话")
|
||||
@PreAuthorize("@ss.hasPermission('asset:terminal-connect-session:management:query')")
|
||||
public List<TerminalConnectLogVO> getTerminalConnectSessions(@Validated @RequestBody TerminalConnectLogQueryRequest request) {
|
||||
|
||||
@@ -52,4 +52,20 @@ public interface ExecHostLogDAO extends IMapper<ExecHostLogDO> {
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 logId 查询
|
||||
*
|
||||
* @param id id
|
||||
* @param logId logId
|
||||
* @return row
|
||||
*/
|
||||
default ExecHostLogDO selectByIdAndLogId(Long id, Long logId) {
|
||||
return this.of()
|
||||
.createWrapper()
|
||||
.eq(ExecHostLogDO::getId, id)
|
||||
.eq(ExecHostLogDO::getLogId, logId)
|
||||
.then()
|
||||
.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Boolean> execLogEnabled;
|
||||
|
||||
/**
|
||||
* 自动清理命令记录保留天数
|
||||
*/
|
||||
private final ConfigRef<Integer> execLogKeepDays;
|
||||
|
||||
/**
|
||||
* 是否开启自动清理终端连接记录
|
||||
*/
|
||||
private final ConfigRef<Boolean> terminalLogEnabled;
|
||||
|
||||
/**
|
||||
* 自动清理终端连接记录保留天数
|
||||
*/
|
||||
private final ConfigRef<Integer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Integer> trackerLoadLines;
|
||||
|
||||
/**
|
||||
* 日志加载间隔毫秒
|
||||
*/
|
||||
private final ConfigRef<Integer> trackerLoadInterval;
|
||||
|
||||
/**
|
||||
* 是否生成详细的执行日志
|
||||
*/
|
||||
private final ConfigRef<Boolean> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Integer> previewSize;
|
||||
|
||||
/**
|
||||
* 重复文件备份
|
||||
*/
|
||||
private final ConfigRef<Boolean> uploadPresentBackup;
|
||||
|
||||
/**
|
||||
* 备份文件名称
|
||||
*/
|
||||
private String backupFileName;
|
||||
private final ConfigRef<String> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -78,9 +78,9 @@ public class TerminalConnectLogDO extends BaseDO {
|
||||
@TableField("type")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "token")
|
||||
@TableField("token")
|
||||
private String token;
|
||||
@Schema(description = "sessionId")
|
||||
@TableField("session_id")
|
||||
private String sessionId;
|
||||
|
||||
@Schema(description = "状态")
|
||||
@TableField("status")
|
||||
|
||||
@@ -1,59 +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.entity.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 执行主机日志查看 缓存对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/18 16:34
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "ExecHostLogTailDTO", description = "执行主机日志查看 缓存对象")
|
||||
public class ExecHostLogTailDTO implements Serializable {
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "hostId")
|
||||
private Long hostId;
|
||||
|
||||
@Schema(description = "文件路径")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "输出编码")
|
||||
private String charset;
|
||||
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 执行日志查看 缓存对象
|
||||
@@ -45,8 +44,8 @@ import java.util.List;
|
||||
@Schema(name = "ExecLogTailDTO", description = "执行日志查看 缓存对象")
|
||||
public class ExecLogTailDTO implements Serializable {
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
@Schema(description = "execId")
|
||||
private Long execId;
|
||||
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
@@ -54,7 +53,4 @@ public class ExecLogTailDTO implements Serializable {
|
||||
@Schema(description = "token")
|
||||
private String token;
|
||||
|
||||
@Schema(description = "执行主机")
|
||||
private List<ExecHostLogTailDTO> hosts;
|
||||
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ public class TerminalConnectDTO {
|
||||
@Schema(description = "hostName")
|
||||
private String hostName;
|
||||
|
||||
@Schema(description = "主机编码")
|
||||
private String hostCode;
|
||||
|
||||
@Schema(description = "主机地址")
|
||||
private String hostAddress;
|
||||
|
||||
|
||||
@@ -1,58 +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.entity.request.exec;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 执行日志查看 请求对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/11 11:46
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "ExecLogTailRequest", description = "执行日志查看 请求对象")
|
||||
public class ExecLogTailRequest {
|
||||
|
||||
@Schema(description = "执行来源")
|
||||
private String source;
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "执行id")
|
||||
private Long execId;
|
||||
|
||||
@Schema(description = "执行主机id")
|
||||
private List<Long> hostExecIdList;
|
||||
|
||||
}
|
||||
@@ -63,9 +63,9 @@ public class TerminalConnectLogCreateRequest {
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Size(max = 128)
|
||||
@Schema(description = "token")
|
||||
private String token;
|
||||
@Size(max = 64)
|
||||
@Schema(description = "sessionId")
|
||||
private String sessionId;
|
||||
|
||||
@Schema(description = "拓展信息")
|
||||
private Map<String, Object> extra;
|
||||
|
||||
@@ -69,9 +69,9 @@ public class TerminalConnectLogQueryRequest extends PageRequest {
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Size(max = 128)
|
||||
@Schema(description = "token")
|
||||
private String token;
|
||||
@Size(max = 64)
|
||||
@Schema(description = "sessionId")
|
||||
private String sessionId;
|
||||
|
||||
@Size(max = 16)
|
||||
@Schema(description = "状态")
|
||||
|
||||
@@ -72,6 +72,9 @@ public class TerminalConnectLogVO implements Serializable {
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "sessionId")
|
||||
private String sessionId;
|
||||
|
||||
@Schema(description = "开始时间")
|
||||
private Date startTime;
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ package org.dromara.visor.module.asset.handler.host.exec.command;
|
||||
|
||||
import org.dromara.visor.module.asset.define.AssetThreadPools;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.handler.ExecTaskHandler;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量执行命令执行器
|
||||
@@ -38,10 +39,11 @@ public class ExecTaskExecutors {
|
||||
/**
|
||||
* 执行命令
|
||||
*
|
||||
* @param command command
|
||||
* @param id id
|
||||
* @param execHostIdList execHostIdList
|
||||
*/
|
||||
public static void start(ExecCommandDTO command) {
|
||||
AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(command));
|
||||
public static void start(Long id, List<Long> execHostIdList) {
|
||||
AssetThreadPools.EXEC_TASK.execute(new ExecTaskHandler(id, execHostIdList));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
import cn.orionsec.kit.lang.exception.AuthenticationException;
|
||||
import cn.orionsec.kit.lang.exception.ConnectionRuntimeException;
|
||||
import cn.orionsec.kit.lang.exception.SftpException;
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.net.host.SessionStore;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
@@ -40,22 +41,29 @@ import com.alibaba.fastjson.JSON;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.FileConst;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.common.enums.EndpointDefine;
|
||||
import org.dromara.visor.common.interfaces.FileClient;
|
||||
import org.dromara.visor.common.utils.PathUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.module.asset.dao.ExecHostLogDAO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.enums.HostOsTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.manager.ExecLogManager;
|
||||
import org.dromara.visor.module.asset.handler.host.jsch.SessionStores;
|
||||
import org.dromara.visor.module.asset.service.TerminalService;
|
||||
import org.dromara.visor.module.asset.utils.ExecUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 命令执行器 基类
|
||||
@@ -75,16 +83,21 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
|
||||
private static final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class);
|
||||
|
||||
protected final ExecCommandDTO execCommand;
|
||||
@Getter
|
||||
protected final Long id;
|
||||
|
||||
protected final ExecCommandHostDTO execHostCommand;
|
||||
protected final Map<String, Object> builtParams;
|
||||
|
||||
private final TimeoutChecker<TimeoutEndpoint> timeoutChecker;
|
||||
protected final TimeoutChecker<TimeoutEndpoint> timeoutChecker;
|
||||
|
||||
protected final ExecLogDO execLog;
|
||||
|
||||
protected ExecHostLogDO execHostLog;
|
||||
|
||||
@Getter
|
||||
protected ExecHostStatusEnum status;
|
||||
|
||||
protected ExecHostLogDO updateRecord;
|
||||
private TerminalConnectDTO connect;
|
||||
|
||||
private OutputStream logOutputStream;
|
||||
|
||||
@@ -99,24 +112,28 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
|
||||
private volatile boolean interrupted;
|
||||
|
||||
public BaseExecCommandHandler(ExecCommandDTO execCommand,
|
||||
ExecCommandHostDTO execHostCommand,
|
||||
public BaseExecCommandHandler(Long id,
|
||||
ExecLogDO execLog,
|
||||
Map<String, Object> builtParams,
|
||||
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
this.status = ExecHostStatusEnum.WAITING;
|
||||
this.execCommand = execCommand;
|
||||
this.execHostCommand = execHostCommand;
|
||||
this.id = id;
|
||||
this.execLog = execLog;
|
||||
this.builtParams = builtParams;
|
||||
this.timeoutChecker = timeoutChecker;
|
||||
this.updateRecord = new ExecHostLogDO();
|
||||
this.status = ExecHostStatusEnum.WAITING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Long id = execHostCommand.getHostLogId();
|
||||
Exception ex = null;
|
||||
log.info("ExecCommandHandler run start id: {}, info: {}", id, JSON.toJSONString(execHostCommand));
|
||||
// 更新状态
|
||||
this.updateStatus(ExecHostStatusEnum.RUNNING, null);
|
||||
log.info("ExecCommandHandler run start id: {}", id);
|
||||
// 初始化数据以及修改状态
|
||||
if (!this.initData()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// 初始化日志
|
||||
this.initLogOutputStream();
|
||||
// 执行命令
|
||||
this.execCommand();
|
||||
log.info("ExecCommandHandler run complete id: {}", id);
|
||||
@@ -135,13 +152,41 @@ 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());
|
||||
// 设置日志路径
|
||||
this.setLogPath();
|
||||
// 设置脚本路径
|
||||
this.setScriptPath();
|
||||
// 设置执行的命令以及参数
|
||||
this.setExecCommand();
|
||||
} 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());
|
||||
s.setParameter(execHostLog.getParameter());
|
||||
s.setLogPath(execHostLog.getLogPath());
|
||||
s.setScriptPath(execHostLog.getScriptPath());
|
||||
});
|
||||
return passed;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -150,29 +195,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 +232,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 +259,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 +291,45 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
*
|
||||
* @param status status
|
||||
* @param ex ex
|
||||
* @param filler filler
|
||||
*/
|
||||
private void updateStatus(ExecHostStatusEnum status, Exception ex) {
|
||||
private void updateStatus(ExecHostStatusEnum status, Exception ex, Consumer<ExecHostLogDO> filler) {
|
||||
this.status = status;
|
||||
Long id = execHostCommand.getHostLogId();
|
||||
String statusName = status.name();
|
||||
execHostLog.setStatus(statusName);
|
||||
log.info("BaseExecCommandHandler.updateStatus start id: {}, status: {}", id, statusName);
|
||||
try {
|
||||
updateRecord.setId(id);
|
||||
updateRecord.setStatus(statusName);
|
||||
if (ExecHostStatusEnum.RUNNING.equals(status)) {
|
||||
// 运行中
|
||||
updateRecord.setStartTime(new Date());
|
||||
execHostLog.setStartTime(new Date());
|
||||
} else if (ExecHostStatusEnum.COMPLETED.equals(status)) {
|
||||
// 完成
|
||||
updateRecord.setFinishTime(new Date());
|
||||
updateRecord.setExitCode(executor.getExitCode());
|
||||
execHostLog.setFinishTime(new Date());
|
||||
execHostLog.setExitCode(executor.getExitCode());
|
||||
this.exitCode = executor.getExitCode();
|
||||
} else if (ExecHostStatusEnum.FAILED.equals(status)) {
|
||||
// 失败
|
||||
updateRecord.setFinishTime(new Date());
|
||||
updateRecord.setErrorMessage(this.getErrorMessage(ex));
|
||||
execHostLog.setFinishTime(new Date());
|
||||
execHostLog.setErrorMessage(this.getErrorMessage(ex));
|
||||
} else if (ExecHostStatusEnum.TIMEOUT.equals(status)) {
|
||||
// 超时
|
||||
updateRecord.setFinishTime(new Date());
|
||||
execHostLog.setFinishTime(new Date());
|
||||
} else if (ExecHostStatusEnum.INTERRUPTED.equals(status)) {
|
||||
// 中断
|
||||
updateRecord.setFinishTime(new Date());
|
||||
execHostLog.setFinishTime(new Date());
|
||||
}
|
||||
// 选择性更新
|
||||
ExecHostLogDO updateRecord = ExecHostLogDO.builder()
|
||||
.id(execHostLog.getId())
|
||||
.status(execHostLog.getStatus())
|
||||
.exitCode(execHostLog.getExitCode())
|
||||
.startTime(execHostLog.getStartTime())
|
||||
.finishTime(execHostLog.getFinishTime())
|
||||
.errorMessage(execHostLog.getErrorMessage())
|
||||
.build();
|
||||
// 填充参数
|
||||
if (filler != null) {
|
||||
filler.accept(updateRecord);
|
||||
}
|
||||
int effect = execHostLogDAO.updateById(updateRecord);
|
||||
log.info("BaseExecCommandHandler.updateStatus finish id: {}, effect: {}", id, effect);
|
||||
@@ -281,8 +345,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 +357,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 +365,59 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
Streams.close(logOutputStream);
|
||||
Streams.close(executor);
|
||||
Streams.close(sessionStore);
|
||||
execLogManager.asyncCloseTailFile(execHostCommand.getLogPath());
|
||||
execLogManager.asyncCloseTailFile(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置日志路径
|
||||
*/
|
||||
private void setLogPath() {
|
||||
// 构建日志路径
|
||||
String logPath = fileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(execHostLog.getLogId(), id));
|
||||
execHostLog.setLogPath(logPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置脚本路径
|
||||
*/
|
||||
private void setScriptPath() {
|
||||
String scriptPath = null;
|
||||
// 获取脚本路径
|
||||
if (BooleanBit.toBoolean(execLog.getScriptExec())) {
|
||||
HostOsTypeEnum os = HostOsTypeEnum.of(connect.getOsType());
|
||||
String name = FileConst.EXEC
|
||||
+ "/" + execHostLog.getLogId()
|
||||
+ "/" + id
|
||||
+ os.getScriptSuffix();
|
||||
scriptPath = PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.equals(os), connect.getUsername(), FileConst.SCRIPT, name);
|
||||
}
|
||||
execHostLog.setScriptPath(scriptPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置实际执行的命令以及参数
|
||||
*/
|
||||
private void setExecCommand() {
|
||||
String uuid = UUIds.random();
|
||||
// 参数列表
|
||||
Map<String, Object> params = Maps.newMap(builtParams);
|
||||
params.put("execHostId", id);
|
||||
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());
|
||||
// 获取实际命令
|
||||
String paramsJson = JSON.toJSONString(params);
|
||||
String command = ExecUtils.format(execLog.getCommand(), paramsJson);
|
||||
execHostLog.setCommand(command);
|
||||
execHostLog.setParameter(paramsJson);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -336,9 +450,4 @@ public abstract class BaseExecCommandHandler implements IExecCommandHandler {
|
||||
return Strings.retain(message, 250);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getHostId() {
|
||||
return execHostCommand.getHostId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,15 +24,16 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.ansi.AnsiAppender;
|
||||
import cn.orionsec.kit.lang.utils.ansi.style.color.AnsiForeground;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import cn.orionsec.kit.net.host.ssh.ExitCode;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 命令执行器 ansi 日志输出
|
||||
@@ -41,10 +42,13 @@ import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecComman
|
||||
* @version 1.0.0
|
||||
* @since 2024/4/25 18:19
|
||||
*/
|
||||
public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
public class ExecCommandDetailHandler extends BaseExecCommandHandler {
|
||||
|
||||
public ExecCommandAnsiHandler(ExecCommandDTO execCommand, ExecCommandHostDTO execHostCommand, TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(execCommand, execHostCommand, timeoutChecker);
|
||||
public ExecCommandDetailHandler(Long id,
|
||||
ExecLogDO execLog,
|
||||
Map<String, Object> builtParams,
|
||||
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(id, execLog, builtParams, timeoutChecker);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,52 +60,52 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
.append(this.getCurrentTime())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "执行记录: ")
|
||||
.append(execCommand.getLogId())
|
||||
.append(execLog.getId())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "执行描述: ")
|
||||
.append(execCommand.getDescription())
|
||||
.append(execLog.getDescription())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "执行用户: ")
|
||||
.append(execCommand.getUsername());
|
||||
.append(execLog.getUsername());
|
||||
// 非系统用户执行添加 userId
|
||||
if (Const.SYSTEM_USER_ID.equals(execCommand.getUserId())) {
|
||||
if (Const.SYSTEM_USER_ID.equals(execLog.getUserId())) {
|
||||
appender.newLine();
|
||||
} else {
|
||||
appender.append(" (")
|
||||
.append(execCommand.getUserId())
|
||||
.append(execLog.getUserId())
|
||||
.append(")")
|
||||
.newLine();
|
||||
}
|
||||
// 执行序列
|
||||
if (execCommand.getExecSeq() != null) {
|
||||
if (execLog.getExecSeq() != null) {
|
||||
appender.append(AnsiForeground.BRIGHT_BLUE, "执行序列: ")
|
||||
.append('#')
|
||||
.append(execCommand.getExecSeq())
|
||||
.append(execLog.getExecSeq())
|
||||
.newLine();
|
||||
}
|
||||
appender.append(AnsiForeground.BRIGHT_BLUE, "执行主机: ")
|
||||
.append(execHostCommand.getHostName())
|
||||
.append(execHostLog.getHostName())
|
||||
.append(" (")
|
||||
.append(execHostCommand.getHostId())
|
||||
.append(execHostLog.getHostId())
|
||||
.append(")")
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "主机地址: ")
|
||||
.append(execHostCommand.getHostAddress())
|
||||
.append(execHostLog.getHostAddress())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "超时时间: ")
|
||||
.append(execCommand.getTimeout())
|
||||
.append(execLog.getTimeout())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "脚本执行: ")
|
||||
.append(execCommand.getScriptExec())
|
||||
.append(execLog.getScriptExec())
|
||||
.newLine()
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_GREEN, "> 执行命令 ")
|
||||
.newLine()
|
||||
.append(execHostCommand.getCommand())
|
||||
.append(execHostLog.getCommand())
|
||||
.newLine()
|
||||
.newLine();
|
||||
// 非脚本执行拼接开始执行日志
|
||||
if (!Booleans.isTrue(execCommand.getScriptExec())) {
|
||||
if (!BooleanBit.toBoolean(execLog.getScriptExec())) {
|
||||
appender.append(AnsiForeground.BRIGHT_GREEN, "> 开始执行命令 ")
|
||||
.append(this.getCurrentTime())
|
||||
.newLine();
|
||||
@@ -119,7 +123,7 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
.append(this.getCurrentTime())
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_BLUE, "文件路径: ")
|
||||
.append(execHostCommand.getScriptPath())
|
||||
.append(execHostLog.getScriptPath())
|
||||
.newLine();
|
||||
this.appendLog(startAppender);
|
||||
// 上传脚本文件
|
||||
@@ -170,9 +174,9 @@ public class ExecCommandAnsiHandler extends BaseExecCommandHandler {
|
||||
appender.append(AnsiForeground.BRIGHT_YELLOW, "< 命令执行超时 ")
|
||||
.append(this.getCurrentTime())
|
||||
.newLine();
|
||||
} else {
|
||||
long ms = updateRecord.getFinishTime().getTime() - updateRecord.getStartTime().getTime();
|
||||
Integer exitCode = updateRecord.getExitCode();
|
||||
} else if (this.status == ExecHostStatusEnum.COMPLETED) {
|
||||
long ms = execHostLog.getFinishTime().getTime() - execHostLog.getStartTime().getTime();
|
||||
Integer exitCode = execHostLog.getExitCode();
|
||||
// 执行完成
|
||||
appender.append(AnsiForeground.BRIGHT_GREEN, "< 命令执行完成 ")
|
||||
.append(this.getCurrentTime())
|
||||
@@ -25,8 +25,9 @@ package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 命令执行器 原始日志输出
|
||||
@@ -38,8 +39,11 @@ import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecComman
|
||||
@Slf4j
|
||||
public class ExecCommandOriginHandler extends BaseExecCommandHandler {
|
||||
|
||||
public ExecCommandOriginHandler(ExecCommandDTO execCommand, ExecCommandHostDTO execHostCommand, TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(execCommand, execHostCommand, timeoutChecker);
|
||||
public ExecCommandOriginHandler(Long id,
|
||||
ExecLogDO execLog,
|
||||
Map<String, Object> builtParams,
|
||||
TimeoutChecker<TimeoutEndpoint> timeoutChecker) {
|
||||
super(id, execLog, builtParams, timeoutChecker);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,11 +22,14 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.exec.command.handler;
|
||||
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutChecker;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutCheckers;
|
||||
import cn.orionsec.kit.lang.support.timeout.TimeoutEndpoint;
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.Threads;
|
||||
import cn.orionsec.kit.lang.utils.Valid;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
@@ -34,17 +37,17 @@ import cn.orionsec.kit.net.host.ssh.ExitCode;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.module.asset.dao.ExecLogDAO;
|
||||
import org.dromara.visor.module.asset.define.AssetThreadPools;
|
||||
import org.dromara.visor.module.asset.define.config.AppExecLogConfig;
|
||||
import org.dromara.visor.module.asset.define.config.AppLogConfig;
|
||||
import org.dromara.visor.module.asset.define.message.ExecMessageDefine;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.ExecStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.manager.ExecTaskManager;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.utils.ExecUtils;
|
||||
import org.dromara.visor.module.infra.api.SystemMessageApi;
|
||||
import org.dromara.visor.module.infra.entity.dto.message.SystemMessageDTO;
|
||||
|
||||
@@ -63,15 +66,21 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class ExecTaskHandler implements IExecTaskHandler {
|
||||
|
||||
private static final AppLogConfig appLogConfig = SpringHolder.getBean(AppLogConfig.class);
|
||||
|
||||
private static final ExecLogDAO execLogDAO = SpringHolder.getBean(ExecLogDAO.class);
|
||||
|
||||
private static final ExecTaskManager execTaskManager = SpringHolder.getBean(ExecTaskManager.class);
|
||||
|
||||
private static final AppExecLogConfig appExecLogConfig = SpringHolder.getBean(AppExecLogConfig.class);
|
||||
|
||||
private static final SystemMessageApi systemMessageApi = SpringHolder.getBean(SystemMessageApi.class);
|
||||
|
||||
private final ExecCommandDTO execCommand;
|
||||
private final Long id;
|
||||
|
||||
private final List<Long> execHostIdList;
|
||||
|
||||
private ExecLogDO execLog;
|
||||
|
||||
private Map<String, Object> builtParams;
|
||||
|
||||
private TimeoutChecker<TimeoutEndpoint> timeoutChecker;
|
||||
|
||||
@@ -80,14 +89,19 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
|
||||
private Date startTime;
|
||||
|
||||
public ExecTaskHandler(ExecCommandDTO execCommand) {
|
||||
this.execCommand = execCommand;
|
||||
public ExecTaskHandler(Long id, List<Long> execHostIdList) {
|
||||
this.id = id;
|
||||
this.execHostIdList = execHostIdList;
|
||||
this.handlers = Lists.newList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Long id = execCommand.getLogId();
|
||||
log.info("ExecTaskHandler start id: {}", id);
|
||||
// 初始化数据
|
||||
if (!this.initData()) {
|
||||
return;
|
||||
}
|
||||
// 添加任务
|
||||
execTaskManager.addTask(id, this);
|
||||
log.info("ExecTaskHandler.run start id: {}", id);
|
||||
@@ -115,17 +129,37 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
|
||||
@Override
|
||||
public void interrupt() {
|
||||
log.info("ExecTaskHandler-interrupt id: {}", execCommand.getLogId());
|
||||
log.info("ExecTaskHandler-interrupt id: {}", id);
|
||||
handlers.forEach(IExecCommandHandler::interrupt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
log.info("ExecTaskHandler-close id: {}", execCommand.getLogId());
|
||||
log.info("ExecTaskHandler-close id: {}", id);
|
||||
Streams.close(timeoutChecker);
|
||||
this.handlers.forEach(Streams::close);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化数据
|
||||
*
|
||||
* @return pass
|
||||
*/
|
||||
private boolean initData() {
|
||||
try {
|
||||
// 查询任务
|
||||
this.execLog = execLogDAO.selectById(id);
|
||||
Valid.notNull(execLog, ErrorMessage.TASK_ABSENT);
|
||||
Valid.eq(execLog.getStatus(), ExecStatusEnum.WAITING.name(), ErrorMessage.ILLEGAL_STATUS);
|
||||
// 获取内置变量
|
||||
this.builtParams = this.getBaseBuiltinParams();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("ExecTaskHandler.init error id: {}", id, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行主机命令
|
||||
*
|
||||
@@ -133,19 +167,18 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
*/
|
||||
private void runHostCommand() throws Exception {
|
||||
// 超时检查
|
||||
if (execCommand.getTimeout() != 0) {
|
||||
if (execLog.getTimeout() != 0) {
|
||||
this.timeoutChecker = TimeoutCheckers.create();
|
||||
AssetThreadPools.TIMEOUT_CHECK.execute(this.timeoutChecker);
|
||||
}
|
||||
// 执行命令
|
||||
List<ExecCommandHostDTO> hosts = execCommand.getHosts();
|
||||
if (hosts.size() == 1) {
|
||||
if (execHostIdList.size() == 1) {
|
||||
// 单个主机直接执行
|
||||
IExecCommandHandler handler = this.createCommandHandler(hosts.get(0));
|
||||
IExecCommandHandler handler = this.createCommandHandler(execHostIdList.get(0));
|
||||
handlers.add(handler);
|
||||
handler.run();
|
||||
} else {
|
||||
hosts.stream()
|
||||
execHostIdList.stream()
|
||||
.map(this::createCommandHandler)
|
||||
.forEach(handlers::add);
|
||||
// 多个主机异步阻塞执行
|
||||
@@ -156,16 +189,16 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
/**
|
||||
* 创建命令执行器
|
||||
*
|
||||
* @param host host
|
||||
* @param execHostId execHostId
|
||||
* @return handler
|
||||
*/
|
||||
private IExecCommandHandler createCommandHandler(ExecCommandHostDTO host) {
|
||||
if (Booleans.isTrue(appExecLogConfig.getAppendAnsi())) {
|
||||
// ansi 日志
|
||||
return new ExecCommandAnsiHandler(execCommand, host, timeoutChecker);
|
||||
private IExecCommandHandler createCommandHandler(Long execHostId) {
|
||||
if (Booleans.isTrue(appLogConfig.getExecDetailLog())) {
|
||||
// 详细日志
|
||||
return new ExecCommandDetailHandler(execHostId, execLog, builtParams, timeoutChecker);
|
||||
} else {
|
||||
// 原始日志
|
||||
return new ExecCommandOriginHandler(execCommand, host, timeoutChecker);
|
||||
return new ExecCommandOriginHandler(execHostId, execLog, builtParams, timeoutChecker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +208,6 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
* @param status status
|
||||
*/
|
||||
private void updateStatus(ExecStatusEnum status) {
|
||||
Long id = execCommand.getLogId();
|
||||
try {
|
||||
String statusName = status.name();
|
||||
log.info("ExecTaskHandler-updateStatus start id: {}, status: {}", id, statusName);
|
||||
@@ -214,16 +246,43 @@ public class ExecTaskHandler implements IExecTaskHandler {
|
||||
}
|
||||
// 参数
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put(ExtraFieldConst.ID, execCommand.getLogId());
|
||||
params.put(ExtraFieldConst.ID, id);
|
||||
params.put(ExtraFieldConst.TIME, Dates.format(this.startTime, Dates.MD_HM));
|
||||
SystemMessageDTO message = SystemMessageDTO.builder()
|
||||
.receiverId(execCommand.getUserId())
|
||||
.receiverUsername(execCommand.getUsername())
|
||||
.relKey(String.valueOf(execCommand.getLogId()))
|
||||
.receiverId(execLog.getUserId())
|
||||
.receiverUsername(execLog.getUsername())
|
||||
.relKey(String.valueOf(id))
|
||||
.params(params)
|
||||
.build();
|
||||
// 发送
|
||||
systemMessageApi.create(ExecMessageDefine.EXEC_FAILED, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础内置参数
|
||||
*
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getBaseBuiltinParams() {
|
||||
String uuid = UUIds.random();
|
||||
Date date = new Date();
|
||||
// 输入参数
|
||||
Map<String, Object> params = ExecUtils.extraSchemaParams(execLog.getParameterSchema());
|
||||
// 添加内置参数
|
||||
params.put("userId", execLog.getUserId());
|
||||
params.put("username", execLog.getUsername());
|
||||
params.put("source", execLog.getSource());
|
||||
params.put("sourceId", execLog.getSourceId());
|
||||
params.put("seq", execLog.getExecSeq());
|
||||
params.put("execId", id);
|
||||
params.put("scriptExec", execLog.getScriptExec());
|
||||
params.put("uuid", uuid);
|
||||
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("timestampMillis", date.getTime());
|
||||
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
|
||||
params.put("date", Dates.format(date, Dates.YMD));
|
||||
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
|
||||
return params;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ public interface IExecCommandHandler extends Runnable, SafeCloseable {
|
||||
Integer getExitCode();
|
||||
|
||||
/**
|
||||
* 获取主机 id
|
||||
* 获取任务 id
|
||||
*
|
||||
* @return hostId
|
||||
*/
|
||||
Long getHostId();
|
||||
Long getId();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 - present Dromara, All rights reserved.
|
||||
*
|
||||
* https://visor.dromara.org
|
||||
* https://visor.dromara.org.cn
|
||||
* https://visor.orionsec.cn
|
||||
*
|
||||
* Members:
|
||||
* Jiahang Li - ljh1553488six@139.com - author
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.exec.command.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 批量执行启动对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/11 15:46
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ExecCommandDTO {
|
||||
|
||||
/**
|
||||
* logId
|
||||
*/
|
||||
private Long logId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 执行描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 执行序列
|
||||
*/
|
||||
private Integer execSeq;
|
||||
|
||||
/**
|
||||
* 超时时间
|
||||
*/
|
||||
private Integer timeout;
|
||||
|
||||
/**
|
||||
* 是否使用脚本执行
|
||||
*/
|
||||
private Boolean scriptExec;
|
||||
|
||||
/**
|
||||
* 执行主机
|
||||
*/
|
||||
private List<ExecCommandHostDTO> hosts;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -22,13 +22,11 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.handler.host.exec.log;
|
||||
|
||||
import cn.orionsec.kit.lang.annotation.Keep;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.common.interfaces.FileClient;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.define.AssetThreadPools;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecHostLogTailDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecLogTailDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.constant.LogConst;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.manager.ExecLogManager;
|
||||
@@ -52,41 +50,27 @@ import javax.annotation.Resource;
|
||||
@Component
|
||||
public class ExecLogTailHandler extends AbstractWebSocketHandler {
|
||||
|
||||
@Keep
|
||||
@Resource
|
||||
private FileClient logsFileClient;
|
||||
|
||||
@Resource
|
||||
private ExecLogManager execLogManager;
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(WebSocketSession session) {
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
String id = session.getId();
|
||||
log.info("ExecLogTailHandler-afterConnectionEstablished id: {}", id);
|
||||
// 获取参数
|
||||
ExecLogTailDTO info = WebSockets.getAttr(session, ExtraFieldConst.INFO);
|
||||
// 打开会话
|
||||
for (ExecHostLogTailDTO host : info.getHosts()) {
|
||||
String trackerId = this.getTrackerId(id, info, host);
|
||||
String absolutePath = logsFileClient.getAbsolutePath(host.getPath());
|
||||
// 追踪器
|
||||
ExecLogTracker tracker = new ExecLogTracker(trackerId,
|
||||
absolutePath,
|
||||
WebSockets.createSyncSession(session),
|
||||
host);
|
||||
// 执行
|
||||
String payload = message.getPayload();
|
||||
if (LogConst.PING_PAYLOAD.equals(payload)) {
|
||||
// ping
|
||||
WebSockets.sendText(session, LogConst.PONG_PAYLOAD);
|
||||
} else if (Strings.isInteger(payload)) {
|
||||
// 获取日志
|
||||
ExecLogTailDTO info = WebSockets.getAttr(session, ExtraFieldConst.INFO);
|
||||
Long execHostId = Long.valueOf(payload);
|
||||
ExecLogTracker tracker = new ExecLogTracker(info.getExecId(),
|
||||
execHostId,
|
||||
WebSockets.createSyncSession(session));
|
||||
// 执行追踪器
|
||||
AssetThreadPools.EXEC_LOG.execute(tracker);
|
||||
// 添加追踪器
|
||||
execLogManager.addTracker(tracker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
|
||||
String payload = message.getPayload();
|
||||
// ping
|
||||
if (LogConst.PING_PAYLOAD.equals(payload)) {
|
||||
WebSockets.sendText(session, LogConst.PONG_PAYLOAD);
|
||||
execLogManager.addTracker(id, tracker);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,24 +83,8 @@ public class ExecLogTailHandler extends AbstractWebSocketHandler {
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
String id = session.getId();
|
||||
log.info("ExecLogTailHandler-afterConnectionClosed id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason());
|
||||
// 关闭会话
|
||||
ExecLogTailDTO info = WebSockets.getAttr(session, ExtraFieldConst.INFO);
|
||||
// 移除追踪器
|
||||
for (ExecHostLogTailDTO host : info.getHosts()) {
|
||||
execLogManager.removeTracker(this.getTrackerId(id, info, host));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取追踪器 id
|
||||
*
|
||||
* @param id id
|
||||
* @param info info
|
||||
* @param host host
|
||||
* @return trackerId
|
||||
*/
|
||||
private String getTrackerId(String id, ExecLogTailDTO info, ExecHostLogTailDTO host) {
|
||||
return id + "_" + info.getId() + "_" + host.getId();
|
||||
execLogManager.removeTrackers(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -28,6 +28,8 @@ import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.tracker.IExecLogTracker;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -43,58 +45,58 @@ import java.util.stream.Collectors;
|
||||
@Component
|
||||
public class ExecLogManager {
|
||||
|
||||
private final ConcurrentHashMap<String, IExecLogTracker> execTrackers = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, List<IExecLogTracker>> execTrackers = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 添加执行日志追踪器
|
||||
*
|
||||
* @param id id
|
||||
* @param tracker tracker
|
||||
*/
|
||||
public void addTracker(IExecLogTracker tracker) {
|
||||
execTrackers.put(tracker.getTrackerId(), tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取日志追踪器
|
||||
*
|
||||
* @param trackerId trackerId
|
||||
* @return tracker
|
||||
*/
|
||||
public IExecLogTracker getTracker(String trackerId) {
|
||||
return execTrackers.get(trackerId);
|
||||
public void addTracker(String id, IExecLogTracker tracker) {
|
||||
execTrackers.computeIfAbsent(id, k -> new ArrayList<>()).add(tracker);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除日志追踪器
|
||||
*
|
||||
* @param trackerId trackerId
|
||||
* @param id id
|
||||
*/
|
||||
public void removeTracker(String trackerId) {
|
||||
IExecLogTracker tracker = execTrackers.remove(trackerId);
|
||||
if (tracker != null) {
|
||||
tracker.close();
|
||||
public void removeTrackers(String id) {
|
||||
// 移除并且关闭
|
||||
List<IExecLogTracker> trackers = execTrackers.remove(id);
|
||||
if (trackers != null) {
|
||||
trackers.forEach(IExecLogTracker::close);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步关闭进行中的追踪器
|
||||
*
|
||||
* @param path path
|
||||
* @param execHostId execHostId
|
||||
*/
|
||||
public void asyncCloseTailFile(String path) {
|
||||
public void asyncCloseTailFile(Long execHostId) {
|
||||
if (execHostId == null) {
|
||||
return;
|
||||
}
|
||||
// 获取当前路径的全部追踪器
|
||||
List<IExecLogTracker> trackers = execTrackers.values()
|
||||
.stream()
|
||||
.flatMap(Collection::stream)
|
||||
.filter(s -> s.getExecHostId().equals(execHostId))
|
||||
.collect(Collectors.toList());
|
||||
if (trackers.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 异步设置更新并且关闭
|
||||
Threads.start(() -> {
|
||||
try {
|
||||
// 获取当前路径的全部追踪器
|
||||
List<IExecLogTracker> trackers = execTrackers.values()
|
||||
.stream()
|
||||
.filter(s -> s.getPath().equals(path))
|
||||
.collect(Collectors.toList());
|
||||
Threads.sleep(Const.MS_S_1);
|
||||
trackers.forEach(IExecLogTracker::setLastModify);
|
||||
Threads.sleep(Const.MS_S_5);
|
||||
trackers.forEach(IExecLogTracker::close);
|
||||
} catch (Exception e) {
|
||||
log.error("ExecLogManager.asyncCloseTailFile error path: {}", path, e);
|
||||
log.error("ExecLogManager.asyncCloseTailFile error execHostId: {}", execHostId, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -26,15 +26,32 @@ import cn.orionsec.kit.ext.tail.Tracker;
|
||||
import cn.orionsec.kit.ext.tail.delay.DelayTrackerListener;
|
||||
import cn.orionsec.kit.ext.tail.mode.FileNotFoundMode;
|
||||
import cn.orionsec.kit.ext.tail.mode.FileOffsetMode;
|
||||
import cn.orionsec.kit.lang.exception.argument.InvalidArgumentException;
|
||||
import cn.orionsec.kit.lang.utils.Charsets;
|
||||
import cn.orionsec.kit.lang.utils.io.FileReaders;
|
||||
import cn.orionsec.kit.lang.utils.io.Files1;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.interfaces.FileClient;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.define.config.AppTrackerConfig;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecHostLogTailDTO;
|
||||
import org.dromara.visor.module.asset.dao.ExecHostLogDAO;
|
||||
import org.dromara.visor.module.asset.define.config.AppLogConfig;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.log.constant.LogConst;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* log tracker 实现类
|
||||
*
|
||||
@@ -45,78 +62,168 @@ 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 static final FileClient localFileClient = SpringHolder.getBean("localFileClient");
|
||||
|
||||
private static final ExecHostLogDAO execHostLogDAO = SpringHolder.getBean(ExecHostLogDAO.class);
|
||||
|
||||
private final WebSocketSession session;
|
||||
|
||||
private final ExecHostLogTailDTO config;
|
||||
@Getter
|
||||
private final Long execId;
|
||||
|
||||
@Getter
|
||||
private final String trackerId;
|
||||
private final Long execHostId;
|
||||
|
||||
@Getter
|
||||
private final String absolutePath;
|
||||
private Charset charset;
|
||||
|
||||
private String absolutePath;
|
||||
|
||||
private ExecHostLogDO execHostLog;
|
||||
|
||||
private RandomAccessFile file;
|
||||
|
||||
private DelayTrackerListener tracker;
|
||||
|
||||
private volatile boolean close;
|
||||
|
||||
public ExecLogTracker(String trackerId,
|
||||
String absolutePath,
|
||||
WebSocketSession session,
|
||||
ExecHostLogTailDTO config) {
|
||||
this.trackerId = trackerId;
|
||||
this.absolutePath = absolutePath;
|
||||
public ExecLogTracker(Long execId,
|
||||
Long execHostId,
|
||||
WebSocketSession session) {
|
||||
this.execId = execId;
|
||||
this.execHostId = execHostId;
|
||||
this.session = session;
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
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.run();
|
||||
// 初始化数据
|
||||
this.initData();
|
||||
// 查看日志
|
||||
if (ExecHostStatusEnum.RUNNING.name().equals(execHostLog.getStatus())) {
|
||||
// 追踪文件
|
||||
this.tailFile();
|
||||
} else {
|
||||
// 直接读取文件
|
||||
this.readFile();
|
||||
}
|
||||
} catch (InvalidArgumentException e) {
|
||||
// 业务异常
|
||||
log.error("exec log tracker init error id: {}", execHostId, e);
|
||||
// 发送消息
|
||||
this.sendMessage(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("exec log tracker error path: {}", absolutePath, e);
|
||||
log.error("exec log tracker exec error id: {}", execHostId, e);
|
||||
} finally {
|
||||
// 释放资源
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModify() {
|
||||
tracker.setFileLastModifyTime();
|
||||
/**
|
||||
* 初始化数据
|
||||
*/
|
||||
private void initData() {
|
||||
// 读取数据
|
||||
this.execHostLog = execHostLogDAO.selectByIdAndLogId(execHostId, execId);
|
||||
Valid.notNull(execHostLog, ErrorMessage.DATA_ABSENT);
|
||||
// 检查任务状态
|
||||
Valid.neq(execHostLog.getStatus(), ExecHostStatusEnum.WAITING.name(), ErrorMessage.ILLEGAL_STATUS);
|
||||
// 获取文件路径
|
||||
this.absolutePath = localFileClient.getAbsolutePath(execHostLog.getLogPath());
|
||||
Valid.isTrue(Files1.isFile(absolutePath), ErrorMessage.FILE_ABSENT);
|
||||
// 获取编码集
|
||||
this.charset = Charsets.of(this.getCharset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return config.getPath();
|
||||
/**
|
||||
* 追踪文件
|
||||
*/
|
||||
private void tailFile() {
|
||||
// 创建追踪器
|
||||
this.tracker = new DelayTrackerListener(absolutePath, this);
|
||||
tracker.charset(charset.name());
|
||||
tracker.delayMillis(appLogConfig.getTrackerLoadInterval());
|
||||
tracker.offset(FileOffsetMode.LINE, appLogConfig.getTrackerLoadLines());
|
||||
tracker.notFoundMode(FileNotFoundMode.WAIT_COUNT, Const.N_10);
|
||||
// 开始追踪
|
||||
tracker.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(byte[] bytes, int len, Tracker tracker) {
|
||||
// 发送消息
|
||||
String message = config.getId() + LogConst.SEPARATOR + new String(bytes, 0, len);
|
||||
/**
|
||||
* 读取文件
|
||||
*
|
||||
* @throws IOException IOException
|
||||
*/
|
||||
private void readFile() throws IOException {
|
||||
this.file = Files1.openRandomAccess(absolutePath, Const.ACCESS_R);
|
||||
// 获取文件位置
|
||||
long pos = FileReaders.readTailLinesSeek(file, appLogConfig.getTrackerLoadLines());
|
||||
// 设置文件位置
|
||||
file.seek(pos);
|
||||
// 读取到尾部
|
||||
byte[] buffer = new byte[Const.BUFFER_KB_8];
|
||||
int len;
|
||||
while ((len = file.read(buffer)) != -1) {
|
||||
// 发送消息
|
||||
this.sendMessage(new String(buffer, 0, len, charset));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取参数中的编码集
|
||||
*
|
||||
* @return charset
|
||||
*/
|
||||
private String getCharset() {
|
||||
JSONObject params = JSON.parseObject(execHostLog.getParameter());
|
||||
if (params != null) {
|
||||
String charset = params.getString(Const.CHARSET);
|
||||
if (charset != null) {
|
||||
return charset;
|
||||
}
|
||||
}
|
||||
return Const.UTF_8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param message message
|
||||
*/
|
||||
private void sendMessage(String message) {
|
||||
try {
|
||||
WebSockets.sendText(session, message);
|
||||
WebSockets.sendText(session, execHostId + LogConst.SEPARATOR + message);
|
||||
} catch (Exception e) {
|
||||
log.error("ExecLogTracker.send error", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLastModify() {
|
||||
if (tracker != null) {
|
||||
tracker.setFileLastModifyTime();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(byte[] bytes, int len, Tracker tracker) {
|
||||
// 发送消息
|
||||
this.sendMessage(new String(bytes, 0, len, charset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
log.info("ExecLogTracker.close path: {}, closed: {}", absolutePath, close);
|
||||
log.info("ExecLogTracker.close execHostId: {}, closed: {}", execHostId, close);
|
||||
if (close) {
|
||||
return;
|
||||
}
|
||||
this.close = true;
|
||||
if (file != null) {
|
||||
Streams.close(file);
|
||||
}
|
||||
if (tracker != null) {
|
||||
tracker.stop();
|
||||
}
|
||||
|
||||
@@ -34,30 +34,23 @@ import cn.orionsec.kit.lang.able.SafeCloseable;
|
||||
*/
|
||||
public interface IExecLogTracker extends Runnable, DataHandler, SafeCloseable {
|
||||
|
||||
/**
|
||||
* 获取 execId
|
||||
*
|
||||
* @return execId
|
||||
*/
|
||||
Long getExecId();
|
||||
|
||||
/**
|
||||
* 获取 execHostId
|
||||
*
|
||||
* @return execHostId
|
||||
*/
|
||||
Long getExecHostId();
|
||||
|
||||
/**
|
||||
* 设置最后修改时间
|
||||
*/
|
||||
void setLastModify();
|
||||
|
||||
/**
|
||||
* 获取 id
|
||||
*
|
||||
* @return id
|
||||
*/
|
||||
String getTrackerId();
|
||||
|
||||
/**
|
||||
* 获取路径
|
||||
*
|
||||
* @return path
|
||||
*/
|
||||
String getPath();
|
||||
|
||||
/**
|
||||
* 获取绝对路径
|
||||
*
|
||||
* @return 绝对路径
|
||||
*/
|
||||
String getAbsolutePath();
|
||||
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
|
||||
.hostName(hostName)
|
||||
.hostAddress(host.getAddress())
|
||||
.status(ex == null ? TerminalConnectStatusEnum.CONNECTING.name() : TerminalConnectStatusEnum.FAILED.name())
|
||||
.token(sessionId)
|
||||
.sessionId(sessionId)
|
||||
.extra(extra)
|
||||
.build();
|
||||
// 填充其他信息
|
||||
|
||||
@@ -34,6 +34,7 @@ import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
import org.dromara.visor.common.enums.BooleanBit;
|
||||
import org.dromara.visor.framework.websocket.core.utils.WebSockets;
|
||||
import org.dromara.visor.module.asset.define.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectTypeEnum;
|
||||
@@ -64,6 +65,9 @@ import java.util.Map;
|
||||
@Component
|
||||
public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConnectRequest> {
|
||||
|
||||
@Resource
|
||||
private AppSftpConfig appSftpConfig;
|
||||
|
||||
@Resource
|
||||
private TerminalConnectLogService terminalConnectLogService;
|
||||
|
||||
@@ -136,6 +140,7 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
|
||||
.charset(connect.getCharset())
|
||||
.fileNameCharset(connect.getFileNameCharset())
|
||||
.fileContentCharset(connect.getFileContentCharset())
|
||||
.filePreviewSize(appSftpConfig.getPreviewSize())
|
||||
.build();
|
||||
// 建立连接
|
||||
SessionStore sessionStore = SessionStores.openSessionStore(connect);
|
||||
|
||||
@@ -85,4 +85,9 @@ public class TerminalConfig {
|
||||
*/
|
||||
private String fileContentCharset;
|
||||
|
||||
/**
|
||||
* 文件预览大小
|
||||
*/
|
||||
private Integer filePreviewSize;
|
||||
|
||||
}
|
||||
|
||||
@@ -92,4 +92,9 @@ public class SftpFileVO {
|
||||
*/
|
||||
private Date modifyTime;
|
||||
|
||||
/**
|
||||
* 是否可预览
|
||||
*/
|
||||
private Boolean canPreview;
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
package org.dromara.visor.module.asset.handler.host.terminal.session;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Exceptions;
|
||||
import cn.orionsec.kit.lang.utils.Objects1;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.io.FileType;
|
||||
import cn.orionsec.kit.lang.utils.io.Files1;
|
||||
@@ -85,7 +86,7 @@ public class SftpSession extends TerminalSession implements ISftpSession {
|
||||
false,
|
||||
true);
|
||||
return files.stream()
|
||||
.map(SftpSession::fileMapping)
|
||||
.map(this::fileMapping)
|
||||
.sorted(Comparator.comparing(SftpFileVO::getName))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
@@ -133,7 +134,7 @@ public class SftpSession extends TerminalSession implements ISftpSession {
|
||||
return Arrays.stream(paths)
|
||||
.map(s -> executor.listFiles(s, true, false))
|
||||
.flatMap(Collection::stream)
|
||||
.map(SftpSession::fileMapping)
|
||||
.map(this::fileMapping)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@@ -197,7 +198,7 @@ public class SftpSession extends TerminalSession implements ISftpSession {
|
||||
* @param sftpFile sftpFile
|
||||
* @return file
|
||||
*/
|
||||
private static SftpFileVO fileMapping(SftpFile sftpFile) {
|
||||
private SftpFileVO fileMapping(SftpFile sftpFile) {
|
||||
SftpFileVO file = new SftpFileVO();
|
||||
file.setName(sftpFile.getName());
|
||||
file.setPath(sftpFile.getPath());
|
||||
@@ -212,7 +213,19 @@ public class SftpSession extends TerminalSession implements ISftpSession {
|
||||
.map(FileType.DIRECTORY::equals)
|
||||
.orElse(false);
|
||||
file.setIsDir(isDir);
|
||||
file.setCanPreview(this.calcCanPreview(sftpFile));
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否可预览
|
||||
*
|
||||
* @param file file
|
||||
* @return canPreview
|
||||
*/
|
||||
private boolean calcCanPreview(SftpFile file) {
|
||||
// 检查文件类型及大小
|
||||
return file.isRegularFile() && file.getSize() <= Objects1.def(config.getFilePreviewSize(), Const.N_2) * 1024 * 1024;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.module.asset.define.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.terminal.utils.TerminalUtils;
|
||||
import org.dromara.visor.module.asset.handler.host.transfer.enums.TransferReceiver;
|
||||
@@ -54,8 +53,6 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public abstract class TransferSession implements ITransferSession {
|
||||
|
||||
protected static final AppSftpConfig sftpConfig = SpringHolder.getBean(AppSftpConfig.class);
|
||||
|
||||
protected final TerminalConnectDTO connectInfo;
|
||||
|
||||
protected final SessionStore sessionStore;
|
||||
|
||||
@@ -62,7 +62,7 @@ public class UploadSession extends TransferSession {
|
||||
// 检查连接
|
||||
this.init();
|
||||
// 检查文件是否存在
|
||||
SftpUtils.checkUploadFilePresent(sftpConfig, executor, path);
|
||||
SftpUtils.checkUploadFilePresent(executor, path);
|
||||
// 打开输出流
|
||||
this.outputStream = executor.openOutputStream(path);
|
||||
// 响应结果
|
||||
|
||||
@@ -38,7 +38,6 @@ 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.module.asset.dao.UploadTaskFileDAO;
|
||||
import org.dromara.visor.module.asset.define.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.asset.entity.domain.UploadTaskFileDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.enums.HostOsTypeEnum;
|
||||
@@ -70,8 +69,6 @@ public class FileUploader implements IFileUploader {
|
||||
|
||||
private static final UploadTaskFileDAO uploadTaskFileDAO = SpringHolder.getBean(UploadTaskFileDAO.class);
|
||||
|
||||
private static final AppSftpConfig sftpConfig = SpringHolder.getBean(AppSftpConfig.class);
|
||||
|
||||
private static final FileClient localFileClient = SpringHolder.getBean("localFileClient");
|
||||
|
||||
private TerminalConnectDTO connectInfo;
|
||||
@@ -189,7 +186,7 @@ public class FileUploader implements IFileUploader {
|
||||
String endpoint = EndpointDefine.UPLOAD_SWAP.format(taskId);
|
||||
String localPath = localFileClient.getReturnPath(endpoint + Const.SLASH + file.getFileId());
|
||||
// 检查文件是否存在
|
||||
SftpUtils.checkUploadFilePresent(sftpConfig, executor, path);
|
||||
SftpUtils.checkUploadFilePresent(executor, path);
|
||||
// 打开输出流
|
||||
this.inputStream = localFileClient.getContentInputStream(localPath);
|
||||
this.outputStream = executor.openOutputStream(path);
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,14 @@ import java.util.List;
|
||||
*/
|
||||
public interface ExecHostLogService {
|
||||
|
||||
/**
|
||||
* 查询批量执行主机日志
|
||||
*
|
||||
* @param id id
|
||||
* @return row
|
||||
*/
|
||||
ExecHostLogVO getExecHostLog(Long id);
|
||||
|
||||
/**
|
||||
* 查询全部批量执行主机日志
|
||||
*
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecLogTailDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogClearRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogQueryRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogStatusVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
|
||||
@@ -139,10 +138,10 @@ public interface ExecLogService {
|
||||
/**
|
||||
* 查看执行日志
|
||||
*
|
||||
* @param request request
|
||||
* @param id id
|
||||
* @return token
|
||||
*/
|
||||
String getExecLogTailToken(ExecLogTailRequest request);
|
||||
String getExecLogTailToken(Long id);
|
||||
|
||||
/**
|
||||
* 获取查看执行日志参数
|
||||
|
||||
@@ -24,10 +24,6 @@ package org.dromara.visor.module.asset.service;
|
||||
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 主机配置 服务类
|
||||
@@ -36,47 +32,24 @@ import java.util.Map;
|
||||
* @version 1.0.0
|
||||
* @since 2023-9-11 14:16
|
||||
*/
|
||||
// TODO 待优化
|
||||
public interface HostConfigService {
|
||||
|
||||
/**
|
||||
* 获取主机配置
|
||||
*
|
||||
* @param id id
|
||||
* @param type type
|
||||
* @param <T> T
|
||||
* @param id id
|
||||
* @param <T> T
|
||||
* @return host
|
||||
*/
|
||||
<T extends GenericsDataModel> T getHostConfig(Long id, HostTypeEnum type);
|
||||
|
||||
/**
|
||||
* 构建主机配置
|
||||
*
|
||||
* @param host host
|
||||
* @param type type
|
||||
* @param <T> T
|
||||
* @return host
|
||||
*/
|
||||
<T extends GenericsDataModel> T buildHostConfig(HostDO host, HostTypeEnum type);
|
||||
<T extends GenericsDataModel> T getHostConfig(Long id);
|
||||
|
||||
/**
|
||||
* 获取主机配置
|
||||
*
|
||||
* @param idList idList
|
||||
* @param type type
|
||||
* @param <T> T
|
||||
* @return config
|
||||
* @param host host
|
||||
* @param <T> T
|
||||
* @return host
|
||||
*/
|
||||
<T extends GenericsDataModel> Map<Long, T> getHostConfigMap(List<Long> idList, HostTypeEnum type);
|
||||
|
||||
/**
|
||||
* 构建主机配置
|
||||
*
|
||||
* @param hostList hostList
|
||||
* @param type type
|
||||
* @param <T> T
|
||||
* @return config
|
||||
*/
|
||||
<T extends GenericsDataModel> Map<Long, T> buildHostConfigMap(List<HostDO> hostList, HostTypeEnum type);
|
||||
<T extends GenericsDataModel> T getHostConfig(HostDO host);
|
||||
|
||||
}
|
||||
|
||||
@@ -22,26 +22,15 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.annotation.Keep;
|
||||
import cn.orionsec.kit.lang.function.Functions;
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.Valid;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.NoMatchStrategy;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatter;
|
||||
import cn.orionsec.kit.lang.utils.json.matcher.ReplacementFormatters;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.FileConst;
|
||||
import org.dromara.visor.common.enums.EndpointDefine;
|
||||
import org.dromara.visor.common.interfaces.FileClient;
|
||||
import org.dromara.visor.common.security.LoginUser;
|
||||
import org.dromara.visor.common.utils.PathUtils;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.framework.security.core.utils.SecurityUtils;
|
||||
import org.dromara.visor.module.asset.convert.ExecConvert;
|
||||
@@ -54,25 +43,19 @@ import org.dromara.visor.module.asset.entity.domain.ExecHostLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.ExecLogDO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecCommandExecDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecParameterSchemaDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecCommandRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecHostLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
import org.dromara.visor.module.asset.enums.*;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.ExecTaskExecutors;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandDTO;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.model.ExecCommandHostDTO;
|
||||
import org.dromara.visor.module.asset.service.AssetAuthorizedDataService;
|
||||
import org.dromara.visor.module.asset.service.ExecCommandService;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -86,15 +69,8 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
|
||||
private static final ReplacementFormatter FORMATTER = ReplacementFormatters.create("@{{ ", " }}")
|
||||
.noMatchStrategy(NoMatchStrategy.KEEP);
|
||||
|
||||
private static final int DESC_OMIT = 60;
|
||||
|
||||
@Keep
|
||||
@Resource
|
||||
private FileClient logsFileClient;
|
||||
|
||||
@Resource
|
||||
private ExecLogDAO execLogDAO;
|
||||
|
||||
@@ -104,17 +80,12 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@Resource
|
||||
private HostConfigService hostConfigService;
|
||||
|
||||
@Resource
|
||||
private AssetAuthorizedDataService assetAuthorizedDataService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExecLogVO execCommand(ExecCommandRequest request) {
|
||||
log.info("ExecService.startExecCommand start params: {}", JSON.toJSONString(request));
|
||||
Valid.valid(ScriptExecEnum::of, request.getScriptExec());
|
||||
log.info("ExecService.execCommand start params: {}", JSON.toJSONString(request));
|
||||
LoginUser user = Valid.notNull(SecurityUtils.getLoginUser());
|
||||
Long userId = user.getId();
|
||||
List<Long> hostIdList = request.getHostIdList();
|
||||
@@ -123,26 +94,58 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
hostIdList.removeIf(s -> !authorizedHostIdList.contains(s));
|
||||
log.info("ExecService.startExecCommand host hostList: {}", hostIdList);
|
||||
Valid.notEmpty(hostIdList, ErrorMessage.CHECK_AUTHORIZED_HOST);
|
||||
// 执行命令
|
||||
// 创建命令
|
||||
ExecCommandExecDTO execRequest = ExecConvert.MAPPER.to(request);
|
||||
execRequest.setUserId(userId);
|
||||
execRequest.setUsername(user.getUsername());
|
||||
execRequest.setSource(ExecSourceEnum.BATCH.name());
|
||||
execRequest.setExecMode(ExecModeEnum.MANUAL.name());
|
||||
// 调用执行
|
||||
return this.execCommandWithSource(execRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExecLogVO reExecCommand(Long logId) {
|
||||
log.info("ExecService.reExecCommand start logId: {}", logId);
|
||||
// 获取执行记录
|
||||
ExecLogDO execLog = execLogDAO.selectByIdSource(logId, ExecSourceEnum.BATCH.name());
|
||||
Valid.notNull(execLog, ErrorMessage.DATA_ABSENT);
|
||||
// 获取执行主机
|
||||
List<ExecHostLogDO> hostLogs = execHostLogDAO.selectByLogId(logId);
|
||||
Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT);
|
||||
List<Long> hostIdList = hostLogs.stream()
|
||||
.map(ExecHostLogDO::getHostId)
|
||||
.collect(Collectors.toList());
|
||||
// 调用创建任务
|
||||
ExecCommandRequest request = ExecCommandRequest.builder()
|
||||
.description(execLog.getDescription())
|
||||
.timeout(execLog.getTimeout())
|
||||
.scriptExec(execLog.getScriptExec())
|
||||
.command(execLog.getCommand())
|
||||
.parameterSchema(execLog.getParameterSchema())
|
||||
.hostIdList(hostIdList)
|
||||
.build();
|
||||
// 调用执行
|
||||
return SpringHolder.getBean(ExecCommandService.class).execCommand(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecLogVO execCommandWithSource(ExecCommandExecDTO request) {
|
||||
log.info("ExecService.execCommandWithSource start params: {}", JSON.toJSONString(request));
|
||||
// 上下文调用执行
|
||||
ExecLogVO result = SpringHolder.getBean(ExecCommandService.class).createCommandWithSource(request);
|
||||
// 执行命令
|
||||
ExecTaskExecutors.start(result.getId(), Lists.map(result.getHosts(), ExecHostLogVO::getId));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
|
||||
public ExecLogVO createCommandWithSource(ExecCommandExecDTO request) {
|
||||
log.info("ExecService.createCommandWithSource start params: {}", JSON.toJSONString(request));
|
||||
String command = request.getCommand();
|
||||
List<Long> hostIdList = request.getHostIdList();
|
||||
// 查询主机信息
|
||||
List<HostDO> hosts = hostDAO.selectBatchIds(hostIdList);
|
||||
// 查询主机配置
|
||||
// TODO 待优化
|
||||
Map<Long, HostSshConfigModel> hostConfigMap = hostConfigService.buildHostConfigMap(hosts, HostTypeEnum.SSH);
|
||||
// 插入日志
|
||||
ExecLogDO execLog = ExecLogDO.builder()
|
||||
.userId(request.getUserId())
|
||||
@@ -166,17 +169,19 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
.build();
|
||||
execLogDAO.insert(execLog);
|
||||
Long execId = execLog.getId();
|
||||
// 获取内置参数
|
||||
Map<String, Object> builtinParams = this.getBaseBuiltinParams(execId, request);
|
||||
// 设置主机日志
|
||||
List<ExecHostLogDO> execHostLogs = hosts.stream()
|
||||
.map(s -> this.convertExecHostLog(s, execLog, hostConfigMap.get(s.getId()), builtinParams))
|
||||
.map(s -> ExecHostLogDO.builder()
|
||||
.logId(execLog.getId())
|
||||
.hostId(s.getId())
|
||||
.hostName(s.getName())
|
||||
.hostAddress(s.getAddress())
|
||||
.status(ExecHostStatusEnum.WAITING.name())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
execHostLogDAO.insertBatch(execHostLogs);
|
||||
// 操作日志
|
||||
OperatorLogs.add(OperatorLogs.LOG_ID, execId);
|
||||
// 开始执行
|
||||
this.startExec(execLog, execHostLogs, hostConfigMap);
|
||||
// 返回
|
||||
ExecLogVO result = ExecLogConvert.MAPPER.to(execLog);
|
||||
List<ExecHostLogVO> resultHosts = ExecHostLogConvert.MAPPER.to(execHostLogs);
|
||||
@@ -184,218 +189,4 @@ public class ExecCommandServiceImpl implements ExecCommandService {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public ExecLogVO reExecCommand(Long logId) {
|
||||
log.info("ExecService.reExecCommand start logId: {}", logId);
|
||||
// 获取执行记录
|
||||
ExecLogDO execLog = execLogDAO.selectByIdSource(logId, ExecSourceEnum.BATCH.name());
|
||||
Valid.notNull(execLog, ErrorMessage.DATA_ABSENT);
|
||||
// 获取执行主机
|
||||
List<ExecHostLogDO> hostLogs = execHostLogDAO.selectByLogId(logId);
|
||||
Valid.notEmpty(hostLogs, ErrorMessage.DATA_ABSENT);
|
||||
List<Long> hostIdList = hostLogs.stream()
|
||||
.map(ExecHostLogDO::getHostId)
|
||||
.collect(Collectors.toList());
|
||||
// 调用执行方法
|
||||
ExecCommandRequest request = ExecCommandRequest.builder()
|
||||
.description(execLog.getDescription())
|
||||
.timeout(execLog.getTimeout())
|
||||
.scriptExec(execLog.getScriptExec())
|
||||
.command(execLog.getCommand())
|
||||
.parameterSchema(execLog.getParameterSchema())
|
||||
.hostIdList(hostIdList)
|
||||
.build();
|
||||
return this.execCommand(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始执行命令
|
||||
*
|
||||
* @param execLog execLog
|
||||
* @param execHostLogs hostLogs
|
||||
* @param hostConfigMap hostConfigMap
|
||||
*/
|
||||
private void startExec(ExecLogDO execLog,
|
||||
List<ExecHostLogDO> execHostLogs,
|
||||
Map<Long, HostSshConfigModel> hostConfigMap) {
|
||||
// 执行主机
|
||||
List<ExecCommandHostDTO> hosts = execHostLogs.stream()
|
||||
.map(s -> {
|
||||
HostSshConfigModel config = hostConfigMap.get(s.getHostId());
|
||||
return ExecCommandHostDTO.builder()
|
||||
.hostId(s.getHostId())
|
||||
.hostLogId(s.getId())
|
||||
.hostName(s.getHostName())
|
||||
.hostAddress(s.getHostAddress())
|
||||
.command(s.getCommand())
|
||||
.logPath(s.getLogPath())
|
||||
.scriptPath(s.getScriptPath())
|
||||
.username(config.getUsername())
|
||||
.charset(config.getCharset())
|
||||
.fileNameCharset(config.getFileNameCharset())
|
||||
.fileContentCharset(config.getFileContentCharset())
|
||||
.build();
|
||||
}).collect(Collectors.toList());
|
||||
// 执行信息
|
||||
ExecCommandDTO exec = ExecCommandDTO.builder()
|
||||
.logId(execLog.getId())
|
||||
.userId(execLog.getUserId())
|
||||
.username(execLog.getUsername())
|
||||
.description(execLog.getDescription())
|
||||
.execSeq(execLog.getExecSeq())
|
||||
.timeout(execLog.getTimeout())
|
||||
.scriptExec(ScriptExecEnum.isEnabled(execLog.getScriptExec()))
|
||||
.hosts(hosts)
|
||||
.build();
|
||||
// 开始执行
|
||||
ExecTaskExecutors.start(exec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 execHostLog
|
||||
*
|
||||
* @param host host
|
||||
* @param execLog execLog
|
||||
* @param config config
|
||||
* @param builtinParams builtinParams
|
||||
* @return execHostLog
|
||||
*/
|
||||
private ExecHostLogDO convertExecHostLog(HostDO host,
|
||||
ExecLogDO execLog,
|
||||
HostSshConfigModel config,
|
||||
Map<String, Object> builtinParams) {
|
||||
Long execId = execLog.getId();
|
||||
Long hostId = host.getId();
|
||||
// 脚本路径
|
||||
String scriptPath = null;
|
||||
if (ScriptExecEnum.isEnabled(execLog.getScriptExec())) {
|
||||
scriptPath = this.buildScriptPath(config.getUsername(), host.getOsType(), execId, hostId);
|
||||
}
|
||||
// 获取参数
|
||||
String parameter = JSON.toJSONString(this.getHostParams(builtinParams, host, config, scriptPath));
|
||||
return ExecHostLogDO.builder()
|
||||
.logId(execId)
|
||||
.hostId(hostId)
|
||||
.hostName(host.getName())
|
||||
.hostAddress(host.getAddress())
|
||||
.status(ExecHostStatusEnum.WAITING.name())
|
||||
.command(FORMATTER.format(execLog.getCommand(), parameter))
|
||||
.parameter(parameter)
|
||||
.logPath(this.buildLogPath(execId, hostId))
|
||||
.scriptPath(scriptPath)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础内置参数
|
||||
*
|
||||
* @param execId execId
|
||||
* @param request request
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getBaseBuiltinParams(Long execId, ExecCommandExecDTO request) {
|
||||
String uuid = UUIds.random();
|
||||
Date date = new Date();
|
||||
// 输入参数
|
||||
Map<String, Object> params = this.extraSchemaParams(request.getParameterSchema());
|
||||
// 添加内置参数
|
||||
params.put("userId", request.getUserId());
|
||||
params.put("username", request.getUsername());
|
||||
params.put("source", request.getSource());
|
||||
params.put("sourceId", request.getSourceId());
|
||||
params.put("seq", request.getExecSeq());
|
||||
params.put("execId", execId);
|
||||
params.put("scriptExec", request.getScriptExec());
|
||||
params.put("uuid", uuid);
|
||||
params.put("uuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("timestampMillis", date.getTime());
|
||||
params.put("timestamp", date.getTime() / Dates.SECOND_STAMP);
|
||||
params.put("date", Dates.format(date, Dates.YMD));
|
||||
params.put("datetime", Dates.format(date, Dates.YMD_HMS));
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主机参数
|
||||
*
|
||||
* @param baseParams baseParams
|
||||
* @param host host
|
||||
* @param config config
|
||||
* @param scriptPath scriptPath
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> getHostParams(Map<String, Object> baseParams,
|
||||
HostDO host,
|
||||
HostSshConfigModel config,
|
||||
String scriptPath) {
|
||||
String uuid = UUIds.random();
|
||||
Map<String, Object> params = Maps.newMap(baseParams);
|
||||
params.put("hostId", host.getId());
|
||||
params.put("hostName", host.getName());
|
||||
params.put("hostCode", host.getCode());
|
||||
params.put("hostAddress", host.getAddress());
|
||||
params.put("hostPort", host.getPort());
|
||||
params.put("hostUuid", uuid);
|
||||
params.put("hostUuidShort", uuid.replace("-", Strings.EMPTY));
|
||||
params.put("hostUsername", config.getUsername());
|
||||
params.put("osType", host.getOsType());
|
||||
params.put("charset", config.getCharset());
|
||||
params.put("scriptPath", scriptPath);
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取参数
|
||||
*
|
||||
* @param parameterSchema parameterSchema
|
||||
* @return params
|
||||
*/
|
||||
private Map<String, Object> extraSchemaParams(String parameterSchema) {
|
||||
List<ExecParameterSchemaDTO> schemaList = JSON.parseArray(parameterSchema, ExecParameterSchemaDTO.class);
|
||||
if (Lists.isEmpty(schemaList)) {
|
||||
return Maps.newMap();
|
||||
}
|
||||
// 解析参数
|
||||
return schemaList.stream()
|
||||
.collect(Collectors.toMap(ExecParameterSchemaDTO::getName,
|
||||
s -> {
|
||||
Object value = s.getValue();
|
||||
if (value == null) {
|
||||
value = Const.EMPTY;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
Functions.right()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建日志路径
|
||||
*
|
||||
* @param logId logId
|
||||
* @param hostId hostId
|
||||
* @return logPath
|
||||
*/
|
||||
private String buildLogPath(Long logId, Long hostId) {
|
||||
return logsFileClient.getReturnPath(EndpointDefine.EXEC_LOG.format(logId, hostId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 侯建脚本路径
|
||||
*
|
||||
* @param username username
|
||||
* @param osType osType
|
||||
* @param logId logId
|
||||
* @param hostId hostId
|
||||
* @return scriptPath
|
||||
*/
|
||||
private String buildScriptPath(String username, String osType, Long logId, Long hostId) {
|
||||
HostOsTypeEnum os = HostOsTypeEnum.of(osType);
|
||||
String name = FileConst.EXEC
|
||||
+ "/" + logId
|
||||
+ "/" + hostId
|
||||
+ os.getScriptSuffix();
|
||||
return PathUtils.buildAppPath(HostOsTypeEnum.WINDOWS.equals(os), username, FileConst.SCRIPT, name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,6 +60,13 @@ public class ExecHostLogServiceImpl implements ExecHostLogService {
|
||||
@Resource
|
||||
private ExecTaskManager execTaskManager;
|
||||
|
||||
@Override
|
||||
public ExecHostLogVO getExecHostLog(Long id) {
|
||||
ExecHostLogDO record = execHostLogDAO.selectById(id);
|
||||
Valid.notNull(record, ErrorMessage.DATA_ABSENT);
|
||||
return ExecHostLogConvert.MAPPER.to(record);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExecHostLogVO> getExecHostLogList(Long logId) {
|
||||
return execHostLogDAO.of()
|
||||
@@ -99,7 +106,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);
|
||||
// 删除
|
||||
|
||||
@@ -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();
|
||||
// 查询任务主机
|
||||
|
||||
@@ -26,7 +26,6 @@ import cn.orionsec.kit.lang.annotation.Keep;
|
||||
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
|
||||
import cn.orionsec.kit.lang.id.UUIds;
|
||||
import cn.orionsec.kit.lang.utils.Arrays1;
|
||||
import cn.orionsec.kit.lang.utils.Objects1;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.collect.Lists;
|
||||
import cn.orionsec.kit.lang.utils.io.Files1;
|
||||
@@ -53,24 +52,19 @@ import org.dromara.visor.module.asset.dao.ExecLogDAO;
|
||||
import org.dromara.visor.module.asset.define.cache.ExecCacheKeyDefine;
|
||||
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.ExecHostLogTailDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.ExecLogTailDTO;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogClearRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogQueryRequest;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecHostLogVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogStatusVO;
|
||||
import org.dromara.visor.module.asset.entity.vo.ExecLogVO;
|
||||
import org.dromara.visor.module.asset.enums.ExecHostStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.ExecStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.handler.IExecCommandHandler;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.handler.IExecTaskHandler;
|
||||
import org.dromara.visor.module.asset.handler.host.exec.command.manager.ExecTaskManager;
|
||||
import org.dromara.visor.module.asset.service.ExecHostLogService;
|
||||
import org.dromara.visor.module.asset.service.ExecLogService;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -79,7 +73,10 @@ import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -105,9 +102,6 @@ public class ExecLogServiceImpl implements ExecLogService {
|
||||
@Resource
|
||||
private ExecTaskManager execTaskManager;
|
||||
|
||||
@Resource
|
||||
private HostConfigService hostConfigService;
|
||||
|
||||
@Keep
|
||||
@Resource
|
||||
private FileClient logsFileClient;
|
||||
@@ -328,7 +322,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);
|
||||
// 中断
|
||||
@@ -366,55 +360,17 @@ public class ExecLogServiceImpl implements ExecLogService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecLogTailToken(ExecLogTailRequest request) {
|
||||
String source = request.getSource();
|
||||
Long execId = request.getExecId();
|
||||
List<Long> hostExecIdList = request.getHostExecIdList();
|
||||
log.info("ExecLogService.getExecLogTailToken start execId: {}, hostExecIdList: {}", execId, hostExecIdList);
|
||||
// 查询执行日志
|
||||
ExecLogDO execLog = execLogDAO.selectByIdSource(execId, source);
|
||||
Valid.notNull(execLog, ErrorMessage.LOG_ABSENT);
|
||||
// 查询主机日志
|
||||
List<ExecHostLogDO> hostLogs;
|
||||
if (hostExecIdList == null) {
|
||||
hostLogs = execHostLogDAO.selectByLogId(execId);
|
||||
} else {
|
||||
hostLogs = execHostLogDAO.of()
|
||||
.createWrapper()
|
||||
.eq(ExecHostLogDO::getLogId, execId)
|
||||
.in(ExecHostLogDO::getId, hostExecIdList)
|
||||
.then()
|
||||
.list();
|
||||
}
|
||||
Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT);
|
||||
// 获取编码集
|
||||
// TODO 待优化
|
||||
List<Long> hostIdList = hostLogs.stream()
|
||||
.map(ExecHostLogDO::getHostId)
|
||||
.collect(Collectors.toList());
|
||||
Map<Long, HostSshConfigModel> configMap = hostConfigService.getHostConfigMap(hostIdList, HostTypeEnum.SSH);
|
||||
public String getExecLogTailToken(Long id) {
|
||||
// 生成缓存
|
||||
String token = UUIds.random19();
|
||||
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
|
||||
ExecLogTailDTO cache = ExecLogTailDTO.builder()
|
||||
.execId(id)
|
||||
.token(token)
|
||||
.id(execId)
|
||||
.userId(SecurityUtils.getLoginUserId())
|
||||
.hosts(hostLogs.stream()
|
||||
.map(s -> ExecHostLogTailDTO.builder()
|
||||
.id(s.getId())
|
||||
.hostId(s.getHostId())
|
||||
.path(s.getLogPath())
|
||||
.charset(Optional.ofNullable(configMap.get(s.getHostId()))
|
||||
.map(HostSshConfigModel::getCharset)
|
||||
.map(Objects1::toString)
|
||||
.orElse(Const.UTF_8))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
// 设置缓存
|
||||
RedisStrings.setJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL, cache);
|
||||
log.info("ExecLogService.getExecLogTailToken finish token: {}, execId: {}, hostExecIdList: {}", token, execId, hostExecIdList);
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.function.Functions;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.handler.data.model.GenericsDataModel;
|
||||
@@ -31,16 +30,10 @@ import org.dromara.visor.module.asset.dao.HostDAO;
|
||||
import org.dromara.visor.module.asset.entity.domain.HostDO;
|
||||
import org.dromara.visor.module.asset.enums.HostStatusEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 主机配置 服务实现类
|
||||
@@ -57,48 +50,23 @@ public class HostConfigServiceImpl implements HostConfigService {
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@Override
|
||||
public <T extends GenericsDataModel> T getHostConfig(Long id, HostTypeEnum type) {
|
||||
public <T extends GenericsDataModel> T getHostConfig(Long id) {
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(id);
|
||||
// 转换为配置
|
||||
return this.buildHostConfig(host, type);
|
||||
return this.getHostConfig(host);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends GenericsDataModel> T buildHostConfig(HostDO host, HostTypeEnum type) {
|
||||
public <T extends GenericsDataModel> T getHostConfig(HostDO host) {
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
// 检查主机类型
|
||||
Valid.isTrue(type.name().equals(host.getType()), ErrorMessage.HOST_TYPE_ERROR);
|
||||
HostTypeEnum type = HostTypeEnum.of(host.getType());
|
||||
// 检查主机状态
|
||||
Valid.isTrue(HostStatusEnum.ENABLED.name().equals(host.getStatus()), ErrorMessage.HOST_NOT_ENABLED);
|
||||
// 查询主机配置
|
||||
HostSshConfigModel model = type.parse(host.getConfig());
|
||||
Valid.notNull(model, ErrorMessage.CONFIG_ABSENT);
|
||||
return (T) model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GenericsDataModel> Map<Long, T> getHostConfigMap(List<Long> idList, HostTypeEnum type) {
|
||||
// 查询主机
|
||||
Map<Long, HostDO> hostMap = hostDAO.selectBatchIds(idList)
|
||||
.stream()
|
||||
.collect(Collectors.toMap(HostDO::getId, Function.identity(), Functions.right()));
|
||||
// 转换为配置
|
||||
Map<Long, T> result = new HashMap<>();
|
||||
for (Long id : idList) {
|
||||
result.put(id, this.buildHostConfig(hostMap.get(id), type));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends GenericsDataModel> Map<Long, T> buildHostConfigMap(List<HostDO> hostList, HostTypeEnum type) {
|
||||
Map<Long, T> result = new HashMap<>();
|
||||
for (HostDO host : hostList) {
|
||||
result.put(host.getId(), this.buildHostConfig(host, type));
|
||||
}
|
||||
return result;
|
||||
T config = type.parse(host.getConfig());
|
||||
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
|
||||
return (T) config;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ public class TerminalConnectLogServiceImpl implements TerminalConnectLogService
|
||||
.eq(TerminalConnectLogDO::getHostId, request.getHostId())
|
||||
.like(TerminalConnectLogDO::getHostAddress, request.getHostAddress())
|
||||
.eq(TerminalConnectLogDO::getType, request.getType())
|
||||
.like(TerminalConnectLogDO::getToken, request.getToken())
|
||||
.like(TerminalConnectLogDO::getSessionId, request.getSessionId())
|
||||
.eq(TerminalConnectLogDO::getStatus, request.getStatus())
|
||||
.in(TerminalConnectLogDO::getStatus, request.getStatusList())
|
||||
.ge(TerminalConnectLogDO::getStartTime, Arrays1.getIfPresent(request.getStartTimeRange(), 0))
|
||||
|
||||
@@ -42,7 +42,10 @@ import org.dromara.visor.module.asset.entity.dto.TerminalAccessDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalConnectDTO;
|
||||
import org.dromara.visor.module.asset.entity.dto.TerminalTransferDTO;
|
||||
import org.dromara.visor.module.asset.entity.vo.TerminalThemeVO;
|
||||
import org.dromara.visor.module.asset.enums.*;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostExtraSshAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostIdentityTypeEnum;
|
||||
import org.dromara.visor.module.asset.enums.HostSshAuthTypeEnum;
|
||||
import org.dromara.visor.module.asset.handler.host.config.model.HostSshConfigModel;
|
||||
import org.dromara.visor.module.asset.handler.host.extra.model.HostSshExtraModel;
|
||||
import org.dromara.visor.module.asset.service.HostConfigService;
|
||||
@@ -171,7 +174,7 @@ public class TerminalServiceImpl implements TerminalService {
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
// 查询主机配置
|
||||
HostSshConfigModel config = hostConfigService.buildHostConfig(host, HostTypeEnum.SSH);
|
||||
HostSshConfigModel config = hostConfigService.getHostConfig(host);
|
||||
// 获取配置
|
||||
return this.getHostConnectInfo(host, config, null);
|
||||
}
|
||||
@@ -195,7 +198,7 @@ public class TerminalServiceImpl implements TerminalService {
|
||||
ErrorMessage.ANY_NO_PERMISSION,
|
||||
DataPermissionTypeEnum.HOST_GROUP.getPermissionName());
|
||||
// 获取主机配置
|
||||
HostSshConfigModel config = hostConfigService.buildHostConfig(host, HostTypeEnum.SSH);
|
||||
HostSshConfigModel config = hostConfigService.getHostConfig(host);
|
||||
Valid.notNull(config, ErrorMessage.CONFIG_ABSENT);
|
||||
// 查询主机额外配置
|
||||
HostSshExtraModel extra = hostExtraService.getHostExtra(userId, hostId, HostExtraItemEnum.SSH);
|
||||
@@ -234,6 +237,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());
|
||||
|
||||
@@ -22,14 +22,15 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.task;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.utils.LockerUtils;
|
||||
import org.dromara.visor.module.asset.define.config.AppExecLogAutoClearConfig;
|
||||
import org.dromara.visor.module.asset.define.config.AppAutoClearConfig;
|
||||
import org.dromara.visor.module.asset.entity.request.exec.ExecLogClearRequest;
|
||||
import org.dromara.visor.module.asset.enums.ExecStatusEnum;
|
||||
import org.dromara.visor.module.asset.service.ExecLogService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -45,16 +46,14 @@ import java.util.Date;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "app.auto-clear.exec-log.enabled", havingValue = "true")
|
||||
public class ExecLogFileAutoClearTask {
|
||||
|
||||
/**
|
||||
* 分布式锁名称
|
||||
*/
|
||||
private static final Integer BATCH_SIZE = Const.N_1000;
|
||||
|
||||
private static final String LOCK_KEY = "clear:exl:lock";
|
||||
|
||||
@Resource
|
||||
private AppExecLogAutoClearConfig appExecLogAutoClearConfig;
|
||||
private AppAutoClearConfig appAutoClearConfig;
|
||||
|
||||
@Resource
|
||||
private ExecLogService execLogService;
|
||||
@@ -65,6 +64,11 @@ public class ExecLogFileAutoClearTask {
|
||||
@Scheduled(cron = "0 0 3 * * ?")
|
||||
public void clear() {
|
||||
log.info("ExecLogFileAutoClearTask.clear start");
|
||||
// 检查是否开启
|
||||
if (!Booleans.isTrue(appAutoClearConfig.getExecLogEnabled())) {
|
||||
log.info("ExecLogFileAutoClearTask.clear disabled");
|
||||
return;
|
||||
}
|
||||
// 获取锁并执行
|
||||
LockerUtils.tryLock(LOCK_KEY, this::doClear);
|
||||
log.info("ExecLogFileAutoClearTask.clear finish");
|
||||
@@ -76,13 +80,22 @@ public class ExecLogFileAutoClearTask {
|
||||
private void doClear() {
|
||||
// 删除的时间区间
|
||||
Date createLessEq = Dates.stream()
|
||||
.subDay(appExecLogAutoClearConfig.getKeepPeriod())
|
||||
.subDay(appAutoClearConfig.getExecLogKeepDays())
|
||||
.date();
|
||||
// 清理
|
||||
ExecLogClearRequest request = new ExecLogClearRequest();
|
||||
request.setCreateTimeLe(createLessEq);
|
||||
request.setStatusList(ExecStatusEnum.FINISH_STATUS_LIST);
|
||||
execLogService.clearExecLog(request);
|
||||
for (int i = 0; ; i++) {
|
||||
log.info("ExecLogFileAutoClearTask.doClear start batch: {}", i + 1);
|
||||
ExecLogClearRequest request = new ExecLogClearRequest();
|
||||
request.setCreateTimeLe(createLessEq);
|
||||
request.setStatusList(ExecStatusEnum.FINISH_STATUS_LIST);
|
||||
request.setLimit(BATCH_SIZE);
|
||||
Integer count = execLogService.clearExecLog(request);
|
||||
log.info("ExecLogFileAutoClearTask.doClear end batch: {}, count: {}", i + 1, count);
|
||||
// 最后一批
|
||||
if (count < BATCH_SIZE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,14 +22,15 @@
|
||||
*/
|
||||
package org.dromara.visor.module.asset.task;
|
||||
|
||||
import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.utils.LockerUtils;
|
||||
import org.dromara.visor.module.asset.define.config.AppTerminalConnectLogAutoClearConfig;
|
||||
import org.dromara.visor.module.asset.define.config.AppAutoClearConfig;
|
||||
import org.dromara.visor.module.asset.entity.request.host.TerminalConnectLogClearRequest;
|
||||
import org.dromara.visor.module.asset.enums.TerminalConnectStatusEnum;
|
||||
import org.dromara.visor.module.asset.service.TerminalConnectLogService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -45,16 +46,14 @@ import java.util.Date;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@ConditionalOnProperty(value = "app.auto-clear.terminal-connect-log.enabled", havingValue = "true")
|
||||
public class TerminalConnectLogAutoClearTask {
|
||||
|
||||
/**
|
||||
* 分布式锁名称
|
||||
*/
|
||||
private static final Integer BATCH_SIZE = Const.N_1000;
|
||||
|
||||
private static final String LOCK_KEY = "clear:tcl:lock";
|
||||
|
||||
@Resource
|
||||
private AppTerminalConnectLogAutoClearConfig appTerminalConnectLogAutoClearConfig;
|
||||
private AppAutoClearConfig appAutoClearConfig;
|
||||
|
||||
@Resource
|
||||
private TerminalConnectLogService terminalConnectLogService;
|
||||
@@ -65,6 +64,11 @@ public class TerminalConnectLogAutoClearTask {
|
||||
@Scheduled(cron = "0 10 3 * * ?")
|
||||
public void clear() {
|
||||
log.info("TerminalConnectLogAutoClearTask.clear start");
|
||||
// 检查是否开启
|
||||
if (!Booleans.isTrue(appAutoClearConfig.getTerminalLogEnabled())) {
|
||||
log.info("TerminalConnectLogAutoClearTask.clear disabled");
|
||||
return;
|
||||
}
|
||||
// 获取锁并执行
|
||||
LockerUtils.tryLock(LOCK_KEY, this::doClear);
|
||||
log.info("TerminalConnectLogAutoClearTask.clear finish");
|
||||
@@ -76,13 +80,22 @@ public class TerminalConnectLogAutoClearTask {
|
||||
private void doClear() {
|
||||
// 删除的时间区间
|
||||
Date createLessEq = Dates.stream()
|
||||
.subDay(appTerminalConnectLogAutoClearConfig.getKeepPeriod())
|
||||
.subDay(appAutoClearConfig.getTerminalLogKeepDays())
|
||||
.date();
|
||||
// 清理
|
||||
TerminalConnectLogClearRequest request = new TerminalConnectLogClearRequest();
|
||||
request.setCreateTimeLe(createLessEq);
|
||||
request.setStatusList(TerminalConnectStatusEnum.FINISH_STATUS_LIST);
|
||||
terminalConnectLogService.clearTerminalConnectLog(request);
|
||||
for (int i = 0; ; i++) {
|
||||
log.info("TerminalConnectLogAutoClearTask.doClear start batch: {}", i + 1);
|
||||
TerminalConnectLogClearRequest request = new TerminalConnectLogClearRequest();
|
||||
request.setCreateTimeLe(createLessEq);
|
||||
request.setStatusList(TerminalConnectStatusEnum.FINISH_STATUS_LIST);
|
||||
request.setLimit(BATCH_SIZE);
|
||||
Integer count = terminalConnectLogService.clearTerminalConnectLog(request);
|
||||
log.info("TerminalConnectLogAutoClearTask.doClear end batch: {}, count: {}", i + 1, count);
|
||||
// 最后一批
|
||||
if (count < BATCH_SIZE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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.EMPTY);
|
||||
|
||||
private ExecUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换命令
|
||||
*
|
||||
* @param command command
|
||||
* @param params params
|
||||
* @return command
|
||||
*/
|
||||
public static String format(String command, Map<String, Object> params) {
|
||||
return Strings.replaceCRLF(FORMATTER.format(command, params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 替换命令
|
||||
*
|
||||
* @param command command
|
||||
* @param json json
|
||||
* @return command
|
||||
*/
|
||||
public static String format(String command, String json) {
|
||||
return Strings.replaceCRLF(FORMATTER.format(command, json));
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取参数
|
||||
*
|
||||
* @param parameterSchema parameterSchema
|
||||
* @return params
|
||||
*/
|
||||
public static Map<String, Object> extraSchemaParams(String parameterSchema) {
|
||||
List<ExecParameterSchemaDTO> schemaList = JSON.parseArray(parameterSchema, ExecParameterSchemaDTO.class);
|
||||
if (Lists.isEmpty(schemaList)) {
|
||||
return Maps.newMap();
|
||||
}
|
||||
// 解析参数
|
||||
return schemaList.stream()
|
||||
.collect(Collectors.toMap(ExecParameterSchemaDTO::getName,
|
||||
s -> {
|
||||
Object value = s.getValue();
|
||||
if (value == null) {
|
||||
value = Const.EMPTY;
|
||||
}
|
||||
return value;
|
||||
},
|
||||
Functions.right()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import cn.orionsec.kit.lang.utils.Booleans;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpExecutor;
|
||||
import cn.orionsec.kit.net.host.sftp.SftpFile;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.dromara.visor.module.asset.define.config.AppSftpConfig;
|
||||
import org.dromara.visor.module.asset.handler.host.transfer.model.SftpFileBackupParams;
|
||||
@@ -39,19 +40,20 @@ import org.dromara.visor.module.asset.handler.host.transfer.model.SftpFileBackup
|
||||
*/
|
||||
public class SftpUtils {
|
||||
|
||||
private static final AppSftpConfig appSftpConfig = SpringHolder.getBean(AppSftpConfig.class);
|
||||
|
||||
private SftpUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查上传文件是否存在 并且执行响应策略
|
||||
*
|
||||
* @param config config
|
||||
* @param executor executor
|
||||
* @param path path
|
||||
*/
|
||||
public static void checkUploadFilePresent(AppSftpConfig config, SftpExecutor executor, String path) {
|
||||
public static void checkUploadFilePresent(SftpExecutor executor, String path) {
|
||||
// 重复不备份
|
||||
if (!Booleans.isTrue(config.getUploadPresentBackup())) {
|
||||
if (!Booleans.isTrue(appSftpConfig.getUploadPresentBackup())) {
|
||||
return;
|
||||
}
|
||||
// 检查文件是否存在
|
||||
@@ -59,7 +61,7 @@ public class SftpUtils {
|
||||
if (file != null) {
|
||||
// 文件存在则备份
|
||||
SftpFileBackupParams backupParams = new SftpFileBackupParams(file.getName());
|
||||
String target = Strings.format(config.getBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams)));
|
||||
String target = Strings.format(appSftpConfig.getUploadBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams)));
|
||||
// 移动
|
||||
executor.move(path, target);
|
||||
}
|
||||
|
||||
@@ -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": "终端连接日志自动清理 保留周期 (天)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
<result column="host_name" property="hostName"/>
|
||||
<result column="host_address" property="hostAddress"/>
|
||||
<result column="type" property="type"/>
|
||||
<result column="token" property="token"/>
|
||||
<result column="session_id" property="sessionId"/>
|
||||
<result column="status" property="status"/>
|
||||
<result column="start_time" property="startTime"/>
|
||||
<result column="end_time" property="endTime"/>
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, user_id, username, host_id, host_name, host_address, type, token, status, start_time, end_time, extra_info, create_time, update_time, deleted
|
||||
id, user_id, username, host_id, host_name, host_address, type, session_id, status, start_time, end_time, extra_info, create_time, update_time, deleted
|
||||
</sql>
|
||||
|
||||
<select id="selectLatestConnectHostId" resultType="java.lang.Long">
|
||||
|
||||
@@ -68,9 +68,6 @@ public class SystemUserDTO implements Serializable {
|
||||
@Schema(description = "用户状态 0停用 1启用")
|
||||
private Integer status;
|
||||
|
||||
@Schema(description = "最后登录时间")
|
||||
private Date lastLoginTime;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
|
||||
@@ -35,8 +35,7 @@ import org.dromara.visor.module.infra.define.operator.SystemSettingOperatorType;
|
||||
import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateBatchRequest;
|
||||
import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateRequest;
|
||||
import org.dromara.visor.module.infra.entity.vo.AppInfoVO;
|
||||
import org.dromara.visor.module.infra.entity.vo.SystemSettingAggregateVO;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.EncryptSystemSettingModel;
|
||||
import org.dromara.visor.module.infra.entity.vo.RsaKeyPairVO;
|
||||
import org.dromara.visor.module.infra.service.SystemSettingService;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -44,6 +43,7 @@ import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.annotation.security.PermitAll;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 系统设置服务
|
||||
@@ -74,7 +74,7 @@ public class SystemSettingController {
|
||||
@IgnoreLog(IgnoreLogMode.RET)
|
||||
@GetMapping("/setting")
|
||||
@Operation(summary = "获取系统聚合设置")
|
||||
public SystemSettingAggregateVO getSystemAggregateSetting() {
|
||||
public Map<String, String> getSystemAggregateSetting() {
|
||||
return systemSettingService.getSystemAggregateSetting();
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ public class SystemSettingController {
|
||||
@GetMapping("/generator-keypair")
|
||||
@Operation(summary = "生成密钥对")
|
||||
@PreAuthorize("@ss.hasPermission('infra:system-setting:update')")
|
||||
public EncryptSystemSettingModel generatorKeypair() {
|
||||
public RsaKeyPairVO generatorKeypair() {
|
||||
return systemSettingService.generatorKeypair();
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ public class SystemSettingController {
|
||||
@Operation(summary = "查询系统设置")
|
||||
@Parameter(name = "type", description = "type", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('infra:system-setting:update')")
|
||||
public Object getSystemSettingByType(@RequestParam("type") String type) {
|
||||
public Map<String, String> getSystemSettingByType(@RequestParam("type") String type) {
|
||||
return systemSettingService.getSystemSettingByType(type);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ package org.dromara.visor.module.infra.define.cache;
|
||||
import cn.orionsec.kit.lang.define.cache.key.CacheKeyBuilder;
|
||||
import cn.orionsec.kit.lang.define.cache.key.CacheKeyDefine;
|
||||
import cn.orionsec.kit.lang.define.cache.key.struct.RedisCacheStruct;
|
||||
import org.dromara.visor.module.infra.entity.vo.SystemSettingAggregateVO;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -39,10 +38,10 @@ import java.util.concurrent.TimeUnit;
|
||||
public interface SystemSettingKeyDefine {
|
||||
|
||||
CacheKeyDefine SETTING = new CacheKeyBuilder()
|
||||
.key("system:setting:agg")
|
||||
.key("system:setting:view")
|
||||
.desc("系统聚合设置")
|
||||
.type(SystemSettingAggregateVO.class)
|
||||
.struct(RedisCacheStruct.STRING)
|
||||
.type(String.class)
|
||||
.struct(RedisCacheStruct.HASH)
|
||||
.timeout(8, TimeUnit.HOURS)
|
||||
.build();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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<Integer> loginSessionTime;
|
||||
|
||||
/**
|
||||
* 是否允许多端登录
|
||||
*/
|
||||
private final ConfigRef<Boolean> allowMultiDevice;
|
||||
|
||||
/**
|
||||
* 是否允许凭证续签
|
||||
*/
|
||||
private final ConfigRef<Boolean> allowRefresh;
|
||||
|
||||
/**
|
||||
* 凭证续签最大次数
|
||||
*/
|
||||
private final ConfigRef<Integer> maxRefreshCount;
|
||||
|
||||
/**
|
||||
* 凭证续签间隔分
|
||||
*/
|
||||
private final ConfigRef<Integer> refreshInterval;
|
||||
|
||||
/**
|
||||
* 登录失败是否锁定
|
||||
*/
|
||||
private final ConfigRef<Boolean> loginFailedLock;
|
||||
|
||||
/**
|
||||
* 登录失败锁定阈值
|
||||
*/
|
||||
private final ConfigRef<Integer> loginFailedLockThreshold;
|
||||
|
||||
/**
|
||||
* 登录失败锁定时间 (分)
|
||||
*/
|
||||
private final ConfigRef<Integer> loginFailedLockTime;
|
||||
|
||||
/**
|
||||
* 登录失败发信
|
||||
*/
|
||||
private final ConfigRef<Boolean> loginFailedSend;
|
||||
|
||||
/**
|
||||
* 登录失败发送站内信阈值
|
||||
*/
|
||||
private final ConfigRef<Integer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -38,16 +38,12 @@ import static org.dromara.visor.framework.biz.operator.log.core.enums.OperatorRi
|
||||
@Module("infra:system-setting")
|
||||
public class SystemSettingOperatorType extends InitializingOperatorTypes {
|
||||
|
||||
public static final String UPDATE_TEXT = "<sb>{}</sb> - <sb>{}</sb> - <sb>{}</sb>";
|
||||
|
||||
public static final String UPDATE_BATCH_TEXT = "<sb>{}</sb>";
|
||||
|
||||
public static final String UPDATE = "system-setting:update";
|
||||
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(M, UPDATE, "更新系统设置 ${text}"),
|
||||
new OperatorType(M, UPDATE, "更新系统设置 <sb>${type}</sb>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -52,17 +52,13 @@ public class SystemSettingDO extends BaseDO {
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "配置key")
|
||||
@TableField("config_key")
|
||||
private String configKey;
|
||||
|
||||
@Schema(description = "配置类型")
|
||||
@TableField("type")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "配置项")
|
||||
@TableField("item")
|
||||
private String item;
|
||||
@Schema(description = "配置key")
|
||||
@TableField("config_key")
|
||||
private String configKey;
|
||||
|
||||
@Schema(description = "配置值")
|
||||
@TableField("value")
|
||||
|
||||
@@ -90,6 +90,10 @@ public class SystemUserDO extends BaseDO {
|
||||
@TableField("update_password_reason")
|
||||
private String updatePasswordReason;
|
||||
|
||||
@Schema(description = "修改密码时间")
|
||||
@TableField("update_password_time")
|
||||
private Date updatePasswordTime;
|
||||
|
||||
@Schema(description = "最后登录时间")
|
||||
@TableField("last_login_time")
|
||||
private Date lastLoginTime;
|
||||
|
||||
@@ -54,9 +54,9 @@ public class SystemSettingUpdateRequest implements Serializable {
|
||||
private String type;
|
||||
|
||||
@NotBlank
|
||||
@Size(max = 32)
|
||||
@Size(max = 64)
|
||||
@Schema(description = "配置项")
|
||||
private String item;
|
||||
private String configKey;
|
||||
|
||||
@Schema(description = "配置值")
|
||||
private String value;
|
||||
|
||||
@@ -58,7 +58,7 @@ public class InfraWorkplaceStatisticsVO {
|
||||
@Schema(description = "未读消息数量")
|
||||
private Integer unreadMessageCount;
|
||||
|
||||
@Schema(description = "上次登录时间")
|
||||
@Schema(description = "最后登录时间")
|
||||
private Date lastLoginTime;
|
||||
|
||||
@Schema(description = "当前登录会话数量")
|
||||
|
||||
@@ -20,34 +20,32 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.dromara.visor.module.infra.handler.setting.model;
|
||||
package org.dromara.visor.module.infra.entity.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 加密系统设置模型
|
||||
* RSA 密钥对响应对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/10/9 11:45
|
||||
* @since 2025/1/3 17:46
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class EncryptSystemSettingModel {
|
||||
@Schema(name = "RsaKeyPairVO", description = "RSA 密钥对响应对象")
|
||||
public class RsaKeyPairVO {
|
||||
|
||||
/**
|
||||
* 加密公钥
|
||||
*/
|
||||
@Schema(description = "公钥")
|
||||
private String publicKey;
|
||||
|
||||
/**
|
||||
* 加密私钥
|
||||
*/
|
||||
@Schema(description = "私钥")
|
||||
private String privateKey;
|
||||
|
||||
}
|
||||
@@ -1,53 +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.entity.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.EncryptSystemSettingModel;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.SftpSystemSettingModel;
|
||||
|
||||
/**
|
||||
* 系统设置 聚合响应对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/1/3 17:46
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "SystemSettingAggregateVO", description = "系统设置 聚合响应对象")
|
||||
public class SystemSettingAggregateVO {
|
||||
|
||||
@Schema(description = "SFTP 设置")
|
||||
private SftpSystemSettingModel sftp;
|
||||
|
||||
@Schema(description = "加密设置")
|
||||
private EncryptSystemSettingModel encrypt;
|
||||
|
||||
}
|
||||
@@ -72,6 +72,9 @@ public class SystemUserVO implements Serializable {
|
||||
@Schema(description = "最后登录时间")
|
||||
private Date lastLoginTime;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private Date createTime;
|
||||
|
||||
@@ -84,9 +87,6 @@ public class SystemUserVO implements Serializable {
|
||||
@Schema(description = "修改人")
|
||||
private String updater;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "用户角色")
|
||||
private List<SystemRoleVO> roles;
|
||||
|
||||
|
||||
@@ -22,14 +22,8 @@
|
||||
*/
|
||||
package org.dromara.visor.module.infra.enums;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.EncryptSystemSettingModel;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.SftpSystemSettingModel;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 系统设置类型枚举
|
||||
@@ -43,52 +37,32 @@ import java.util.Map;
|
||||
public enum SystemSettingTypeEnum {
|
||||
|
||||
/**
|
||||
* SFTP 配置
|
||||
* SFTP 设置
|
||||
*/
|
||||
SFTP("sftp", SftpSystemSettingModel.class),
|
||||
SFTP("sftp"),
|
||||
|
||||
/**
|
||||
* 加密配置
|
||||
* 加密设置
|
||||
*/
|
||||
ENCRYPT("encrypt", EncryptSystemSettingModel.class),
|
||||
ENCRYPT("encrypt"),
|
||||
|
||||
/**
|
||||
* 登录设置
|
||||
*/
|
||||
LOGIN("login"),
|
||||
|
||||
/**
|
||||
* 日志设置
|
||||
*/
|
||||
LOG("log"),
|
||||
|
||||
/**
|
||||
* 自动清理设置
|
||||
*/
|
||||
AUTO_CLEAR("autoClear"),
|
||||
|
||||
;
|
||||
|
||||
private final String prefix;
|
||||
|
||||
private final Class<?> modelClass;
|
||||
|
||||
/**
|
||||
* 解析
|
||||
*
|
||||
* @param settings settings
|
||||
* @return model
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T parseModel(Map<String, String> settings) {
|
||||
return (T) JSON.parseObject(JSON.toJSONString(settings)).toJavaObject(modelClass);
|
||||
}
|
||||
|
||||
public static SystemSettingTypeEnum of(String type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
for (SystemSettingTypeEnum value : values()) {
|
||||
if (value.name().equals(type)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 key
|
||||
*
|
||||
* @param item item
|
||||
* @return key
|
||||
*/
|
||||
public String getConfigKey(String item) {
|
||||
return prefix + Const.DOT + item;
|
||||
}
|
||||
private final String type;
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -25,8 +25,9 @@ package org.dromara.visor.module.infra.service;
|
||||
import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateBatchRequest;
|
||||
import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateRequest;
|
||||
import org.dromara.visor.module.infra.entity.vo.AppInfoVO;
|
||||
import org.dromara.visor.module.infra.entity.vo.SystemSettingAggregateVO;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.EncryptSystemSettingModel;
|
||||
import org.dromara.visor.module.infra.entity.vo.RsaKeyPairVO;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 系统设置服务
|
||||
@@ -49,33 +50,32 @@ public interface SystemSettingService {
|
||||
*
|
||||
* @return setting
|
||||
*/
|
||||
SystemSettingAggregateVO getSystemAggregateSetting();
|
||||
Map<String, String> getSystemAggregateSetting();
|
||||
|
||||
/**
|
||||
* 生成密钥对
|
||||
*
|
||||
* @return keypair
|
||||
*/
|
||||
EncryptSystemSettingModel generatorKeypair();
|
||||
RsaKeyPairVO generatorKeypair();
|
||||
|
||||
/**
|
||||
* 通过类型查询系统设置
|
||||
*
|
||||
* @param type type
|
||||
* @param <T> T
|
||||
* @return row
|
||||
* @return rows
|
||||
*/
|
||||
<T> T getSystemSettingByType(String type);
|
||||
Map<String, String> getSystemSettingByType(String type);
|
||||
|
||||
/**
|
||||
* 更新部分系统设置
|
||||
* 更新系统设置-单个
|
||||
*
|
||||
* @param request request
|
||||
*/
|
||||
void updateSystemSetting(SystemSettingUpdateRequest request);
|
||||
|
||||
/**
|
||||
* 更新系统设置
|
||||
* 更新系统设置-多个
|
||||
*
|
||||
* @param request request
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ package org.dromara.visor.module.infra.service.impl;
|
||||
|
||||
import cn.orionsec.kit.lang.annotation.Keep;
|
||||
import cn.orionsec.kit.lang.define.wrapper.Pair;
|
||||
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.collect.Lists;
|
||||
@@ -31,6 +32,8 @@ import cn.orionsec.kit.lang.utils.crypto.Signatures;
|
||||
import cn.orionsec.kit.lang.utils.time.Dates;
|
||||
import cn.orionsec.kit.web.servlet.web.Servlets;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import org.dromara.visor.common.config.ConfigStore;
|
||||
import org.dromara.visor.common.constant.ConfigKeys;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.constant.ExtraFieldConst;
|
||||
@@ -48,7 +51,7 @@ import org.dromara.visor.module.infra.convert.SystemUserConvert;
|
||||
import org.dromara.visor.module.infra.dao.SystemUserDAO;
|
||||
import org.dromara.visor.module.infra.dao.SystemUserRoleDAO;
|
||||
import org.dromara.visor.module.infra.define.cache.UserCacheKeyDefine;
|
||||
import org.dromara.visor.module.infra.define.config.AppAuthenticationConfig;
|
||||
import org.dromara.visor.module.infra.define.config.AppLoginConfig;
|
||||
import org.dromara.visor.module.infra.define.message.SystemUserMessageDefine;
|
||||
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
|
||||
import org.dromara.visor.module.infra.entity.dto.LoginTokenDTO;
|
||||
@@ -63,6 +66,7 @@ import org.dromara.visor.module.infra.service.UserPermissionService;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.*;
|
||||
@@ -80,7 +84,7 @@ import java.util.stream.Collectors;
|
||||
public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
|
||||
@Resource
|
||||
private AppAuthenticationConfig appAuthenticationConfig;
|
||||
private AppLoginConfig appLoginConfig;
|
||||
|
||||
@Resource
|
||||
private SystemUserDAO systemUserDAO;
|
||||
@@ -98,6 +102,17 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
@Resource
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
@Resource
|
||||
private ConfigStore configStore;
|
||||
|
||||
@PostConstruct
|
||||
public void initCacheExpireTime() {
|
||||
// 监听并且设置缓存过期时间
|
||||
configStore.int32(ConfigKeys.LOGIN_LOGIN_SESSION_TIME).onChange((v, b) -> this.setCacheExpireTime());
|
||||
configStore.int32(ConfigKeys.LOGIN_REFRESH_INTERVAL).onChange((v, b) -> this.setCacheExpireTime());
|
||||
this.setCacheExpireTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) {
|
||||
// 获取登录信息
|
||||
@@ -118,7 +133,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
// 用户状态校验
|
||||
this.checkUserStatus(user);
|
||||
Long id = user.getId();
|
||||
// 设置上次登录时间
|
||||
// 设置最后登录时间
|
||||
this.setLastLoginTime(id);
|
||||
// 删除用户缓存
|
||||
this.deleteUserCache(user);
|
||||
@@ -126,7 +141,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
this.setUserCache(user);
|
||||
long current = System.currentTimeMillis();
|
||||
// 不允许多端登录
|
||||
if (!appAuthenticationConfig.getAllowMultiDevice()) {
|
||||
if (Booleans.isFalse(appLoginConfig.getAllowMultiDevice())) {
|
||||
// 无效化其他缓存
|
||||
this.invalidOtherDeviceToken(id, current, remoteAddr, location, userAgent);
|
||||
}
|
||||
@@ -188,7 +203,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
return JSON.parseObject(loginCache, LoginTokenDTO.class);
|
||||
}
|
||||
// loginToken 不存在 需要查询 refreshToken
|
||||
if (!appAuthenticationConfig.getAllowRefresh()) {
|
||||
if (Booleans.isFalse(appLoginConfig.getAllowRefresh())) {
|
||||
return null;
|
||||
}
|
||||
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(pair.getKey(), pair.getValue());
|
||||
@@ -203,7 +218,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
refresh.setRefreshCount(refreshCount);
|
||||
// 设置登录缓存
|
||||
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, refresh);
|
||||
if (refreshCount < appAuthenticationConfig.getMaxRefreshCount()) {
|
||||
if (refreshCount < appLoginConfig.getMaxRefreshCount()) {
|
||||
// 小于续签最大次数 则再次设置 refreshToken
|
||||
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, refresh);
|
||||
} else {
|
||||
@@ -219,12 +234,14 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
if (password.length() != Const.MD5_LEN) {
|
||||
throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR);
|
||||
}
|
||||
// 检查登录失败次数
|
||||
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username);
|
||||
String failedCount = redisTemplate.opsForValue().get(failedCountKey);
|
||||
if (failedCount != null
|
||||
&& Integer.parseInt(failedCount) >= appAuthenticationConfig.getLoginFailedLockCount()) {
|
||||
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED);
|
||||
// 检查登录失败次数锁定
|
||||
if (Booleans.isTrue(appLoginConfig.getLoginFailedLock())) {
|
||||
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(username);
|
||||
String failedCount = redisTemplate.opsForValue().get(failedCountKey);
|
||||
if (failedCount != null
|
||||
&& Integer.parseInt(failedCount) >= appLoginConfig.getLoginFailedLockThreshold()) {
|
||||
throw Exceptions.argument(ErrorMessage.MAX_LOGIN_FAILED);
|
||||
}
|
||||
}
|
||||
// 获取登录用户
|
||||
SystemUserDO user = systemUserDAO.of()
|
||||
@@ -244,7 +261,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
// 刷新登录失败缓存
|
||||
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
|
||||
redisTemplate.opsForValue().increment(failedCountKey);
|
||||
RedisUtils.setExpire(failedCountKey, appAuthenticationConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
|
||||
RedisUtils.setExpire(failedCountKey, appLoginConfig.getLoginFailedLockTime(), TimeUnit.MINUTES);
|
||||
}
|
||||
return passRight;
|
||||
}
|
||||
@@ -268,13 +285,17 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
if (passRight) {
|
||||
return;
|
||||
}
|
||||
// 检查是否开启登录失败发信
|
||||
if (!Booleans.isTrue(appLoginConfig.getLoginFailedSend())) {
|
||||
return;
|
||||
}
|
||||
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
|
||||
String failedCountStr = redisTemplate.opsForValue().get(failedCountKey);
|
||||
if (failedCountStr == null || !Strings.isInteger(failedCountStr)) {
|
||||
return;
|
||||
}
|
||||
// 直接用相等 因为只触发一次
|
||||
if (!appAuthenticationConfig.getLoginFailedSendThreshold().equals(Integer.valueOf(failedCountStr))) {
|
||||
if (!Integer.valueOf(failedCountStr).equals(appLoginConfig.getLoginFailedSendThreshold())) {
|
||||
return;
|
||||
}
|
||||
// 发送站内信
|
||||
@@ -393,7 +414,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
}
|
||||
}
|
||||
// 删除续签信息
|
||||
if (appAuthenticationConfig.getAllowRefresh()) {
|
||||
if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) {
|
||||
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
|
||||
}
|
||||
}
|
||||
@@ -421,7 +442,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
.build();
|
||||
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue);
|
||||
// 生成 refreshToken
|
||||
if (appAuthenticationConfig.getAllowRefresh()) {
|
||||
if (Booleans.isTrue(appLoginConfig.getAllowRefresh())) {
|
||||
String refreshKey = UserCacheKeyDefine.LOGIN_REFRESH.format(id, loginTime);
|
||||
RedisStrings.setJson(refreshKey, UserCacheKeyDefine.LOGIN_REFRESH, loginValue);
|
||||
}
|
||||
@@ -429,4 +450,15 @@ public class AuthenticationServiceImpl implements AuthenticationService {
|
||||
return AesEncryptUtils.encryptBase62(id + ":" + loginTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新设置缓存时间
|
||||
*/
|
||||
private void setCacheExpireTime() {
|
||||
Integer loginSessionTime = appLoginConfig.getLoginSessionTime();
|
||||
if (loginSessionTime != null) {
|
||||
UserCacheKeyDefine.LOGIN_TOKEN.setTimeout(loginSessionTime);
|
||||
UserCacheKeyDefine.LOGIN_REFRESH.setTimeout(loginSessionTime + appLoginConfig.getRefreshInterval());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,43 +22,32 @@
|
||||
*/
|
||||
package org.dromara.visor.module.infra.service.impl;
|
||||
|
||||
import cn.orionsec.kit.ext.process.ProcessAwaitExecutor;
|
||||
import cn.orionsec.kit.lang.define.wrapper.Pair;
|
||||
import cn.orionsec.kit.lang.function.Functions;
|
||||
import cn.orionsec.kit.lang.support.Attempt;
|
||||
import cn.orionsec.kit.lang.utils.Arrays1;
|
||||
import cn.orionsec.kit.lang.utils.Objects1;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.collect.Maps;
|
||||
import cn.orionsec.kit.lang.utils.crypto.Keys;
|
||||
import cn.orionsec.kit.lang.utils.crypto.RSA;
|
||||
import cn.orionsec.kit.lang.utils.crypto.Signatures;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import cn.orionsec.kit.spring.SpringHolder;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.dromara.visor.common.constant.AppConst;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
import org.dromara.visor.common.constant.ErrorMessage;
|
||||
import org.dromara.visor.common.utils.Valid;
|
||||
import org.dromara.visor.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import org.dromara.visor.common.constant.ConfigKeys;
|
||||
import org.dromara.visor.framework.config.core.event.ConfigUpdateEvent;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisStrings;
|
||||
import org.dromara.visor.framework.mybatis.core.query.Conditions;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisMaps;
|
||||
import org.dromara.visor.framework.redis.core.utils.RedisUtils;
|
||||
import org.dromara.visor.module.infra.dao.SystemSettingDAO;
|
||||
import org.dromara.visor.module.infra.define.cache.SystemSettingKeyDefine;
|
||||
import org.dromara.visor.module.infra.define.operator.SystemSettingOperatorType;
|
||||
import org.dromara.visor.module.infra.entity.domain.SystemSettingDO;
|
||||
import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateBatchRequest;
|
||||
import org.dromara.visor.module.infra.entity.request.system.SystemSettingUpdateRequest;
|
||||
import org.dromara.visor.module.infra.entity.vo.AppInfoVO;
|
||||
import org.dromara.visor.module.infra.entity.vo.SystemSettingAggregateVO;
|
||||
import org.dromara.visor.module.infra.enums.SystemSettingTypeEnum;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.EncryptSystemSettingModel;
|
||||
import org.dromara.visor.module.infra.handler.setting.model.SftpSystemSettingModel;
|
||||
import org.dromara.visor.module.infra.entity.vo.RsaKeyPairVO;
|
||||
import org.dromara.visor.module.infra.service.SystemSettingService;
|
||||
import org.dromara.visor.module.infra.utils.SystemUuidUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.util.List;
|
||||
@@ -75,8 +64,6 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class SystemSettingServiceImpl implements SystemSettingService {
|
||||
|
||||
private String uuid;
|
||||
|
||||
@Resource
|
||||
private SystemSettingDAO systemSettingDAO;
|
||||
|
||||
@@ -84,129 +71,102 @@ public class SystemSettingServiceImpl implements SystemSettingService {
|
||||
public AppInfoVO getAppInfo() {
|
||||
return AppInfoVO.builder()
|
||||
.version(AppConst.VERSION)
|
||||
.uuid(this.getSystemUuid())
|
||||
.uuid(SystemUuidUtils.getSystemUuid())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SystemSettingAggregateVO getSystemAggregateSetting() {
|
||||
public Map<String, String> getSystemAggregateSetting() {
|
||||
// 查询缓存
|
||||
SystemSettingAggregateVO cache = RedisStrings.getJson(SystemSettingKeyDefine.SETTING);
|
||||
SystemSettingAggregateVO result = Objects1.def(cache, SystemSettingAggregateVO::new);
|
||||
if (cache == null) {
|
||||
Map<String, String> cache = RedisMaps.entities(SystemSettingKeyDefine.SETTING);
|
||||
if (Maps.isEmpty(cache)) {
|
||||
// 查询数据库
|
||||
Map<String, List<SystemSettingDO>> typeGroup = systemSettingDAO.of()
|
||||
cache = systemSettingDAO.of()
|
||||
.createWrapper()
|
||||
.select(SystemSettingDO::getType,
|
||||
SystemSettingDO::getItem,
|
||||
.select(SystemSettingDO::getConfigKey,
|
||||
SystemSettingDO::getValue)
|
||||
.in(SystemSettingDO::getType,
|
||||
SystemSettingTypeEnum.SFTP.name(),
|
||||
SystemSettingTypeEnum.ENCRYPT.name())
|
||||
.in(SystemSettingDO::getConfigKey,
|
||||
ConfigKeys.ENCRYPT_PUBLIC_KEY,
|
||||
ConfigKeys.LOG_WEB_SCROLL_LINES)
|
||||
.then()
|
||||
.stream()
|
||||
.collect(Collectors.groupingBy(SystemSettingDO::getType));
|
||||
// 数据组合
|
||||
typeGroup.forEach((k, v) -> {
|
||||
// 类型数据
|
||||
SystemSettingTypeEnum settingType = SystemSettingTypeEnum.of(k);
|
||||
Map<String, String> typeSettings = v.stream()
|
||||
.collect(Collectors.toMap(
|
||||
SystemSettingDO::getItem,
|
||||
SystemSettingDO::getValue,
|
||||
Functions.right()));
|
||||
Object setting = settingType.parseModel(typeSettings);
|
||||
if (SystemSettingTypeEnum.SFTP.equals(settingType)) {
|
||||
// SFTP 设置
|
||||
result.setSftp((SftpSystemSettingModel) setting);
|
||||
} else if (SystemSettingTypeEnum.ENCRYPT.equals(settingType)) {
|
||||
// 加密设置
|
||||
EncryptSystemSettingModel encryptSetting = (EncryptSystemSettingModel) setting;
|
||||
encryptSetting.setPrivateKey(null);
|
||||
result.setEncrypt(encryptSetting);
|
||||
}
|
||||
});
|
||||
.collect(Collectors.toMap(SystemSettingDO::getConfigKey,
|
||||
SystemSettingDO::getValue,
|
||||
Functions.right()));
|
||||
// 设置缓存
|
||||
RedisStrings.setJson(SystemSettingKeyDefine.SETTING, result);
|
||||
RedisMaps.putAll(SystemSettingKeyDefine.SETTING, cache);
|
||||
}
|
||||
return result;
|
||||
return cache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public EncryptSystemSettingModel generatorKeypair() {
|
||||
public RsaKeyPairVO generatorKeypair() {
|
||||
// 生成密钥对
|
||||
Pair<RSAPublicKey, RSAPrivateKey> pair = RSA.generatorKeys();
|
||||
return EncryptSystemSettingModel.builder()
|
||||
return RsaKeyPairVO.builder()
|
||||
.publicKey(Keys.getPublicKey(pair.getKey()))
|
||||
.privateKey(Keys.getPrivateKey(pair.getValue()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getSystemSettingByType(String type) {
|
||||
SystemSettingTypeEnum settingType = SystemSettingTypeEnum.of(type);
|
||||
Valid.notNull(settingType, ErrorMessage.ERROR_TYPE);
|
||||
// 查询数据库
|
||||
Map<String, String> settings = systemSettingDAO.of()
|
||||
public Map<String, String> getSystemSettingByType(String type) {
|
||||
return systemSettingDAO.of()
|
||||
.createWrapper()
|
||||
.eq(SystemSettingDO::getType, type)
|
||||
.then()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(
|
||||
SystemSettingDO::getItem,
|
||||
SystemSettingDO::getConfigKey,
|
||||
SystemSettingDO::getValue,
|
||||
Functions.right()));
|
||||
// 解析
|
||||
return settingType.parseModel(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSystemSetting(SystemSettingUpdateRequest request) {
|
||||
String type = request.getType();
|
||||
SystemSettingTypeEnum settingType = Valid.valid(SystemSettingTypeEnum::of, type);
|
||||
String item = request.getItem();
|
||||
String configKey = request.getConfigKey();
|
||||
String value = request.getValue();
|
||||
// 删除
|
||||
systemSettingDAO.delete(Conditions.eq(SystemSettingDO::getConfigKey, configKey));
|
||||
// 插入
|
||||
SystemSettingDO insert = SystemSettingDO.builder()
|
||||
.type(type)
|
||||
.configKey(configKey)
|
||||
.value(Strings.def(value))
|
||||
.build();
|
||||
systemSettingDAO.insert(insert);
|
||||
// 更新
|
||||
SystemSettingDO update = new SystemSettingDO();
|
||||
update.setValue(value);
|
||||
LambdaQueryWrapper<SystemSettingDO> wrapper = systemSettingDAO.lambda()
|
||||
.eq(SystemSettingDO::getType, type)
|
||||
.eq(SystemSettingDO::getItem, item);
|
||||
.eq(SystemSettingDO::getConfigKey, configKey);
|
||||
systemSettingDAO.update(update, wrapper);
|
||||
// 删除缓存
|
||||
RedisUtils.delete(SystemSettingKeyDefine.SETTING);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.TEXT, Strings.format(SystemSettingOperatorType.UPDATE_TEXT, type, item, value));
|
||||
// 触发修改事件
|
||||
SpringHolder.publishEvent(ConfigUpdateEvent.of(settingType.getConfigKey(item), value));
|
||||
SpringHolder.publishEvent(ConfigUpdateEvent.of(configKey, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateSystemSettingBatch(SystemSettingUpdateBatchRequest request) {
|
||||
String type = request.getType();
|
||||
SystemSettingTypeEnum settingType = Valid.valid(SystemSettingTypeEnum::of, type);
|
||||
Map<String, String> settings = request.getSettings();
|
||||
// 删除
|
||||
LambdaQueryWrapper<SystemSettingDO> deleteWrapper = systemSettingDAO.lambda()
|
||||
.eq(SystemSettingDO::getType, type)
|
||||
.in(SystemSettingDO::getItem, settings.keySet());
|
||||
systemSettingDAO.delete(deleteWrapper);
|
||||
systemSettingDAO.delete(Conditions.in(SystemSettingDO::getConfigKey, settings.keySet()));
|
||||
// 插入
|
||||
List<SystemSettingDO> rows = settings.entrySet()
|
||||
.stream()
|
||||
.map(s -> SystemSettingDO.builder()
|
||||
.configKey(settingType.getConfigKey(s.getKey()))
|
||||
.type(type)
|
||||
.item(s.getKey())
|
||||
.value(s.getValue())
|
||||
.configKey(s.getKey())
|
||||
.value(Strings.def(s.getValue()))
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
// 插入
|
||||
systemSettingDAO.insertBatch(rows);
|
||||
// 删除缓存
|
||||
RedisUtils.delete(SystemSettingKeyDefine.SETTING);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.TEXT, Strings.format(SystemSettingOperatorType.UPDATE_BATCH_TEXT, type));
|
||||
// 触发修改事件
|
||||
Map<String, String> eventConfig = rows.stream()
|
||||
.collect(Collectors.toMap(
|
||||
@@ -216,69 +176,4 @@ public class SystemSettingServiceImpl implements SystemSettingService {
|
||||
SpringHolder.publishEvent(ConfigUpdateEvent.of(eventConfig));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统 uuid
|
||||
*
|
||||
* @return uuid
|
||||
*/
|
||||
private String getSystemUuid() {
|
||||
if (this.uuid != null) {
|
||||
return this.uuid;
|
||||
}
|
||||
String[][] cmd = new String[][]{
|
||||
new String[]{"/bin/sh", "-c", "cat /sys/class/dmi/id/product_serial"},
|
||||
new String[]{"/bin/bash", "-c", "cat /sys/class/dmi/id/product_serial"},
|
||||
new String[]{"/bin/sh", "-c", "dmidecode -s system-uuid"},
|
||||
new String[]{"/bin/bash", "-c", "dmidecode -s system-uuid"},
|
||||
new String[]{"cmd", "/c", "wmic csproduct get uuid"}
|
||||
};
|
||||
for (String[] s : cmd) {
|
||||
try {
|
||||
String uuid = this.getCommandOutput(s);
|
||||
if (Strings.isBlank(uuid)) {
|
||||
continue;
|
||||
}
|
||||
// 去除符号并且转为大写
|
||||
uuid = uuid.replaceAll(Const.DASHED, Const.EMPTY)
|
||||
.toUpperCase()
|
||||
.trim();
|
||||
// 去除 \n
|
||||
String extraUuid = Arrays1.last(uuid.trim().split(Const.LF));
|
||||
if (!Strings.isBlank(extraUuid)) {
|
||||
uuid = extraUuid.trim();
|
||||
}
|
||||
// 去除 :
|
||||
extraUuid = Arrays1.last(uuid.trim().split(Const.COLON));
|
||||
if (!Strings.isBlank(extraUuid)) {
|
||||
uuid = extraUuid.trim();
|
||||
}
|
||||
return this.uuid = Signatures.md5(uuid);
|
||||
} catch (Exception e) {
|
||||
// IGNORED
|
||||
}
|
||||
}
|
||||
return this.uuid = Const.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出结果
|
||||
*
|
||||
* @param command command
|
||||
* @return result
|
||||
*/
|
||||
private String getCommandOutput(String[] command) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ProcessAwaitExecutor executor = new ProcessAwaitExecutor(command);
|
||||
try {
|
||||
executor.streamHandler(i -> Attempt.uncheck(Streams::transfer, i, out))
|
||||
.waitFor()
|
||||
.sync()
|
||||
.exec();
|
||||
return out.toString();
|
||||
} finally {
|
||||
Streams.close(out);
|
||||
Streams.close(executor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -50,7 +50,6 @@ import org.dromara.visor.module.infra.dao.SystemUserDAO;
|
||||
import org.dromara.visor.module.infra.dao.SystemUserRoleDAO;
|
||||
import org.dromara.visor.module.infra.define.cache.TipsCacheKeyDefine;
|
||||
import org.dromara.visor.module.infra.define.cache.UserCacheKeyDefine;
|
||||
import org.dromara.visor.module.infra.define.config.AppAuthenticationConfig;
|
||||
import org.dromara.visor.module.infra.entity.domain.SystemRoleDO;
|
||||
import org.dromara.visor.module.infra.entity.domain.SystemUserDO;
|
||||
import org.dromara.visor.module.infra.entity.dto.UserInfoDTO;
|
||||
@@ -64,10 +63,7 @@ import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -81,9 +77,6 @@ import java.util.stream.Collectors;
|
||||
@Service
|
||||
public class SystemUserServiceImpl implements SystemUserService {
|
||||
|
||||
@Resource
|
||||
private AppAuthenticationConfig appAuthenticationConfig;
|
||||
|
||||
@Resource
|
||||
private SystemUserDAO systemUserDAO;
|
||||
|
||||
@@ -322,6 +315,7 @@ public class SystemUserServiceImpl implements SystemUserService {
|
||||
update.setPassword(Signatures.md5(request.getPassword()));
|
||||
update.setUpdatePasswordStatus(UpdatePasswordStatusEnum.NO_REQUIRE.getStatus());
|
||||
update.setUpdatePasswordReason(Const.EMPTY);
|
||||
update.setUpdatePasswordTime(new Date());
|
||||
int effect = systemUserDAO.updateById(update);
|
||||
log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect);
|
||||
// 删除登录失败次数缓存
|
||||
@@ -329,9 +323,7 @@ public class SystemUserServiceImpl implements SystemUserService {
|
||||
// 删除登录缓存
|
||||
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"));
|
||||
// 删除续签信息
|
||||
if (appAuthenticationConfig.getAllowRefresh()) {
|
||||
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
|
||||
}
|
||||
RedisUtils.scanKeysDelete(UserCacheKeyDefine.LOGIN_REFRESH.format(id, "*"));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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.utils;
|
||||
|
||||
import cn.orionsec.kit.ext.process.ProcessAwaitExecutor;
|
||||
import cn.orionsec.kit.lang.support.Attempt;
|
||||
import cn.orionsec.kit.lang.utils.Arrays1;
|
||||
import cn.orionsec.kit.lang.utils.Strings;
|
||||
import cn.orionsec.kit.lang.utils.crypto.Signatures;
|
||||
import cn.orionsec.kit.lang.utils.io.Streams;
|
||||
import org.dromara.visor.common.constant.Const;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* 系统 UUID 工具类
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2025/1/16 11:07
|
||||
*/
|
||||
public class SystemUuidUtils {
|
||||
|
||||
private static String uuid;
|
||||
|
||||
private SystemUuidUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统 uuid
|
||||
*
|
||||
* @return uuid
|
||||
*/
|
||||
public static String getSystemUuid() {
|
||||
if (SystemUuidUtils.uuid != null) {
|
||||
return SystemUuidUtils.uuid;
|
||||
}
|
||||
String[][] cmd = new String[][]{
|
||||
new String[]{"/bin/sh", "-c", "cat /sys/class/dmi/id/product_serial"},
|
||||
new String[]{"/bin/bash", "-c", "cat /sys/class/dmi/id/product_serial"},
|
||||
new String[]{"/bin/sh", "-c", "dmidecode -s system-uuid"},
|
||||
new String[]{"/bin/bash", "-c", "dmidecode -s system-uuid"},
|
||||
new String[]{"cmd", "/c", "wmic csproduct get uuid"}
|
||||
};
|
||||
for (String[] s : cmd) {
|
||||
try {
|
||||
String uuid = SystemUuidUtils.getCommandOutput(s);
|
||||
if (Strings.isBlank(uuid)) {
|
||||
continue;
|
||||
}
|
||||
// 去除符号并且转为大写
|
||||
uuid = uuid.replaceAll(Const.DASHED, Const.EMPTY)
|
||||
.toUpperCase()
|
||||
.trim();
|
||||
// 去除 \n
|
||||
String extraUuid = Arrays1.last(uuid.trim().split(Const.LF));
|
||||
if (!Strings.isBlank(extraUuid)) {
|
||||
uuid = extraUuid.trim();
|
||||
}
|
||||
// 去除 :
|
||||
extraUuid = Arrays1.last(uuid.trim().split(Const.COLON));
|
||||
if (!Strings.isBlank(extraUuid)) {
|
||||
uuid = extraUuid.trim();
|
||||
}
|
||||
return SystemUuidUtils.uuid = Signatures.md5(uuid);
|
||||
} catch (Exception e) {
|
||||
// IGNORED
|
||||
}
|
||||
}
|
||||
return SystemUuidUtils.uuid = Const.UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取输出结果
|
||||
*
|
||||
* @param command command
|
||||
* @return result
|
||||
*/
|
||||
public static String getCommandOutput(String[] command) {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
ProcessAwaitExecutor executor = new ProcessAwaitExecutor(command);
|
||||
try {
|
||||
executor.streamHandler(i -> Attempt.uncheck(Streams::transfer, i, out))
|
||||
.waitFor()
|
||||
.sync()
|
||||
.exec();
|
||||
return out.toString();
|
||||
} finally {
|
||||
Streams.close(out);
|
||||
Streams.close(executor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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": "登录失败锁定时间 (分)."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,9 +5,8 @@
|
||||
<!-- 通用查询映射结果 -->
|
||||
<resultMap id="BaseResultMap" type="org.dromara.visor.module.infra.entity.domain.SystemSettingDO">
|
||||
<id column="id" property="id"/>
|
||||
<result column="config_key" property="configKey"/>
|
||||
<result column="type" property="type"/>
|
||||
<result column="item" property="item"/>
|
||||
<result column="config_key" property="configKey"/>
|
||||
<result column="value" property="value"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
<result column="update_time" property="updateTime"/>
|
||||
@@ -18,7 +17,7 @@
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, config_key, type, item, value, create_time, update_time, creator, updater, deleted
|
||||
id, config_key, type, value, create_time, update_time, creator, updater, deleted
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<result column="status" property="status"/>
|
||||
<result column="update_password_status" property="updatePasswordStatus"/>
|
||||
<result column="update_password_reason" property="updatePasswordReason"/>
|
||||
<result column="update_password_time" property="updatePasswordTime"/>
|
||||
<result column="last_login_time" property="lastLoginTime"/>
|
||||
<result column="description" property="description"/>
|
||||
<result column="create_time" property="createTime"/>
|
||||
@@ -25,7 +26,7 @@
|
||||
|
||||
<!-- 通用查询结果列 -->
|
||||
<sql id="Base_Column_List">
|
||||
id, username, password, nickname, avatar, mobile, email, status, update_password_status, update_password_reason, last_login_time, description, create_time, update_time, creator, updater, deleted
|
||||
id, username, password, nickname, avatar, mobile, email, status, update_password_status, update_password_reason, update_password_time, last_login_time, description, create_time, update_time, creator, updater, deleted
|
||||
</sql>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_API_BASE_URL=http://127.0.0.1:9200/orion-visor/api
|
||||
# websocket 路径
|
||||
VITE_WS_BASE_URL=ws://127.0.0.1:9200/orion-visor/keep-alive
|
||||
# 版本号
|
||||
VITE_APP_VERSION=2.3.1
|
||||
VITE_APP_VERSION=2.3.3
|
||||
|
||||
@@ -3,4 +3,4 @@ VITE_API_BASE_URL=/orion-visor/api
|
||||
# websocket 路径
|
||||
VITE_WS_BASE_URL=/orion-visor/keep-alive
|
||||
# 版本号
|
||||
VITE_APP_VERSION=2.3.1
|
||||
VITE_APP_VERSION=2.3.3
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "orion-visor-ui",
|
||||
"description": "Orion Visor UI",
|
||||
"version": "2.3.1",
|
||||
"version": "2.3.3",
|
||||
"private": true,
|
||||
"author": "Jiahang Li",
|
||||
"license": "Apache 2.0",
|
||||
|
||||
@@ -12,7 +12,7 @@ export interface TerminalConnectLogQueryRequest extends Pagination {
|
||||
hostId?: number;
|
||||
hostAddress?: string;
|
||||
type?: string;
|
||||
token?: string;
|
||||
sessionId?: string;
|
||||
status?: string;
|
||||
startTimeRange?: string[];
|
||||
}
|
||||
@@ -34,7 +34,7 @@ export interface TerminalConnectLogQueryResponse extends TableData {
|
||||
hostName: string;
|
||||
hostAddress: string;
|
||||
type: string;
|
||||
token: string;
|
||||
sessionId: string;
|
||||
status: string;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
@@ -65,7 +65,7 @@ export function getTerminalConnectLogPage(request: TerminalConnectLogQueryReques
|
||||
* 查询全部终端连接会话
|
||||
*/
|
||||
export function getTerminalConnectSessions(request: TerminalConnectLogQueryRequest) {
|
||||
return axios.post<Array<TerminalConnectLogQueryResponse>>('/asset/terminal-connect-log/session', request);
|
||||
return axios.post<Array<TerminalConnectLogQueryResponse>>('/asset/terminal-connect-log/sessions', request);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,8 +5,7 @@ import type {
|
||||
ExecLogInterruptRequest,
|
||||
ExecLogQueryRequest,
|
||||
ExecLogQueryResponse,
|
||||
ExecLogStatusResponse,
|
||||
ExecLogTailRequest
|
||||
ExecLogStatusResponse
|
||||
} from './exec-log';
|
||||
import axios from 'axios';
|
||||
import qs from 'query-string';
|
||||
@@ -25,6 +24,13 @@ export function getExecCommandLog(id: number) {
|
||||
return axios.get<ExecLogQueryResponse>('/asset/exec-command-log/get', { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主机计划任务日志
|
||||
*/
|
||||
export function getExecCommandHostLog(id: number) {
|
||||
return axios.get<ExecHostLogQueryResponse>('/asset/exec-command-log/get-host', { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主机批量执行日志
|
||||
*/
|
||||
@@ -98,8 +104,8 @@ export function clearExecCommandLog(request: ExecLogClearRequest) {
|
||||
/**
|
||||
* 查看批量执行日志
|
||||
*/
|
||||
export function getExecCommandLogTailToken(request: ExecLogTailRequest) {
|
||||
return axios.post<string>('/asset/exec-command-log/tail', request);
|
||||
export function getExecCommandLogTailToken(id: number) {
|
||||
return axios.get<string>('/asset/exec-command-log/tail', { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@ import type {
|
||||
ExecLogQueryRequest,
|
||||
ExecLogQueryResponse,
|
||||
ExecLogStatusResponse,
|
||||
ExecLogTailRequest
|
||||
} from './exec-log';
|
||||
import axios from 'axios';
|
||||
import qs from 'query-string';
|
||||
@@ -25,6 +24,13 @@ export function getExecJobLog(id: number) {
|
||||
return axios.get<ExecLogQueryResponse>('/asset/exec-job-log/get', { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主机计划任务日志
|
||||
*/
|
||||
export function getExecJobHostLog(id: number) {
|
||||
return axios.get<ExecHostLogQueryResponse>('/asset/exec-job-log/get-host', { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询主机计划任务日志
|
||||
*/
|
||||
@@ -91,8 +97,8 @@ export function clearExecJobLog(request: ExecLogClearRequest) {
|
||||
/**
|
||||
* 查看计划任务日志
|
||||
*/
|
||||
export function getExecJobLogTailToken(request: ExecLogTailRequest) {
|
||||
return axios.post<string>('/asset/exec-job-log/tail', request);
|
||||
export function getExecJobLogTailToken(id: number) {
|
||||
return axios.get<string>('/asset/exec-job-log/tail', { params: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,8 +48,8 @@ export interface ExecJobQueryRequest extends Pagination {
|
||||
name?: string;
|
||||
command?: string;
|
||||
status?: number;
|
||||
execUserId?: number;
|
||||
queryRecentLog?: boolean;
|
||||
execUserId?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface ExecLogQueryExtraResponse {
|
||||
/**
|
||||
* 主机执行日志查询响应
|
||||
*/
|
||||
export interface ExecHostLogQueryResponse extends TableData {
|
||||
export interface ExecHostLogQueryResponse extends TableData, ExecHostLogQueryResponseExtra {
|
||||
id: number;
|
||||
logId: number;
|
||||
hostId: number;
|
||||
@@ -66,6 +66,13 @@ export interface ExecHostLogQueryResponse extends TableData {
|
||||
finishTime: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 主机执行日志额外参数
|
||||
*/
|
||||
export interface ExecHostLogQueryResponseExtra {
|
||||
refreshed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行状态查询响应
|
||||
*/
|
||||
@@ -74,14 +81,6 @@ export interface ExecLogStatusResponse {
|
||||
hostList: Array<ExecHostLogQueryResponse>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行日志 tail 请求
|
||||
*/
|
||||
export interface ExecLogTailRequest {
|
||||
execId?: number;
|
||||
hostExecIdList?: Array<number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行中断命令请求
|
||||
*/
|
||||
|
||||
@@ -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<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用信息查询响应
|
||||
*/
|
||||
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<SystemSettingAggregateResponse>('/infra/system-setting/setting');
|
||||
return axios.get<Record<keyof SystemSetting, string>>('/infra/system-setting/setting');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,26 +126,26 @@ export function getAppLatestRelease() {
|
||||
* 生成密钥对
|
||||
*/
|
||||
export function generatorKeypair() {
|
||||
return axios.get<EncryptSetting>('/infra/system-setting/generator-keypair');
|
||||
return axios.get<RsaKeyPairResponse>('/infra/system-setting/generator-keypair');
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置-单个
|
||||
*/
|
||||
export function updateSystemSetting(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update', request);
|
||||
return axios.put<string>('/infra/system-setting/update', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统设置-多个
|
||||
*/
|
||||
export function updateSystemSettingBatch(request: SystemSettingUpdateRequest) {
|
||||
return axios.put<number>('/infra/system-setting/update-batch', request);
|
||||
return axios.put<string>('/infra/system-setting/update-batch', request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询系统设置
|
||||
*/
|
||||
export function getSystemSetting<T>(type: SystemSettingType) {
|
||||
return axios.get<T>('/infra/system-setting/get', { params: { type } });
|
||||
export function getSystemSetting(type: string) {
|
||||
return axios.get<Record<keyof SystemSetting, string>>('/infra/system-setting/get', { params: { type } });
|
||||
}
|
||||
|
||||
@@ -65,17 +65,18 @@ export const LogAppenderOptions: ITerminalOptions & ITerminalInitOnlyOptions = {
|
||||
fontFamily: defaultFontFamily,
|
||||
};
|
||||
|
||||
// dom 引用
|
||||
export interface LogDomRef {
|
||||
// append 配置
|
||||
export interface LogAppenderConfig {
|
||||
id: number;
|
||||
el: HTMLElement;
|
||||
openSearch: () => {};
|
||||
type: ExecType;
|
||||
scrollLines: number;
|
||||
}
|
||||
|
||||
// appender 配置
|
||||
export interface LogAppenderConf {
|
||||
// appender 视口
|
||||
export interface LogAppenderView {
|
||||
id: number;
|
||||
el: HTMLElement;
|
||||
opened: boolean;
|
||||
openSearch: () => {};
|
||||
terminal: Terminal;
|
||||
addons: XtermAddons;
|
||||
@@ -84,7 +85,10 @@ export interface LogAppenderConf {
|
||||
// 执行日志 appender 定义
|
||||
export interface ILogAppender {
|
||||
// 初始化
|
||||
init(refs: Array<LogDomRef>): Promise<void>;
|
||||
init(refs: Array<LogAppenderView>): Promise<void>;
|
||||
|
||||
// 打开日志
|
||||
openLog(id: number): void;
|
||||
|
||||
// 设置当前元素
|
||||
setCurrent(id: number): void;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user