From 63f458ac1a2e348c97a34593b5774421d4574cdf Mon Sep 17 00:00:00 2001 From: lijiahang Date: Wed, 3 Jan 2024 19:54:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=BF=9E=E6=8E=A5=E4=B8=BB=E6=9C=BA?= =?UTF-8?q?=E7=BB=88=E7=AB=AF.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constant/ErrorMessage.java | 2 + .../common/constant/ExtraFieldConst.java | 2 + .../framework/common/enums/BooleanBit.java | 4 + .../websocket/core/constant/WsCloseCode.java | 151 +--------------- .../core/constant/WsCloseReason.java | 30 --- .../websocket/core/utils/WebSockets.java | 60 ++---- .../entity/dto/HostTerminalAccessDTO.java | 3 - .../entity/dto/HostTerminalConnectDTO.java | 12 +- .../host/HostConnectLogCreateRequest.java | 7 + .../host/config/model/HostSshConfigModel.java | 10 + .../strategy/HostSshConfigStrategy.java | 4 + .../terminal/TerminalMessageDispatcher.java | 20 +- .../terminal/constant/TerminalMessage.java | 22 +++ .../entity/request/TerminalCheckRequest.java | 30 +++ .../request/TerminalConnectRequest.java | 8 +- .../entity/request/TerminalResizeRequest.java | 34 ++++ .../response/TerminalCheckResponse.java | 36 ++++ .../response/TerminalConnectResponse.java | 10 +- .../terminal/enums/InputOperatorTypeEnum.java | 6 + .../enums/OutputOperatorTypeEnum.java | 8 +- .../handler/AbstractTerminalHandler.java | 42 +++++ .../handler/TerminalCheckHandler.java | 155 ++++++++++++++++ .../handler/TerminalConnectHandler.java | 171 +++++++++--------- .../terminal/session/TerminalSession.java | 19 +- .../impl/HostConnectLogServiceImpl.java | 9 +- .../service/impl/HostTerminalServiceImpl.java | 3 +- .../components/config/ssh/ssh-config-form.vue | 2 +- 27 files changed, 518 insertions(+), 342 deletions(-) delete mode 100644 orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseReason.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/constant/TerminalMessage.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/entity/request/TerminalCheckRequest.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/entity/request/TerminalResizeRequest.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/entity/response/TerminalCheckResponse.java create mode 100644 orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalCheckHandler.java diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorMessage.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorMessage.java index 7808d812..5852b1af 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorMessage.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ErrorMessage.java @@ -71,4 +71,6 @@ public interface ErrorMessage { String ANY_NO_PERMISSION = "{}无权限"; + String SESSION_ABSENT = "会话不存在"; + } diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ExtraFieldConst.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ExtraFieldConst.java index e93f7579..690c3192 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ExtraFieldConst.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/constant/ExtraFieldConst.java @@ -13,6 +13,8 @@ public interface ExtraFieldConst extends FieldConst { String TRACE_ID = "traceId"; + String SESSION_ID = "sessionId"; + String IDENTITY = "identity"; String GROUP_NAME = "groupName"; diff --git a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/enums/BooleanBit.java b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/enums/BooleanBit.java index f4f452c9..ba3a296b 100644 --- a/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/enums/BooleanBit.java +++ b/orion-ops-framework/orion-ops-framework-common/src/main/java/com/orion/ops/framework/common/enums/BooleanBit.java @@ -28,6 +28,10 @@ public enum BooleanBit { private final Integer value; + public static BooleanBit of(boolean value) { + return value ? TRUE : FALSE; + } + public static BooleanBit of(Integer value) { if (value == null) { return null; diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseCode.java b/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseCode.java index 3f950cb2..00d0a14e 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseCode.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseCode.java @@ -1,11 +1,5 @@ package com.orion.ops.framework.websocket.core.constant; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.socket.CloseStatus; -import org.springframework.web.socket.WebSocketSession; - /** * ws 服务端关闭 code * @@ -13,145 +7,20 @@ import org.springframework.web.socket.WebSocketSession; * @version 1.0.0 * @since 2021/6/16 15:18 */ -@Slf4j -@Getter -@AllArgsConstructor -public enum WsCloseCode { +public interface WsCloseCode { /** - * 未查询到token - */ - INCORRECT_TOKEN(4100, WsCloseReason.CLOSED_CONNECTION), - - /** - * 伪造token - */ - FORGE_TOKEN(4120, WsCloseReason.CLOSED_CONNECTION), - - /** - * token已被绑定 - */ - TOKEN_BIND(4125, WsCloseReason.CLOSED_CONNECTION), - - /** - * 未知的连接 - */ - UNKNOWN_CONNECT(4130, WsCloseReason.CLOSED_CONNECTION), - - /** - * 认证失败 id不匹配 - */ - IDENTITY_MISMATCH(4140, WsCloseReason.IDENTITY_MISMATCH), - - /** - * 认证信息不匹配 - */ - VALID(4150, WsCloseReason.AUTHENTICATION_FAILURE), - - /** - * 主机不合法 - */ - INVALID_HOST(4200, WsCloseReason.CLOSED_CONNECTION), - - /** - * 连接远程服务器连接超时 - */ - CONNECTION_TIMEOUT(4201, WsCloseReason.CONNECTION_TIMEOUT), - - /** - * 连接远程服务器失败 - */ - CONNECTION_FAILURE(4202, WsCloseReason.REMOTE_SERVER_UNREACHABLE), - - /** - * 远程服务器认证失败 - */ - CONNECTION_AUTH_FAILURE(4205, WsCloseReason.REMOTE_SERVER_AUTHENTICATION_FAILURE), - - /** - * 远程服务器认证出现异常 - */ - CONNECTION_EXCEPTION(4210, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), - - /** - * 主机未启用 - */ - HOST_DISABLED(4215, WsCloseReason.HOST_DISABLED), - - /** - * 打开shell出现异常 - */ - OPEN_SHELL_EXCEPTION(4220, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), - - /** - * 打开command出现异常 - */ - OPEN_COMMAND_EXCEPTION(4225, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), - - /** - * 打开sftp出现异常 - */ - OPEN_SFTP_EXCEPTION(4230, WsCloseReason.UNABLE_TO_CONNECT_REMOTE_SERVER), - - /** - * 服务出现异常 - */ - RUNTIME_EXCEPTION(4300, WsCloseReason.CLOSED_CONNECTION), - - /** - * 心跳结束 - */ - HEART_DOWN(4310, WsCloseReason.CLOSED_CONNECTION), - - /** - * 用户关闭 - */ - DISCONNECT(4320, WsCloseReason.CLOSED_CONNECTION), - - /** - * 结束 - */ - EOF(4330, WsCloseReason.CLOSED_CONNECTION), - - /** - * 读取失败 - */ - READ_EXCEPTION(4335, WsCloseReason.CLOSED_CONNECTION), - - /** - * 强制下线 - */ - FORCED_OFFLINE(4500, WsCloseReason.FORCED_OFFLINE), - - ; - - private final int code; - - private final String reason; - - /** - * 关闭会话 + * code * - * @param session session + * @return code */ - public void close(WebSocketSession session) { - if (!session.isOpen()) { - return; - } - try { - session.close(new CloseStatus(code, reason)); - } catch (Exception e) { - log.error("websocket close failure", e); - } - } + int getCode(); - public static WsCloseCode of(int code) { - for (WsCloseCode value : values()) { - if (value.code == code) { - return value; - } - } - return null; - } + /** + * reason + * + * @return reason + */ + String getReason(); } diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseReason.java b/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseReason.java deleted file mode 100644 index a7c8ae8b..00000000 --- a/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/constant/WsCloseReason.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.orion.ops.framework.websocket.core.constant; - -/** - * ws服务端关闭reason - * - * @author Jiahang Li - * @version 1.0.0 - * @since 2021/6/16 15:21 - */ -public interface WsCloseReason { - - String CLOSED_CONNECTION = "closed connection..."; - - String IDENTITY_MISMATCH = "identity mismatch..."; - - String AUTHENTICATION_FAILURE = "authentication failure..."; - - String REMOTE_SERVER_UNREACHABLE = "remote server unreachable..."; - - String CONNECTION_TIMEOUT = "connection timeout..."; - - String REMOTE_SERVER_AUTHENTICATION_FAILURE = "remote server authentication failure..."; - - String HOST_DISABLED = "host disabled..."; - - String UNABLE_TO_CONNECT_REMOTE_SERVER = "unable to connect remote server..."; - - String FORCED_OFFLINE = "forced offline..."; - -} diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/utils/WebSockets.java b/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/utils/WebSockets.java index 7ced55a4..11e97fe1 100644 --- a/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/utils/WebSockets.java +++ b/orion-ops-framework/orion-ops-spring-boot-starter-websocket/src/main/java/com/orion/ops/framework/websocket/core/utils/WebSockets.java @@ -1,20 +1,14 @@ package com.orion.ops.framework.websocket.core.utils; -import com.orion.lang.exception.AuthenticationException; -import com.orion.lang.exception.ConnectionRuntimeException; -import com.orion.lang.exception.DisabledException; -import com.orion.lang.exception.TimeoutException; +import com.alibaba.fastjson.JSON; import com.orion.lang.utils.Exceptions; -import com.orion.lang.utils.Urls; import com.orion.ops.framework.websocket.core.constant.WsCloseCode; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.server.ServerHttpRequest; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import java.io.IOException; -import java.util.Objects; /** * websocket 工具类 @@ -35,7 +29,17 @@ public class WebSockets { * @param session session * @param message message */ - public static void sendText(WebSocketSession session, byte[] message) { + public static void sendJson(WebSocketSession session, Object message) { + sendText(session, JSON.toJSONString(message)); + } + + /** + * 发送消息 忽略并发报错 + * + * @param session session + * @param message message + */ + public static void sendText(WebSocketSession session, String message) { if (!session.isOpen()) { return; } @@ -67,44 +71,4 @@ public class WebSockets { } } - /** - * 获取 urlToken - * - * @param request request - * @return token - */ - public static String getToken(ServerHttpRequest request) { - return Urls.getUrlSource(Objects.requireNonNull(request.getURI().toString())); - } - - /** - * 获取 urlToken - * - * @param session session - * @return token - */ - public static String getToken(WebSocketSession session) { - return Urls.getUrlSource(Objects.requireNonNull(session.getUri()).toString()); - } - - /** - * 打开 session 异常关闭 - * - * @param session session - * @param e e - */ - public static void openSessionStoreThrowClose(WebSocketSession session, Exception e) { - if (Exceptions.isCausedBy(e, TimeoutException.class)) { - close(session, WsCloseCode.CONNECTION_TIMEOUT); - } else if (Exceptions.isCausedBy(e, ConnectionRuntimeException.class)) { - close(session, WsCloseCode.CONNECTION_FAILURE); - } else if (Exceptions.isCausedBy(e, AuthenticationException.class)) { - close(session, WsCloseCode.CONNECTION_AUTH_FAILURE); - } else if (Exceptions.isCausedBy(e, DisabledException.class)) { - close(session, WsCloseCode.HOST_DISABLED); - } else { - close(session, WsCloseCode.CONNECTION_EXCEPTION); - } - } - } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalAccessDTO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalAccessDTO.java index 50767e31..389c993e 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalAccessDTO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalAccessDTO.java @@ -25,7 +25,4 @@ public class HostTerminalAccessDTO { @Schema(description = "userId") private Long userId; - @Schema(description = "token") - private String token; - } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalConnectDTO.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalConnectDTO.java index 7981e063..e6e9b2b4 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalConnectDTO.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/dto/HostTerminalConnectDTO.java @@ -23,12 +23,6 @@ import lombok.NoArgsConstructor; @Schema(name = "HostTerminalConnectDTO", description = "主机终端连接参数") public class HostTerminalConnectDTO { - @Schema(description = "token") - private String token; - - @Schema(description = "userId") - private Long userId; - @Schema(description = "hostId") private Long hostId; @@ -44,9 +38,15 @@ public class HostTerminalConnectDTO { @Schema(description = "超时时间") private Integer timeout; + @Schema(description = "SSH输出编码") + private String charset; + @Schema(description = "文件名称编码") private String fileNameCharset; + @Schema(description = "文件内容编码") + private String fileContentCharset; + @Schema(description = "用户名") private String username; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/host/HostConnectLogCreateRequest.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/host/HostConnectLogCreateRequest.java index 0f367263..265622ab 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/host/HostConnectLogCreateRequest.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/entity/request/host/HostConnectLogCreateRequest.java @@ -7,6 +7,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import javax.validation.constraints.Size; +import java.util.Map; /** * 主机连接日志 创建请求对象 @@ -37,8 +38,14 @@ public class HostConnectLogCreateRequest { @Schema(description = "主机地址") private String hostAddress; + @Schema(description = "状态") + private String status; + @Size(max = 128) @Schema(description = "token") private String token; + @Schema(description = "拓展信息") + private Map extra; + } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/model/HostSshConfigModel.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/model/HostSshConfigModel.java index 2aae2629..9d9c9330 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/model/HostSshConfigModel.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/model/HostSshConfigModel.java @@ -54,11 +54,21 @@ public class HostSshConfigModel implements GenericsDataModel, UpdatePasswordActi @Schema(description = "连接超时时间") private Integer connectTimeout; + @NotBlank + @Size(max = 12) + @Schema(description = "SSH输出编码") + private String charset; + @NotBlank @Size(max = 12) @Schema(description = "文件名称编码") private String fileNameCharset; + @NotBlank + @Size(max = 12) + @Schema(description = "文件内容编码") + private String fileContentCharset; + @Schema(description = "是否使用新密码 仅参数") private Boolean useNewPassword; diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/strategy/HostSshConfigStrategy.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/strategy/HostSshConfigStrategy.java index af2a81f4..3ad6d3ee 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/strategy/HostSshConfigStrategy.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/config/strategy/HostSshConfigStrategy.java @@ -46,7 +46,9 @@ public class HostSshConfigStrategy implements MapDataStrategy implements ITerminalHandler { */ protected abstract void handle(WebSocketSession session, Message msg); + /** + * 获取响应结构 + * + * @param in in + * @param type type + * @param body body + * @param E + * @return out + */ + public Message out(Message in, OutputOperatorTypeEnum type, E body) { + return Message.builder() + .session(in.getSession()) + .type(type.getType()) + .body(body) + .build(); + } + + /** + * 发送消息 + * + * @param session session + * @param in in + * @param type type + * @param body body + */ + public void send(WebSocketSession session, Message in, OutputOperatorTypeEnum type, Object body) { + // 发送消息 + this.send(session, this.out(in, type, body)); + } + + /** + * 发送消息 + * + * @param session session + * @param message message + */ + protected void send(WebSocketSession session, Message message) { + WebSockets.sendJson(session, message); + } + /** * 获取属性 * diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalCheckHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalCheckHandler.java new file mode 100644 index 00000000..d8d66b2e --- /dev/null +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalCheckHandler.java @@ -0,0 +1,155 @@ +package com.orion.ops.module.asset.handler.host.terminal.handler; + +import com.orion.lang.id.UUIds; +import com.orion.lang.utils.collect.Maps; +import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService; +import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogFiller; +import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs; +import com.orion.ops.framework.common.constant.ErrorMessage; +import com.orion.ops.framework.common.constant.ExtraFieldConst; +import com.orion.ops.framework.common.enums.BooleanBit; +import com.orion.ops.module.asset.dao.HostDAO; +import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType; +import com.orion.ops.module.asset.entity.domain.HostDO; +import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO; +import com.orion.ops.module.asset.entity.request.host.HostConnectLogCreateRequest; +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.entity.Message; +import com.orion.ops.module.asset.handler.host.terminal.entity.request.TerminalCheckRequest; +import com.orion.ops.module.asset.handler.host.terminal.entity.response.TerminalCheckResponse; +import com.orion.ops.module.asset.handler.host.terminal.enums.OutputOperatorTypeEnum; +import com.orion.ops.module.asset.service.HostConnectLogService; +import com.orion.ops.module.asset.service.HostTerminalService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.WebSocketSession; + +import javax.annotation.Resource; +import java.util.Map; + +/** + * 主机连接检查 + * + * @author Jiahang Li + * @version 1.0.0 + * @since 2023/12/29 15:32 + */ +@Slf4j +@Component +public class TerminalCheckHandler extends AbstractTerminalHandler { + + @Resource + private HostDAO hostDAO; + + @Resource + private HostTerminalService hostTerminalService; + + @Resource + private HostConnectLogService hostConnectLogService; + + @Resource + private OperatorLogFrameworkService operatorLogFrameworkService; + + public TerminalCheckHandler() { + super(TerminalCheckRequest.class); + } + + @Override + protected void handle(WebSocketSession session, Message msg) { + Long hostId = msg.getBody().getHostId(); + Long userId = this.getAttr(session, ExtraFieldConst.USER_ID); + long startTime = System.currentTimeMillis(); + String terminalToken = UUIds.random15(); + log.info("TerminalCheckHandler-handle start userId: {}, hostId: {}, terminalToken: {}", userId, hostId, terminalToken); + // 查询主机信息 + HostDO host = hostDAO.selectById(hostId); + // 不存在返回错误信息 + if (host == null) { + log.info("TerminalCheckHandler-handle unknown host userId: {}, hostId: {}", userId, hostId); + this.send(session, msg, + OutputOperatorTypeEnum.CHECK, + new TerminalCheckResponse(terminalToken, BooleanBit.FALSE.getValue(), ErrorMessage.HOST_ABSENT)); + return; + } + Exception ex = null; + try { + // 获取连接信息 + HostTerminalConnectDTO connect = hostTerminalService.getTerminalConnectInfo(userId, host); + // 设置到缓存中 + session.getAttributes().put(terminalToken, connect); + log.info("TerminalCheckHandler-handle success userId: {}, hostId: {}, token: {}", userId, hostId, terminalToken); + } catch (Exception e) { + ex = e; + log.error("TerminalCheckHandler-handle error userId: {}, hostId: {}, token: {}", userId, hostId, terminalToken, e); + } + // 记录主机日志 + this.saveTerminalLog(session, userId, host, startTime, ex, terminalToken); + // 响应检查结果 + this.send(session, msg, + OutputOperatorTypeEnum.CHECK, + TerminalCheckResponse.builder() + .session(terminalToken) + .result(BooleanBit.of(ex == null).getValue()) + .errorMessage(ex == null ? null : ex.getMessage()) + .build()); + } + + /** + * 记录主机日志 + * + * @param session session + * @param userId userId + * @param host host + * @param startTime startTime + * @param ex ex + * @param terminalToken terminalToken + */ + private void saveTerminalLog(WebSocketSession session, + Long userId, + HostDO host, + long startTime, + Exception ex, + String terminalToken) { + Long hostId = host.getId(); + String hostName = host.getName(); + String username = this.getAttr(session, ExtraFieldConst.USERNAME); + // 额外参数 + Map extra = Maps.newMap(); + extra.put(OperatorLogs.ID, hostId); + extra.put(OperatorLogs.NAME, hostName); + extra.put(OperatorLogs.TOKEN, terminalToken); + extra.put(OperatorLogs.SESSION_ID, session.getId()); + // 日志参数 + OperatorLogFiller logModel = OperatorLogFiller.create() + // 填充用户信息 + .fillUserInfo(userId, username) + // 填充 traceId + .fillTraceId(this.getAttr(session, ExtraFieldConst.TRACE_ID)) + // 填充请求留痕信息 + .fillIdentity(this.getAttr(session, ExtraFieldConst.IDENTITY)) + // 填充使用时间 + .fillUsedTime(startTime) + // 填充结果信息 + .fillResult(null, ex) + // 填充拓展信息 + .fillExtra(extra) + // 填充日志 + .fillLogInfo(extra, HostTerminalOperatorType.CONNECT); + // 记录操作日志 + operatorLogFrameworkService.insert(logModel.get()); + // 记录连接日志 + HostConnectLogCreateRequest connectLog = HostConnectLogCreateRequest.builder() + .userId(userId) + .username(username) + .hostId(hostId) + .hostName(hostName) + .hostAddress(host.getAddress()) + .status(ex == null ? HostConnectStatusEnum.CONNECTING.name() : HostConnectStatusEnum.FAILED.name()) + .token(terminalToken) + .extra(extra) + .build(); + hostConnectLogService.create(HostConnectTypeEnum.SSH, connectLog); + } + +} diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalConnectHandler.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalConnectHandler.java index 9c5ecf11..0d4af2f6 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalConnectHandler.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/handler/TerminalConnectHandler.java @@ -1,21 +1,21 @@ package com.orion.ops.module.asset.handler.host.terminal.handler; -import com.orion.lang.id.UUIds; -import com.orion.lang.utils.collect.Maps; +import com.orion.lang.exception.AuthenticationException; +import com.orion.lang.exception.ConnectionRuntimeException; +import com.orion.lang.exception.TimeoutException; +import com.orion.lang.exception.argument.InvalidArgumentException; +import com.orion.lang.utils.Exceptions; import com.orion.lang.utils.io.Streams; import com.orion.net.host.SessionStore; -import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService; -import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogFiller; -import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogs; -import com.orion.ops.framework.common.constant.ExtraFieldConst; -import com.orion.ops.module.asset.dao.HostDAO; -import com.orion.ops.module.asset.define.operator.HostTerminalOperatorType; -import com.orion.ops.module.asset.entity.domain.HostDO; +import com.orion.ops.framework.common.constant.ErrorMessage; +import com.orion.ops.framework.common.enums.BooleanBit; import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO; -import com.orion.ops.module.asset.entity.request.host.HostConnectLogCreateRequest; -import com.orion.ops.module.asset.enums.HostConnectTypeEnum; +import com.orion.ops.module.asset.enums.HostConnectStatusEnum; +import com.orion.ops.module.asset.handler.host.terminal.constant.TerminalMessage; import com.orion.ops.module.asset.handler.host.terminal.entity.Message; import com.orion.ops.module.asset.handler.host.terminal.entity.request.TerminalConnectRequest; +import com.orion.ops.module.asset.handler.host.terminal.entity.response.TerminalConnectResponse; +import com.orion.ops.module.asset.handler.host.terminal.enums.OutputOperatorTypeEnum; import com.orion.ops.module.asset.handler.host.terminal.manager.TerminalManager; import com.orion.ops.module.asset.handler.host.terminal.session.TerminalSession; import com.orion.ops.module.asset.service.HostConnectLogService; @@ -25,7 +25,6 @@ import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketSession; import javax.annotation.Resource; -import java.util.Map; /** * 连接主机处理器 @@ -38,18 +37,12 @@ import java.util.Map; @Component public class TerminalConnectHandler extends AbstractTerminalHandler { - @Resource - private HostDAO hostDAO; - @Resource private HostTerminalService hostTerminalService; @Resource private HostConnectLogService hostConnectLogService; - @Resource - private OperatorLogFrameworkService operatorLogFrameworkService; - @Resource private TerminalManager terminalManager; @@ -59,93 +52,93 @@ public class TerminalConnectHandler extends AbstractTerminalHandler msg) { - TerminalConnectRequest body = msg.getBody(); - Long hostId = body.getHostId(); - Long userId = this.getAttr(session, ExtraFieldConst.USER_ID); - log.info("TerminalConnectHandler-handle start userId: {}, hostId: {}", userId, hostId); - // 查询主机信息 - HostDO host = hostDAO.selectById(hostId); - if (host == null) { - // TODO 不存在返回错误信息 - + String token = msg.getSession(); + log.info("TerminalConnectHandler-handle start token: {}", token); + // 获取主机连接信息 + HostTerminalConnectDTO connect = this.getAttr(session, token); + if (connect == null) { + log.info("TerminalConnectHandler-handle unknown token: {}", token); + this.send(session, msg, + OutputOperatorTypeEnum.CONNECT, + new TerminalConnectResponse(BooleanBit.FALSE.getValue(), ErrorMessage.SESSION_ABSENT)); return; } - // 日志信息 - long startTime = System.currentTimeMillis(); - String terminalToken = UUIds.random15(); - TerminalSession terminalSession = null; + // 移除会话连接信息 + session.getAttributes().remove(token); Exception ex = null; try { // 连接主机 - HostTerminalConnectDTO connect = hostTerminalService.getTerminalConnectInfo(userId, host); - SessionStore sessionStore = hostTerminalService.openSessionStore(connect); - terminalSession = new TerminalSession(terminalToken, session, sessionStore); - terminalSession.connect(body.getCols(), body.getRows()); - log.info("TerminalConnectHandler-handle success userId: {}, hostId: {}, token: {}", userId, hostId, terminalToken); + TerminalSession terminalSession = this.connect(token, connect, session, msg.getBody()); // 添加会话到 manager terminalManager.addSession(terminalSession); } catch (Exception e) { - log.error("TerminalConnectHandler-handle error userId: {}, hostId: {}, token: {}", userId, hostId, terminalToken, e); ex = e; + // 修改连接状态为失败 + hostConnectLogService.updateStatusByToken(token, HostConnectStatusEnum.FAILED); + } + // 返回连接状态 + this.send(session, msg, + OutputOperatorTypeEnum.CONNECT, + TerminalConnectResponse.builder() + .result(BooleanBit.of(ex == null).getValue()) + .errorMessage(this.getConnectErrorMessage(ex)) + .build()); + } + + /** + * 连接主机 + * + * @param token token + * @param connect connect + * @param session session + * @param body body + * @return session + */ + private TerminalSession connect(String token, + HostTerminalConnectDTO connect, + WebSocketSession session, + TerminalConnectRequest body) { + TerminalSession terminalSession = null; + try { + // 建立连接 + SessionStore sessionStore = hostTerminalService.openSessionStore(connect); + terminalSession = new TerminalSession(token, session, sessionStore); + terminalSession.connect(body.getCols(), body.getRows()); + log.info("TerminalConnectHandler-handle success token: {}", token); + return terminalSession; + } catch (Exception e) { Streams.close(terminalSession); - } finally { - // 记录主机日志 - this.saveTerminalLog(session, userId, host, startTime, ex, terminalToken); + log.error("TerminalConnectHandler-handle error token: {}", token, e); + throw e; } } /** - * 记录主机日志 + * 获取建立连接错误信息 * - * @param session session - * @param userId userId - * @param host host - * @param startTime startTime - * @param ex ex - * @param terminalToken terminalToken + * @param e e + * @return errorMessage */ - private void saveTerminalLog(WebSocketSession session, - Long userId, - HostDO host, - long startTime, - Exception ex, - String terminalToken) { - Long hostId = host.getId(); - String hostName = host.getName(); - String username = this.getAttr(session, ExtraFieldConst.USERNAME); - // 额外参数 - Map extra = Maps.newMap(); - extra.put(OperatorLogs.ID, hostId); - extra.put(OperatorLogs.NAME, hostName); - extra.put(OperatorLogs.TOKEN, terminalToken); - // 日志参数 - OperatorLogFiller logModel = OperatorLogFiller.create() - // 填充用户信息 - .fillUserInfo(userId, username) - // 填充 traceId - .fillTraceId(this.getAttr(session, ExtraFieldConst.TRACE_ID)) - // 填充请求留痕信息 - .fillIdentity(this.getAttr(session, ExtraFieldConst.IDENTITY)) - // 填充使用时间 - .fillUsedTime(startTime) - // 填充结果信息 - .fillResult(null, ex) - // 填充拓展信息 - .fillExtra(extra) - // 填充日志 - .fillLogInfo(extra, HostTerminalOperatorType.CONNECT); - // 记录操作日志 - operatorLogFrameworkService.insert(logModel.get()); - // 记录连接日志 - HostConnectLogCreateRequest connectLog = HostConnectLogCreateRequest.builder() - .userId(userId) - .username(username) - .hostId(hostId) - .hostName(hostName) - .hostAddress(host.getAddress()) - .token(terminalToken) - .build(); - hostConnectLogService.create(HostConnectTypeEnum.SSH, connectLog); + private String getConnectErrorMessage(Exception e) { + if (e == null) { + return null; + } + if (Exceptions.isCausedBy(e, TimeoutException.class)) { + // 连接超时 + return TerminalMessage.CONNECTION_TIMEOUT; + } else if (Exceptions.isCausedBy(e, ConnectionRuntimeException.class)) { + // 无法连接 + return TerminalMessage.UNREACHABLE; + } else if (Exceptions.isCausedBy(e, AuthenticationException.class)) { + // 认证失败 + return TerminalMessage.AUTHENTICATION_FAILURE; + } else if (Exceptions.isCausedBy(e, InvalidArgumentException.class)) { + // 参数错误 + return e.getMessage(); + } else { + // 其他错误 + return TerminalMessage.UNREACHABLE; + } } } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java index 511fb7bc..77a6348d 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/handler/host/terminal/session/TerminalSession.java @@ -1,11 +1,16 @@ package com.orion.ops.module.asset.handler.host.terminal.session; +import com.alibaba.fastjson.JSON; +import com.orion.lang.utils.awt.Clipboards; import com.orion.lang.utils.io.Streams; import com.orion.net.host.SessionStore; import com.orion.net.host.ssh.TerminalType; import com.orion.net.host.ssh.shell.ShellExecutor; import com.orion.ops.framework.common.constant.Const; +import com.orion.ops.framework.websocket.core.utils.WebSockets; import com.orion.ops.module.asset.define.AssetThreadPools; +import com.orion.ops.module.asset.handler.host.terminal.entity.Message; +import com.orion.ops.module.asset.handler.host.terminal.enums.OutputOperatorTypeEnum; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.web.socket.WebSocketSession; @@ -13,6 +18,7 @@ import org.springframework.web.socket.WebSocketSession; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; /** * 终端会话 @@ -100,18 +106,23 @@ public class TerminalSession implements ITerminalSession { try { while (session.isOpen() && (read = in.read(bs)) != -1) { // 响应 - // byte[] msg = WsProtocol.OK.msg(bs, 0, read); - // WebSockets.sendText(session, msg); + String body = new String(bs, 0, read, StandardCharsets.UTF_8); + // TODO lastline + Message msg = Message.builder() + .session(token) + .type(OutputOperatorTypeEnum.OUTPUT.getType()) + // FIXME TERMINAL charset + .body(body) + .build(); + WebSockets.sendJson(session, msg); } } catch (IOException ex) { log.error("terminal 读取流失败", ex); - // WebSockets.close(session, WsCloseCode.READ_EXCEPTION); } // eof if (close) { return; } - // WebSockets.close(session, WsCloseCode.EOF); log.info("terminal eof回调 {}", token); } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConnectLogServiceImpl.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConnectLogServiceImpl.java index f9337ab6..781ef982 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConnectLogServiceImpl.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostConnectLogServiceImpl.java @@ -1,5 +1,6 @@ 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.define.wrapper.DataGrid; import com.orion.lang.utils.Arrays1; @@ -37,8 +38,14 @@ public class HostConnectLogServiceImpl implements HostConnectLogService { public void create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) { HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request); record.setType(type.name()); - record.setStatus(HostConnectStatusEnum.CONNECTING.name()); + String status = request.getStatus(); + record.setStatus(status); record.setStartTime(new Date()); + record.setExtraInfo(JSON.toJSONString(request.getExtra())); + // 失败直接设置结束时间 + if (HostConnectStatusEnum.FAILED.name().equals(status)) { + record.setEndTime(new Date()); + } hostConnectLogDAO.insert(record); } diff --git a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostTerminalServiceImpl.java b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostTerminalServiceImpl.java index c56ea236..5cc9d982 100644 --- a/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostTerminalServiceImpl.java +++ b/orion-ops-module-asset/orion-ops-module-asset-service/src/main/java/com/orion/ops/module/asset/service/impl/HostTerminalServiceImpl.java @@ -84,7 +84,6 @@ public class HostTerminalServiceImpl implements HostTerminalService { log.info("HostConnectService.getHostAccessToken userId: {}", userId); String token = UUIds.random32(); HostTerminalAccessDTO access = HostTerminalAccessDTO.builder() - .token(token) .userId(userId) .build(); // 设置缓存 @@ -254,7 +253,9 @@ public class HostTerminalServiceImpl implements HostTerminalService { conn.setHostName(host.getName()); conn.setHostAddress(host.getAddress()); conn.setPort(config.getPort()); + conn.setCharset(config.getCharset()); conn.setFileNameCharset(config.getFileNameCharset()); + conn.setFileContentCharset(config.getFileContentCharset()); conn.setTimeout(config.getConnectTimeout()); conn.setUsername(config.getUsername()); // 填充身份信息 diff --git a/orion-ops-ui/src/views/asset/host-list/components/config/ssh/ssh-config-form.vue b/orion-ops-ui/src/views/asset/host-list/components/config/ssh/ssh-config-form.vue index 61f08311..813a8a4c 100644 --- a/orion-ops-ui/src/views/asset/host-list/components/config/ssh/ssh-config-form.vue +++ b/orion-ops-ui/src/views/asset/host-list/components/config/ssh/ssh-config-form.vue @@ -96,7 +96,7 @@ - +