🔨 查看执行日志.
This commit is contained in:
@@ -31,4 +31,6 @@ public interface Const extends com.orion.lang.constant.Const, FieldConst, CnCons
|
||||
|
||||
Integer DEFAULT_VERSION = 1;
|
||||
|
||||
String ERROR_LOG = "error.log";
|
||||
|
||||
}
|
||||
|
||||
@@ -89,4 +89,6 @@ public interface ErrorMessage {
|
||||
|
||||
String CHECK_AUTHORIZED_HOST = "请选择已授权的主机";
|
||||
|
||||
String FILE_READ_ERROR = "文件读取失败";
|
||||
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ Authorization: {{token}}
|
||||
{
|
||||
"description": 1,
|
||||
"timeout": 10,
|
||||
"command": "echo @{{ hostAddress }}\nsleep 10\necho @{{ str }}",
|
||||
"parameter": "{\"str\":\"end\"}",
|
||||
"command": "echo 这是日志@{{ hostAddress }}\nsleep 1\necho @{{ hostName }}",
|
||||
"parameterSchema": "[]",
|
||||
"hostIdList": [1,7]
|
||||
}
|
||||
|
||||
@@ -22,4 +22,19 @@ Authorization: {{token}}
|
||||
}
|
||||
|
||||
|
||||
### 查看执行日志
|
||||
POST {{baseUrl}}/asset/exec/tail-log
|
||||
Content-Type: application/json
|
||||
Authorization: {{token}}
|
||||
|
||||
{
|
||||
"execId": 1
|
||||
}
|
||||
|
||||
|
||||
### 下载执行日志文件
|
||||
GET {{baseUrl}}/asset/exec/download-log?id=83
|
||||
Authorization: {{token}}
|
||||
|
||||
|
||||
###
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.orion.ops.framework.web.core.annotation.RestWrapper;
|
||||
import com.orion.ops.module.asset.define.operator.ExecOperatorType;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecInterruptRequest;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ReExecCommandRequest;
|
||||
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
|
||||
import com.orion.ops.module.asset.service.ExecService;
|
||||
@@ -18,6 +19,7 @@ import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 批量执行
|
||||
@@ -74,6 +76,19 @@ public class ExecController {
|
||||
return HttpWrapper.ok();
|
||||
}
|
||||
|
||||
// TODO tail log
|
||||
@PostMapping("/tail-log")
|
||||
@Operation(summary = "查看批量执行日志")
|
||||
@PreAuthorize("@ss.hasAnyPermission('asset:exec:exec-command', 'asset:exec-log:query')")
|
||||
public String getExecLogTailToken(@Validated @RequestBody ExecLogTailRequest request) {
|
||||
return execService.getExecLogTailToken(request);
|
||||
}
|
||||
|
||||
@OperatorLog(ExecOperatorType.DOWNLOAD_HOST_LOG)
|
||||
@GetMapping("/download-log")
|
||||
@Operation(summary = "下载执行日志文件")
|
||||
@PreAuthorize("@ss.hasAnyPermission('asset:exec:exec-command', 'asset:exec-log:query')")
|
||||
public void downloadLogFile(@RequestParam("id") Long id, HttpServletResponse response) {
|
||||
execService.downloadLogFile(id, response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.orion.ops.module.asset.define.cache;
|
||||
|
||||
import com.orion.lang.define.cache.key.CacheKeyBuilder;
|
||||
import com.orion.lang.define.cache.key.CacheKeyDefine;
|
||||
import com.orion.lang.define.cache.key.struct.RedisCacheStruct;
|
||||
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 命令执行服务缓存 key
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/18 16:31
|
||||
*/
|
||||
public interface ExecCacheKeyDefine {
|
||||
|
||||
CacheKeyDefine EXEC_TAIL = new CacheKeyBuilder()
|
||||
.key("exec:tail:{}")
|
||||
.desc("命令执行日志查看 ${token}")
|
||||
.type(ExecLogTailDTO.class)
|
||||
.struct(RedisCacheStruct.STRING)
|
||||
.timeout(5, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
}
|
||||
@@ -4,8 +4,7 @@ import com.orion.ops.framework.biz.operator.log.core.annotation.Module;
|
||||
import com.orion.ops.framework.biz.operator.log.core.factory.InitializingOperatorTypes;
|
||||
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
|
||||
|
||||
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.H;
|
||||
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.M;
|
||||
import static com.orion.ops.framework.biz.operator.log.core.enums.OperatorRiskLevel.*;
|
||||
|
||||
/**
|
||||
* 批量执行 操作记录类型
|
||||
@@ -23,21 +22,24 @@ public class ExecOperatorType extends InitializingOperatorTypes {
|
||||
|
||||
public static final String INTERRUPT_HOST = "exec:interrupt-host";
|
||||
|
||||
public static final String DELETE_HOST_LOG = "exec:delete-host-log";
|
||||
|
||||
public static final String DELETE_LOG = "exec:delete-log";
|
||||
|
||||
public static final String CLEAR_LOG = "exec:clear-log";
|
||||
|
||||
public static final String DELETE_HOST_LOG = "exec:delete-host-log";
|
||||
|
||||
public static final String DOWNLOAD_HOST_LOG = "exec:download-host-log";
|
||||
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(M, EXEC_COMMAND, "执行主机命令"),
|
||||
new OperatorType(M, INTERRUPT_EXEC, "中断执行命令"),
|
||||
new OperatorType(M, INTERRUPT_HOST, "中断主机执行命令 ${logId} ${hostName}"),
|
||||
new OperatorType(H, DELETE_HOST_LOG, "删除主机执行记录 ${logId} ${hostName}"),
|
||||
new OperatorType(H, DELETE_LOG, "删除执行记录 ${count} 条"),
|
||||
new OperatorType(H, CLEAR_LOG, "清理执行记录 ${count} 条"),
|
||||
new OperatorType(H, DELETE_HOST_LOG, "删除主机执行记录 ${logId} ${hostName}"),
|
||||
new OperatorType(L, DOWNLOAD_HOST_LOG, "下载主机执行日志 ${logId} ${hostName}"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.orion.ops.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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.orion.ops.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;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 执行日志查看 缓存对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/18 16:34
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "ExecLogTailDTO", description = "执行日志查看 缓存对象")
|
||||
public class ExecLogTailDTO implements Serializable {
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
@Schema(description = "token")
|
||||
private String token;
|
||||
|
||||
@Schema(description = "执行主机")
|
||||
private List<ExecHostLogTailDTO> hosts;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.orion.ops.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 {
|
||||
|
||||
@NotNull
|
||||
@Schema(description = "执行id")
|
||||
private Long execId;
|
||||
|
||||
@Schema(description = "执行主机id")
|
||||
private List<Long> execHostIdList;
|
||||
|
||||
}
|
||||
@@ -51,7 +51,6 @@ public class TerminalAccessInterceptor implements HandshakeInterceptor {
|
||||
|
||||
@Override
|
||||
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
|
||||
log.info("afterHandshake");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package com.orion.ops.module.asset.service;
|
||||
|
||||
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 批量执行服务
|
||||
*
|
||||
@@ -42,4 +46,28 @@ public interface ExecService {
|
||||
*/
|
||||
void interruptHostExec(Long hostLogId);
|
||||
|
||||
/**
|
||||
* 查看执行日志
|
||||
*
|
||||
* @param request request
|
||||
* @return token
|
||||
*/
|
||||
String getExecLogTailToken(ExecLogTailRequest request);
|
||||
|
||||
/**
|
||||
* 获取查看执行日志参数
|
||||
*
|
||||
* @param token token
|
||||
* @return log
|
||||
*/
|
||||
ExecLogTailDTO getExecLogTailInfo(String token);
|
||||
|
||||
/**
|
||||
* 下载执行日志文件
|
||||
*
|
||||
* @param id id
|
||||
* @param response response
|
||||
*/
|
||||
void downloadLogFile(Long id, HttpServletResponse response);
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@ package com.orion.ops.module.asset.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.orion.lang.exception.argument.InvalidArgumentException;
|
||||
import com.orion.lang.function.Functions;
|
||||
import com.orion.lang.id.UUIds;
|
||||
import com.orion.lang.utils.Strings;
|
||||
import com.orion.lang.utils.collect.Lists;
|
||||
import com.orion.lang.utils.collect.Maps;
|
||||
import com.orion.lang.utils.io.Files1;
|
||||
import com.orion.lang.utils.json.matcher.NoMatchStrategy;
|
||||
import com.orion.lang.utils.json.matcher.ReplacementFormatter;
|
||||
import com.orion.lang.utils.json.matcher.ReplacementFormatters;
|
||||
@@ -17,15 +19,20 @@ import com.orion.ops.framework.common.constant.ErrorMessage;
|
||||
import com.orion.ops.framework.common.file.FileClient;
|
||||
import com.orion.ops.framework.common.security.LoginUser;
|
||||
import com.orion.ops.framework.common.utils.Valid;
|
||||
import com.orion.ops.framework.redis.core.utils.RedisStrings;
|
||||
import com.orion.ops.framework.security.core.utils.SecurityUtils;
|
||||
import com.orion.ops.module.asset.dao.ExecHostLogDAO;
|
||||
import com.orion.ops.module.asset.dao.ExecLogDAO;
|
||||
import com.orion.ops.module.asset.dao.HostDAO;
|
||||
import com.orion.ops.module.asset.define.cache.ExecCacheKeyDefine;
|
||||
import com.orion.ops.module.asset.entity.domain.ExecHostLogDO;
|
||||
import com.orion.ops.module.asset.entity.domain.ExecLogDO;
|
||||
import com.orion.ops.module.asset.entity.domain.HostDO;
|
||||
import com.orion.ops.module.asset.entity.dto.ExecHostLogTailDTO;
|
||||
import com.orion.ops.module.asset.entity.dto.ExecLogTailDTO;
|
||||
import com.orion.ops.module.asset.entity.dto.ExecParameterSchemaDTO;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecCommandRequest;
|
||||
import com.orion.ops.module.asset.entity.request.exec.ExecLogTailRequest;
|
||||
import com.orion.ops.module.asset.entity.vo.ExecCommandHostVO;
|
||||
import com.orion.ops.module.asset.entity.vo.ExecCommandVO;
|
||||
import com.orion.ops.module.asset.enums.ExecHostStatusEnum;
|
||||
@@ -40,11 +47,14 @@ import com.orion.ops.module.asset.handler.host.exec.handler.IExecTaskHandler;
|
||||
import com.orion.ops.module.asset.handler.host.exec.manager.ExecManager;
|
||||
import com.orion.ops.module.asset.service.AssetAuthorizedDataService;
|
||||
import com.orion.ops.module.asset.service.ExecService;
|
||||
import com.orion.web.servlet.web.Servlets;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -275,6 +285,92 @@ public class ExecServiceImpl implements ExecService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getExecLogTailToken(ExecLogTailRequest request) {
|
||||
Long execId = request.getExecId();
|
||||
List<Long> execHostIdList = request.getExecHostIdList();
|
||||
log.info("ExecService.getExecLogTailToken start execId: {}, execHostIdList: {}", execId, execHostIdList);
|
||||
// 查询执行日志
|
||||
ExecLogDO execLog = execLogDAO.selectById(execId);
|
||||
Valid.notNull(execLog, ErrorMessage.LOG_ABSENT);
|
||||
// 查询主机日志
|
||||
List<ExecHostLogDO> hostLogs;
|
||||
if (execHostIdList == null) {
|
||||
hostLogs = execHostLogDAO.selectByLogId(execId);
|
||||
} else {
|
||||
hostLogs = execHostLogDAO.of()
|
||||
.createWrapper()
|
||||
.eq(ExecHostLogDO::getLogId, execId)
|
||||
.in(ExecHostLogDO::getId, execHostIdList)
|
||||
.then()
|
||||
.list();
|
||||
}
|
||||
Valid.notEmpty(hostLogs, ErrorMessage.LOG_ABSENT);
|
||||
// 生成缓存
|
||||
String token = UUIds.random19();
|
||||
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
|
||||
ExecLogTailDTO cache = ExecLogTailDTO.builder()
|
||||
.token(token)
|
||||
.id(execId)
|
||||
.userId(SecurityUtils.getLoginUserId())
|
||||
.hosts(hostLogs.stream()
|
||||
.map(s -> ExecHostLogTailDTO.builder()
|
||||
.id(s.getId())
|
||||
.hostId(s.getHostId())
|
||||
.path(s.getLogPath())
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
// 设置缓存
|
||||
RedisStrings.setJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL, cache);
|
||||
log.info("ExecService.getExecLogTailToken finish token: {}, execId: {}, execHostIdList: {}", token, execId, execHostIdList);
|
||||
return token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecLogTailDTO getExecLogTailInfo(String token) {
|
||||
String cacheKey = ExecCacheKeyDefine.EXEC_TAIL.format(token);
|
||||
// 获取缓存
|
||||
ExecLogTailDTO tail = RedisStrings.getJson(cacheKey, ExecCacheKeyDefine.EXEC_TAIL);
|
||||
if (tail != null) {
|
||||
// 删除缓存
|
||||
RedisStrings.delete(cacheKey);
|
||||
}
|
||||
return tail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void downloadLogFile(Long id, HttpServletResponse response) {
|
||||
log.info("ExecService.downloadLogFile id: {}", id);
|
||||
try {
|
||||
// 获取主机执行日志
|
||||
ExecHostLogDO hostLog = execHostLogDAO.selectById(id);
|
||||
Valid.notNull(hostLog, ErrorMessage.LOG_ABSENT);
|
||||
String logPath = hostLog.getLogPath();
|
||||
Valid.notNull(logPath, ErrorMessage.LOG_ABSENT);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.LOG_ID, hostLog.getLogId());
|
||||
OperatorLogs.add(OperatorLogs.HOST_ID, hostLog.getHostId());
|
||||
OperatorLogs.add(OperatorLogs.HOST_NAME, hostLog.getHostName());
|
||||
// 获取日志
|
||||
InputStream in = logsFileClient.getContentInputStream(logPath);
|
||||
// 返回
|
||||
Servlets.transfer(response, in, Files1.getFileName(logPath));
|
||||
} catch (Exception e) {
|
||||
log.error("ExecService.downloadLogFile error id: {}", id, e);
|
||||
String errorMessage = ErrorMessage.FILE_READ_ERROR;
|
||||
if (e instanceof InvalidArgumentException) {
|
||||
errorMessage = e.getMessage();
|
||||
}
|
||||
// 响应错误信息
|
||||
try {
|
||||
Servlets.transfer(response, Strings.bytes(errorMessage), Const.ERROR_LOG);
|
||||
} catch (Exception ex) {
|
||||
log.error("ExecService.downloadLogFile transfer-error id: {}", id, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始执行命令
|
||||
*
|
||||
@@ -306,7 +402,7 @@ public class ExecServiceImpl implements ExecService {
|
||||
* @return logPath
|
||||
*/
|
||||
private String buildLogPath(Long logId, Long hostId) {
|
||||
String logFile = "/exec/" + logId + "/" + hostId + ".log";
|
||||
String logFile = "/exec/" + logId + "/" + logId + "_" + hostId + ".log";
|
||||
return logsFileClient.getReturnPath(logFile);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ public class HostSftpLogServiceImpl implements HostSftpLogService {
|
||||
vo.setExtra(extra);
|
||||
return vo;
|
||||
}).collect(Collectors.toList());
|
||||
// 返回
|
||||
// 返回 TODO KIT
|
||||
DataGrid<HostSftpLogVO> result = new DataGrid<>(rows, dataGrid.getTotal());
|
||||
result.setPage(dataGrid.getPage());
|
||||
result.setLimit(dataGrid.getLimit());
|
||||
|
||||
Reference in New Issue
Block a user