✨ 强制下线终端.
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
⚡ 注意: 应用不支持跨版本升级, 可以进行多次升级
|
||||
|
||||
## v1.0.1
|
||||
|
||||
> sql 脚本
|
||||
|
||||
```sql
|
||||
|
||||
```
|
||||
|
||||
## v1.0.0
|
||||
|
||||
> sql 脚本
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
### 连接日志
|
||||
|
||||
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来, 这里默认只展示 `SSH` 连接记录, 可以展开条件进行修改。
|
||||
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来。
|
||||
|
||||
* 详情: 查看连接详情
|
||||
* 断开: 断开连接
|
||||
* 删除: 删除连接记录
|
||||
* 清理: 根据条件清理数据
|
||||
|
||||
@@ -85,4 +85,8 @@ public interface ErrorMessage {
|
||||
|
||||
String FILE_ABSENT = "文件不存在";
|
||||
|
||||
String LOG_ABSENT = "日志不存在";
|
||||
|
||||
String ILLEGAL_STATUS = "当前状态不支持此操作";
|
||||
|
||||
}
|
||||
|
||||
@@ -47,4 +47,12 @@ public interface FieldConst {
|
||||
|
||||
String MOD = "mod";
|
||||
|
||||
String COUNT = "count";
|
||||
|
||||
String LOCATION = "location";
|
||||
|
||||
String USER_AGENT = "userAgent";
|
||||
|
||||
String ERROR_MESSAGE = "errorMessage";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package com.orion.ops.module.asset.controller;
|
||||
|
||||
import com.orion.lang.define.wrapper.DataGrid;
|
||||
import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
|
||||
import com.orion.ops.framework.common.validator.group.Id;
|
||||
import com.orion.ops.framework.common.validator.group.Page;
|
||||
import com.orion.ops.framework.log.core.annotation.IgnoreLog;
|
||||
import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
|
||||
import com.orion.ops.framework.web.core.annotation.RestWrapper;
|
||||
import com.orion.ops.module.asset.define.operator.HostConnectLogOperatorType;
|
||||
import com.orion.ops.module.asset.entity.request.host.HostConnectLogQueryRequest;
|
||||
import com.orion.ops.module.asset.entity.vo.HostConnectLogVO;
|
||||
import com.orion.ops.module.asset.service.HostConnectLogService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
@@ -55,5 +56,36 @@ public class HostConnectLogController {
|
||||
return hostConnectLogService.getLatestConnectHostId(request);
|
||||
}
|
||||
|
||||
@OperatorLog(HostConnectLogOperatorType.DELETE)
|
||||
@DeleteMapping("/delete")
|
||||
@Operation(summary = "删除主机连接日志")
|
||||
@Parameter(name = "idList", description = "idList", required = true)
|
||||
@PreAuthorize("@ss.hasPermission('asset:host-connect-log:management:delete')")
|
||||
public Integer deleteHostConnectLog(@RequestParam("idList") List<Long> idList) {
|
||||
return hostConnectLogService.deleteHostConnectLog(idList);
|
||||
}
|
||||
|
||||
@PostMapping("/query-count")
|
||||
@Operation(summary = "查询主机连接日志数量")
|
||||
public Long getHostConnectLogCount(@RequestBody HostConnectLogQueryRequest request) {
|
||||
return hostConnectLogService.getHostConnectLogCount(request);
|
||||
}
|
||||
|
||||
@OperatorLog(HostConnectLogOperatorType.CLEAR)
|
||||
@PostMapping("/clear")
|
||||
@Operation(summary = "清空主机连接日志")
|
||||
@PreAuthorize("@ss.hasPermission('asset:host-connect-log:management:clear')")
|
||||
public Integer clearHostConnectLog(@RequestBody HostConnectLogQueryRequest request) {
|
||||
return hostConnectLogService.clearHostConnectLog(request);
|
||||
}
|
||||
|
||||
@OperatorLog(HostConnectLogOperatorType.FORCE_OFFLINE)
|
||||
@PutMapping("/force-offline")
|
||||
@Operation(summary = "强制断开主机连接")
|
||||
@PreAuthorize("@ss.hasPermission('asset:host-connect-log:management:force-offline')")
|
||||
public Integer forceOffline(@Validated(Id.class) @RequestBody HostConnectLogQueryRequest request) {
|
||||
return hostConnectLogService.forceOffline(request);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.orion.ops.module.asset.define.operator;
|
||||
|
||||
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.*;
|
||||
|
||||
/**
|
||||
* 主机连接日志 操作日志类型
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/3/2 14:37
|
||||
*/
|
||||
@Module("asset:host-connect-log")
|
||||
public class HostConnectLogOperatorType extends InitializingOperatorTypes {
|
||||
|
||||
public static final String DELETE = "host-connect-log:delete";
|
||||
|
||||
public static final String CLEAR = "host-connect-log:clear";
|
||||
|
||||
public static final String FORCE_OFFLINE = "host-connect-log:force-offline";
|
||||
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(H, DELETE, "删除主机连接记录 <sb>${count}</sb>条"),
|
||||
new OperatorType(H, CLEAR, "清理主机连接记录 <sb>${count}</sb>条"),
|
||||
new OperatorType(M, FORCE_OFFLINE, "强制下线主机连接 <sb>${hostName}</sb>"),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* 主机连接日志推展信息对象
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024-3-12 23:31
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(name = "HostConnectLogExtraDTO", description = "主机连接日志推展信息对象")
|
||||
public class HostConnectLogExtraDTO {
|
||||
|
||||
@Schema(description = "主机 id")
|
||||
private Long hostId;
|
||||
|
||||
@Schema(description = "主机名称")
|
||||
private String hostName;
|
||||
|
||||
@Schema(description = "连接类型")
|
||||
private String connectType;
|
||||
|
||||
@Schema(description = "channelId")
|
||||
private String channelId;
|
||||
|
||||
@Schema(description = "sessionId")
|
||||
private String sessionId;
|
||||
|
||||
@Schema(description = "traceId")
|
||||
private String traceId;
|
||||
|
||||
@Schema(description = "请求地址")
|
||||
private String address;
|
||||
|
||||
@Schema(description = "请求位置")
|
||||
private String location;
|
||||
|
||||
@Schema(description = "ua")
|
||||
private String userAgent;
|
||||
|
||||
@Schema(description = "错误信息")
|
||||
private String errorMessage;
|
||||
|
||||
}
|
||||
@@ -23,6 +23,9 @@ import java.util.Date;
|
||||
@Schema(name = "HostConnectLogQueryRequest", description = "主机连接日志 查询请求对象")
|
||||
public class HostConnectLogQueryRequest extends PageRequest {
|
||||
|
||||
@Schema(description = "id")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户id")
|
||||
private Long userId;
|
||||
|
||||
|
||||
@@ -46,9 +46,6 @@ public class HostConnectLogVO implements Serializable {
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
|
||||
@Schema(description = "token")
|
||||
private String token;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@@ -67,10 +64,4 @@ public class HostConnectLogVO implements Serializable {
|
||||
@Schema(description = "修改时间")
|
||||
private Date updateTime;
|
||||
|
||||
@Schema(description = "创建人")
|
||||
private String creator;
|
||||
|
||||
@Schema(description = "修改人")
|
||||
private String updater;
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,11 @@ public enum HostConnectStatusEnum {
|
||||
*/
|
||||
FAILED,
|
||||
|
||||
/**
|
||||
* 强制下线
|
||||
*/
|
||||
FORCE_OFFLINE,
|
||||
|
||||
;
|
||||
|
||||
public static HostConnectStatusEnum of(String type) {
|
||||
|
||||
@@ -173,7 +173,7 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
|
||||
String username = WebSockets.getAttr(channel, ExtraFieldConst.USERNAME);
|
||||
// 额外参数
|
||||
Map<String, Object> extra = Maps.newMap();
|
||||
extra.put(OperatorLogs.ID, hostId);
|
||||
extra.put(OperatorLogs.HOST_ID, hostId);
|
||||
extra.put(OperatorLogs.HOST_NAME, hostName);
|
||||
extra.put(OperatorLogs.CONNECT_TYPE, connectType.name());
|
||||
extra.put(OperatorLogs.CHANNEL_ID, channel.getId());
|
||||
@@ -194,6 +194,13 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
|
||||
.token(sessionId)
|
||||
.extra(extra)
|
||||
.build();
|
||||
// 填充其他信息
|
||||
extra.put(OperatorLogs.TRACE_ID, logModel.getTraceId());
|
||||
extra.put(OperatorLogs.ADDRESS, logModel.getAddress());
|
||||
extra.put(OperatorLogs.LOCATION, logModel.getLocation());
|
||||
extra.put(OperatorLogs.USER_AGENT, logModel.getUserAgent());
|
||||
extra.put(OperatorLogs.ERROR_MESSAGE, logModel.getErrorMessage());
|
||||
// 记录连接日志
|
||||
hostConnectLogService.create(connectType, connectLog);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,4 +38,9 @@ public interface ITerminalSession extends SafeCloseable {
|
||||
*/
|
||||
void keepAlive();
|
||||
|
||||
/**
|
||||
* 强制下线
|
||||
*/
|
||||
void forceOffline();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.orion.ops.module.asset.handler.host.terminal.session;
|
||||
|
||||
import com.orion.lang.utils.ansi.AnsiAppender;
|
||||
import com.orion.lang.utils.ansi.style.color.AnsiForeground;
|
||||
import com.orion.lang.utils.io.Streams;
|
||||
import com.orion.net.host.SessionStore;
|
||||
import com.orion.net.host.ssh.shell.ShellExecutor;
|
||||
@@ -97,6 +99,26 @@ public class SshSession extends TerminalSession implements ISshSession {
|
||||
Streams.close(sessionStore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceOffline() {
|
||||
if (this.close) {
|
||||
return;
|
||||
}
|
||||
// 发送消息
|
||||
String body = AnsiAppender.create()
|
||||
.newLine()
|
||||
.append(AnsiForeground.BRIGHT_RED, TerminalMessage.FORCED_OFFLINE)
|
||||
.toString();
|
||||
SshOutputResponse resp = SshOutputResponse.builder()
|
||||
.type(OutputTypeEnum.SSH_OUTPUT.getType())
|
||||
.sessionId(sessionId)
|
||||
.body(body)
|
||||
.build();
|
||||
WebSockets.sendText(channel, OutputTypeEnum.SSH_OUTPUT.format(resp));
|
||||
// 强制下线
|
||||
super.forceOffline();
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准输出处理
|
||||
*
|
||||
|
||||
@@ -41,9 +41,27 @@ public abstract class TerminalSession implements ITerminalSession {
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// 检查并且关闭
|
||||
if (this.checkAndClose()) {
|
||||
// 修改状态
|
||||
SpringHolder.getBean(HostConnectLogService.class).updateStatusByToken(sessionId, HostConnectStatusEnum.COMPLETE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceOffline() {
|
||||
this.checkAndClose();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并且关闭会话
|
||||
*
|
||||
* @return close
|
||||
*/
|
||||
private boolean checkAndClose() {
|
||||
log.info("terminal close {}", sessionId);
|
||||
if (close) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
this.close = true;
|
||||
// 释放资源
|
||||
@@ -52,8 +70,7 @@ public abstract class TerminalSession implements ITerminalSession {
|
||||
} catch (Exception e) {
|
||||
log.error("terminal release error {}", sessionId, e);
|
||||
}
|
||||
// 修改状态
|
||||
SpringHolder.getBean(HostConnectLogService.class).updateStatusByToken(sessionId, HostConnectStatusEnum.COMPLETE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -40,8 +40,9 @@ public interface HostConnectLogService {
|
||||
*
|
||||
* @param token token
|
||||
* @param status status
|
||||
* @return effect
|
||||
*/
|
||||
void updateStatusByToken(String token, HostConnectStatusEnum status);
|
||||
Integer updateStatusByToken(String token, HostConnectStatusEnum status);
|
||||
|
||||
/**
|
||||
* 查询用户最近连接的主机
|
||||
@@ -60,4 +61,36 @@ public interface HostConnectLogService {
|
||||
*/
|
||||
Future<List<Long>> getLatestConnectHostIdAsync(HostConnectTypeEnum type, Long userId);
|
||||
|
||||
/**
|
||||
* 删除主机连接日志
|
||||
*
|
||||
* @param idList idList
|
||||
* @return effect
|
||||
*/
|
||||
Integer deleteHostConnectLog(List<Long> idList);
|
||||
|
||||
/**
|
||||
* 获取主机连接日志数量
|
||||
*
|
||||
* @param request request
|
||||
* @return count
|
||||
*/
|
||||
Long getHostConnectLogCount(HostConnectLogQueryRequest request);
|
||||
|
||||
/**
|
||||
* 清理主机连接日志
|
||||
*
|
||||
* @param request request
|
||||
* @return effect
|
||||
*/
|
||||
Integer clearHostConnectLog(HostConnectLogQueryRequest request);
|
||||
|
||||
/**
|
||||
* 强制断开主机连接
|
||||
*
|
||||
* @param request request
|
||||
* @return effect
|
||||
*/
|
||||
Integer forceOffline(HostConnectLogQueryRequest request);
|
||||
|
||||
}
|
||||
|
||||
@@ -5,16 +5,22 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.orion.lang.constant.Const;
|
||||
import com.orion.lang.define.wrapper.DataGrid;
|
||||
import com.orion.lang.utils.Arrays1;
|
||||
import com.orion.lang.utils.Valid;
|
||||
import com.orion.ops.framework.biz.operator.log.core.utils.OperatorLogs;
|
||||
import com.orion.ops.framework.common.constant.ErrorMessage;
|
||||
import com.orion.ops.framework.mybatis.core.query.Conditions;
|
||||
import com.orion.ops.framework.security.core.utils.SecurityUtils;
|
||||
import com.orion.ops.module.asset.convert.HostConnectLogConvert;
|
||||
import com.orion.ops.module.asset.dao.HostConnectLogDAO;
|
||||
import com.orion.ops.module.asset.entity.domain.HostConnectLogDO;
|
||||
import com.orion.ops.module.asset.entity.dto.HostConnectLogExtraDTO;
|
||||
import com.orion.ops.module.asset.entity.request.host.HostConnectLogCreateRequest;
|
||||
import com.orion.ops.module.asset.entity.request.host.HostConnectLogQueryRequest;
|
||||
import com.orion.ops.module.asset.entity.vo.HostConnectLogVO;
|
||||
import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
|
||||
import com.orion.ops.module.asset.enums.HostConnectTypeEnum;
|
||||
import com.orion.ops.module.asset.handler.host.terminal.manager.TerminalManager;
|
||||
import com.orion.ops.module.asset.handler.host.terminal.session.ITerminalSession;
|
||||
import com.orion.ops.module.asset.service.HostConnectLogService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@@ -40,6 +46,9 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
||||
@Resource
|
||||
private HostConnectLogDAO hostConnectLogDAO;
|
||||
|
||||
@Resource
|
||||
private TerminalManager terminalManager;
|
||||
|
||||
@Override
|
||||
public void create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) {
|
||||
HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request);
|
||||
@@ -66,12 +75,12 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStatusByToken(String token, HostConnectStatusEnum status) {
|
||||
public Integer updateStatusByToken(String token, HostConnectStatusEnum status) {
|
||||
log.info("HostConnectLogService-updateStatusByToken token: {}, status: {}", token, status);
|
||||
HostConnectLogDO update = new HostConnectLogDO();
|
||||
update.setStatus(status.name());
|
||||
update.setEndTime(new Date());
|
||||
hostConnectLogDAO.update(update, Conditions.eq(HostConnectLogDO::getToken, token));
|
||||
return hostConnectLogDAO.update(update, Conditions.eq(HostConnectLogDO::getToken, token));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,6 +95,55 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
||||
return CompletableFuture.completedFuture(hostIdList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer deleteHostConnectLog(List<Long> idList) {
|
||||
// TODO 测试一下参数
|
||||
log.info("HostConnectLogService.deleteHostConnectLog start {}", JSON.toJSONString(idList));
|
||||
int effect = hostConnectLogDAO.deleteBatchIds(idList);
|
||||
log.info("HostConnectLogService.deleteHostConnectLog finish {}", effect);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.COUNT, effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getHostConnectLogCount(HostConnectLogQueryRequest request) {
|
||||
return hostConnectLogDAO.selectCount(this.buildQueryWrapper(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer clearHostConnectLog(HostConnectLogQueryRequest request) {
|
||||
// TODO 测试一下参数
|
||||
log.info("HostConnectLogService.clearHostConnectLog start {}", JSON.toJSONString(request));
|
||||
// 删除
|
||||
LambdaQueryWrapper<HostConnectLogDO> wrapper = this.buildQueryWrapper(request);
|
||||
int effect = hostConnectLogDAO.delete(wrapper);
|
||||
log.info("HostConnectLogService.clearHostConnectLog finish {}", effect);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.COUNT, effect);
|
||||
return effect;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer forceOffline(HostConnectLogQueryRequest request) {
|
||||
Long id = request.getId();
|
||||
// 查询数据是否存在
|
||||
HostConnectLogDO connect = hostConnectLogDAO.selectById(id);
|
||||
Valid.notNull(connect, ErrorMessage.LOG_ABSENT);
|
||||
Valid.eq(connect.getStatus(), HostConnectStatusEnum.CONNECTING.name(), ErrorMessage.ILLEGAL_STATUS);
|
||||
// 设置日志参数
|
||||
OperatorLogs.add(OperatorLogs.HOST_NAME, connect.getHostName());
|
||||
// 获取会话
|
||||
HostConnectLogExtraDTO extra = JSON.parseObject(connect.getExtraInfo(), HostConnectLogExtraDTO.class);
|
||||
ITerminalSession session = terminalManager.getSession(extra.getChannelId(), extra.getSessionId());
|
||||
if (session != null) {
|
||||
// 关闭会话
|
||||
session.forceOffline();
|
||||
}
|
||||
// 更新状态
|
||||
return this.updateStatusByToken(connect.getToken(), HostConnectStatusEnum.FORCE_OFFLINE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建查询 wrapper
|
||||
*
|
||||
@@ -94,6 +152,7 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
||||
*/
|
||||
private LambdaQueryWrapper<HostConnectLogDO> buildQueryWrapper(HostConnectLogQueryRequest request) {
|
||||
return hostConnectLogDAO.wrapper()
|
||||
.eq(HostConnectLogDO::getId, request.getId())
|
||||
.eq(HostConnectLogDO::getUserId, request.getUserId())
|
||||
.eq(HostConnectLogDO::getHostId, request.getHostId())
|
||||
.like(HostConnectLogDO::getHostAddress, request.getHostAddress())
|
||||
|
||||
Reference in New Issue
Block a user