强制下线终端.

This commit is contained in:
lijiahangmax
2024-03-03 00:24:00 +08:00
parent f1d14b4a12
commit b424dd02db
16 changed files with 307 additions and 21 deletions

View File

@@ -1,5 +1,13 @@
⚡ 注意: 应用不支持跨版本升级, 可以进行多次升级
## v1.0.1
> sql 脚本
```sql
```
## v1.0.0
> sql 脚本

View File

@@ -1,3 +1,8 @@
### 连接日志
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来, 这里默认只展示 `SSH` 连接记录, 可以展开条件进行修改。
在主机终端页面打开的 `SSH` `SFTP` 连接都会记录下来
* 详情: 查看连接详情
* 断开: 断开连接
* 删除: 删除连接记录
* 清理: 根据条件清理数据

View File

@@ -85,4 +85,8 @@ public interface ErrorMessage {
String FILE_ABSENT = "文件不存在";
String LOG_ABSENT = "日志不存在";
String ILLEGAL_STATUS = "当前状态不支持此操作";
}

View File

@@ -47,4 +47,12 @@ public interface FieldConst {
String MOD = "mod";
String COUNT = "count";
String LOCATION = "location";
String USER_AGENT = "userAgent";
String ERROR_MESSAGE = "errorMessage";
}

View File

@@ -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);
}
}

View File

@@ -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>"),
};
}
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -24,6 +24,11 @@ public enum HostConnectStatusEnum {
*/
FAILED,
/**
* 强制下线
*/
FORCE_OFFLINE,
;
public static HostConnectStatusEnum of(String type) {

View File

@@ -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);
}

View File

@@ -38,4 +38,9 @@ public interface ITerminalSession extends SafeCloseable {
*/
void keepAlive();
/**
* 强制下线
*/
void forceOffline();
}

View File

@@ -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();
}
/**
* 标准输出处理
*

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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())