feat: 连接主机终端.
This commit is contained in:
@@ -71,4 +71,6 @@ public interface ErrorMessage {
|
|||||||
|
|
||||||
String ANY_NO_PERMISSION = "{}无权限";
|
String ANY_NO_PERMISSION = "{}无权限";
|
||||||
|
|
||||||
|
String SESSION_ABSENT = "会话不存在";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ public interface ExtraFieldConst extends FieldConst {
|
|||||||
|
|
||||||
String TRACE_ID = "traceId";
|
String TRACE_ID = "traceId";
|
||||||
|
|
||||||
|
String SESSION_ID = "sessionId";
|
||||||
|
|
||||||
String IDENTITY = "identity";
|
String IDENTITY = "identity";
|
||||||
|
|
||||||
String GROUP_NAME = "groupName";
|
String GROUP_NAME = "groupName";
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ public enum BooleanBit {
|
|||||||
|
|
||||||
private final Integer value;
|
private final Integer value;
|
||||||
|
|
||||||
|
public static BooleanBit of(boolean value) {
|
||||||
|
return value ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
public static BooleanBit of(Integer value) {
|
public static BooleanBit of(Integer value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
package com.orion.ops.framework.websocket.core.constant;
|
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
|
* ws 服务端关闭 code
|
||||||
*
|
*
|
||||||
@@ -13,145 +7,20 @@ import org.springframework.web.socket.WebSocketSession;
|
|||||||
* @version 1.0.0
|
* @version 1.0.0
|
||||||
* @since 2021/6/16 15:18
|
* @since 2021/6/16 15:18
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
public interface WsCloseCode {
|
||||||
@Getter
|
|
||||||
@AllArgsConstructor
|
|
||||||
public enum WsCloseCode {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 未查询到token
|
* code
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关闭会话
|
|
||||||
*
|
*
|
||||||
* @param session session
|
* @return code
|
||||||
*/
|
*/
|
||||||
public void close(WebSocketSession session) {
|
int getCode();
|
||||||
if (!session.isOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
session.close(new CloseStatus(code, reason));
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("websocket close failure", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WsCloseCode of(int code) {
|
/**
|
||||||
for (WsCloseCode value : values()) {
|
* reason
|
||||||
if (value.code == code) {
|
*
|
||||||
return value;
|
* @return reason
|
||||||
}
|
*/
|
||||||
}
|
String getReason();
|
||||||
return 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...";
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,14 @@
|
|||||||
package com.orion.ops.framework.websocket.core.utils;
|
package com.orion.ops.framework.websocket.core.utils;
|
||||||
|
|
||||||
import com.orion.lang.exception.AuthenticationException;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.orion.lang.exception.ConnectionRuntimeException;
|
|
||||||
import com.orion.lang.exception.DisabledException;
|
|
||||||
import com.orion.lang.exception.TimeoutException;
|
|
||||||
import com.orion.lang.utils.Exceptions;
|
import com.orion.lang.utils.Exceptions;
|
||||||
import com.orion.lang.utils.Urls;
|
|
||||||
import com.orion.ops.framework.websocket.core.constant.WsCloseCode;
|
import com.orion.ops.framework.websocket.core.constant.WsCloseCode;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.http.server.ServerHttpRequest;
|
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
import org.springframework.web.socket.TextMessage;
|
import org.springframework.web.socket.TextMessage;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* websocket 工具类
|
* websocket 工具类
|
||||||
@@ -35,7 +29,17 @@ public class WebSockets {
|
|||||||
* @param session session
|
* @param session session
|
||||||
* @param message message
|
* @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()) {
|
if (!session.isOpen()) {
|
||||||
return;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,4 @@ public class HostTerminalAccessDTO {
|
|||||||
@Schema(description = "userId")
|
@Schema(description = "userId")
|
||||||
private Long userId;
|
private Long userId;
|
||||||
|
|
||||||
@Schema(description = "token")
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,6 @@ import lombok.NoArgsConstructor;
|
|||||||
@Schema(name = "HostTerminalConnectDTO", description = "主机终端连接参数")
|
@Schema(name = "HostTerminalConnectDTO", description = "主机终端连接参数")
|
||||||
public class HostTerminalConnectDTO {
|
public class HostTerminalConnectDTO {
|
||||||
|
|
||||||
@Schema(description = "token")
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@Schema(description = "userId")
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
@Schema(description = "hostId")
|
@Schema(description = "hostId")
|
||||||
private Long hostId;
|
private Long hostId;
|
||||||
|
|
||||||
@@ -44,9 +38,15 @@ public class HostTerminalConnectDTO {
|
|||||||
@Schema(description = "超时时间")
|
@Schema(description = "超时时间")
|
||||||
private Integer timeout;
|
private Integer timeout;
|
||||||
|
|
||||||
|
@Schema(description = "SSH输出编码")
|
||||||
|
private String charset;
|
||||||
|
|
||||||
@Schema(description = "文件名称编码")
|
@Schema(description = "文件名称编码")
|
||||||
private String fileNameCharset;
|
private String fileNameCharset;
|
||||||
|
|
||||||
|
@Schema(description = "文件内容编码")
|
||||||
|
private String fileContentCharset;
|
||||||
|
|
||||||
@Schema(description = "用户名")
|
@Schema(description = "用户名")
|
||||||
private String username;
|
private String username;
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import javax.validation.constraints.Size;
|
import javax.validation.constraints.Size;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主机连接日志 创建请求对象
|
* 主机连接日志 创建请求对象
|
||||||
@@ -37,8 +38,14 @@ public class HostConnectLogCreateRequest {
|
|||||||
@Schema(description = "主机地址")
|
@Schema(description = "主机地址")
|
||||||
private String hostAddress;
|
private String hostAddress;
|
||||||
|
|
||||||
|
@Schema(description = "状态")
|
||||||
|
private String status;
|
||||||
|
|
||||||
@Size(max = 128)
|
@Size(max = 128)
|
||||||
@Schema(description = "token")
|
@Schema(description = "token")
|
||||||
private String token;
|
private String token;
|
||||||
|
|
||||||
|
@Schema(description = "拓展信息")
|
||||||
|
private Map<String, Object> extra;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,11 +54,21 @@ public class HostSshConfigModel implements GenericsDataModel, UpdatePasswordActi
|
|||||||
@Schema(description = "连接超时时间")
|
@Schema(description = "连接超时时间")
|
||||||
private Integer connectTimeout;
|
private Integer connectTimeout;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 12)
|
||||||
|
@Schema(description = "SSH输出编码")
|
||||||
|
private String charset;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@Size(max = 12)
|
@Size(max = 12)
|
||||||
@Schema(description = "文件名称编码")
|
@Schema(description = "文件名称编码")
|
||||||
private String fileNameCharset;
|
private String fileNameCharset;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 12)
|
||||||
|
@Schema(description = "文件内容编码")
|
||||||
|
private String fileContentCharset;
|
||||||
|
|
||||||
@Schema(description = "是否使用新密码 仅参数")
|
@Schema(description = "是否使用新密码 仅参数")
|
||||||
private Boolean useNewPassword;
|
private Boolean useNewPassword;
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ public class HostSshConfigStrategy implements MapDataStrategy<HostSshConfigModel
|
|||||||
.username(USERNAME)
|
.username(USERNAME)
|
||||||
.authType(HostSshAuthTypeEnum.PASSWORD.name())
|
.authType(HostSshAuthTypeEnum.PASSWORD.name())
|
||||||
.connectTimeout(Const.MS_S_10)
|
.connectTimeout(Const.MS_S_10)
|
||||||
|
.charset(Const.UTF_8)
|
||||||
.fileNameCharset(Const.UTF_8)
|
.fileNameCharset(Const.UTF_8)
|
||||||
|
.fileContentCharset(Const.UTF_8)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +57,9 @@ public class HostSshConfigStrategy implements MapDataStrategy<HostSshConfigModel
|
|||||||
// 验证认证类型
|
// 验证认证类型
|
||||||
Valid.valid(HostSshAuthTypeEnum::of, model.getAuthType());
|
Valid.valid(HostSshAuthTypeEnum::of, model.getAuthType());
|
||||||
// 验证编码格式
|
// 验证编码格式
|
||||||
|
this.validCharset(model.getCharset());
|
||||||
this.validCharset(model.getFileNameCharset());
|
this.validCharset(model.getFileNameCharset());
|
||||||
|
this.validCharset(model.getFileContentCharset());
|
||||||
// 检查主机秘钥是否存在
|
// 检查主机秘钥是否存在
|
||||||
Long keyId = model.getKeyId();
|
Long keyId = model.getKeyId();
|
||||||
if (keyId != null) {
|
if (keyId != null) {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import com.alibaba.fastjson.JSON;
|
|||||||
import com.orion.ops.framework.websocket.core.handler.TextWebSocketHandler;
|
import com.orion.ops.framework.websocket.core.handler.TextWebSocketHandler;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.entity.Message;
|
import com.orion.ops.module.asset.handler.host.terminal.entity.Message;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.enums.InputOperatorTypeEnum;
|
import com.orion.ops.module.asset.handler.host.terminal.enums.InputOperatorTypeEnum;
|
||||||
|
import com.orion.ops.module.asset.handler.host.terminal.manager.TerminalManager;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.web.socket.CloseStatus;
|
import org.springframework.web.socket.CloseStatus;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 终端处理器
|
* 终端处理器
|
||||||
*
|
*
|
||||||
@@ -20,6 +23,9 @@ import org.springframework.web.socket.WebSocketSession;
|
|||||||
@Component
|
@Component
|
||||||
public class TerminalMessageDispatcher extends TextWebSocketHandler {
|
public class TerminalMessageDispatcher extends TextWebSocketHandler {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private TerminalManager terminalManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(WebSocketSession session, String payload) {
|
public void onMessage(WebSocketSession session, String payload) {
|
||||||
try {
|
try {
|
||||||
@@ -31,19 +37,21 @@ public class TerminalMessageDispatcher extends TextWebSocketHandler {
|
|||||||
type.getHandler().process(session, message);
|
type.getHandler().process(session, message);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("TerminalDispatchHandler-handleMessage-error msg: {}", payload, e);
|
log.error("TerminalDispatchHandler-handleMessage-error id: {}, msg: {}", session.getId(), payload, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
public void handleTransportError(WebSocketSession session, Throwable exception) {
|
||||||
log.info("handleTransportError");
|
log.error("TerminalMessageDispatcher-handleTransportError id: {}", session.getId(), exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
|
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||||
log.info("afterConnectionClosed");
|
String id = session.getId();
|
||||||
// release session
|
log.info("TerminalMessageDispatcher-afterConnectionClosed id: {}, code: {}, reason: {}", id, status.getCode(), status.getReason());
|
||||||
|
// 关闭会话
|
||||||
|
terminalManager.closeAll(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.orion.ops.module.asset.handler.host.terminal.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 终端信息常量
|
||||||
|
*
|
||||||
|
* @author Jiahang Li
|
||||||
|
* @version 1.0.0
|
||||||
|
* @since 2024/1/3 16:57
|
||||||
|
*/
|
||||||
|
public interface TerminalMessage {
|
||||||
|
|
||||||
|
String CLOSED_CONNECTION = "closed connection...";
|
||||||
|
|
||||||
|
String AUTHENTICATION_FAILURE = "authentication failure...";
|
||||||
|
|
||||||
|
String UNREACHABLE = "remote server unreachable...";
|
||||||
|
|
||||||
|
String CONNECTION_TIMEOUT = "connection timeout...";
|
||||||
|
|
||||||
|
String FORCED_OFFLINE = "forced offline...";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.orion.ops.module.asset.handler.host.terminal.entity.request;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
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 2023/12/29 16:20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(name = "TerminalConnectRequest", description = "主机连接检查请求 实体对象")
|
||||||
|
public class TerminalCheckRequest {
|
||||||
|
|
||||||
|
// 连接主机 {"t":"ck","s": "1001","b":{"h":1}}
|
||||||
|
|
||||||
|
@JSONField(name = "h")
|
||||||
|
@Schema(description = "主机id")
|
||||||
|
private Long hostId;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,15 +21,13 @@ import lombok.NoArgsConstructor;
|
|||||||
@Schema(name = "TerminalConnectRequest", description = "终端连接请求 实体对象")
|
@Schema(name = "TerminalConnectRequest", description = "终端连接请求 实体对象")
|
||||||
public class TerminalConnectRequest {
|
public class TerminalConnectRequest {
|
||||||
|
|
||||||
// 连接主机 {"t":"co","s": "1001","b":{"h":1,"cols":100,"rows":20}}
|
// 连接主机 {"t":"co","s": "1001","b":{"c":100,"r":20}}
|
||||||
|
|
||||||
@JSONField(name = "h")
|
|
||||||
@Schema(description = "主机id")
|
|
||||||
private Long hostId;
|
|
||||||
|
|
||||||
|
@JSONField(name = "c")
|
||||||
@Schema(description = "列数")
|
@Schema(description = "列数")
|
||||||
private Integer cols;
|
private Integer cols;
|
||||||
|
|
||||||
|
@JSONField(name = "r")
|
||||||
@Schema(description = "行数")
|
@Schema(description = "行数")
|
||||||
private Integer rows;
|
private Integer rows;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.orion.ops.module.asset.handler.host.terminal.entity.request;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
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 2023/12/29 16:20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(name = "TerminalResizeRequest", description = "修改大小请求 实体对象")
|
||||||
|
public class TerminalResizeRequest {
|
||||||
|
|
||||||
|
// 连接主机 {"t":"rs","s": "1001","b":{"c":100,"r":20}}
|
||||||
|
|
||||||
|
@JSONField(name = "c")
|
||||||
|
@Schema(description = "列数")
|
||||||
|
private Integer cols;
|
||||||
|
|
||||||
|
@JSONField(name = "r")
|
||||||
|
@Schema(description = "行数")
|
||||||
|
private Integer rows;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.orion.ops.module.asset.handler.host.terminal.entity.response;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
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 2023/12/29 16:20
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(name = "TerminalCheckResponse", description = "主机连接检查响应 实体对象")
|
||||||
|
public class TerminalCheckResponse {
|
||||||
|
|
||||||
|
@JSONField(name = "s")
|
||||||
|
@Schema(description = "会话id")
|
||||||
|
private String session;
|
||||||
|
|
||||||
|
@JSONField(name = "r")
|
||||||
|
@Schema(description = "检查结果")
|
||||||
|
private Integer result;
|
||||||
|
|
||||||
|
@JSONField(name = "em")
|
||||||
|
@Schema(description = "错误信息")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -21,8 +21,12 @@ import lombok.NoArgsConstructor;
|
|||||||
@Schema(name = "TerminalConnectResponse", description = "终端连接响应 实体对象")
|
@Schema(name = "TerminalConnectResponse", description = "终端连接响应 实体对象")
|
||||||
public class TerminalConnectResponse {
|
public class TerminalConnectResponse {
|
||||||
|
|
||||||
@JSONField(name = "s")
|
@JSONField(name = "r")
|
||||||
@Schema(description = "会话id")
|
@Schema(description = "检查结果")
|
||||||
private String s;
|
private Integer result;
|
||||||
|
|
||||||
|
@JSONField(name = "em")
|
||||||
|
@Schema(description = "错误信息")
|
||||||
|
private String errorMessage;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.orion.ops.module.asset.handler.host.terminal.enums;
|
package com.orion.ops.module.asset.handler.host.terminal.enums;
|
||||||
|
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.handler.ITerminalHandler;
|
import com.orion.ops.module.asset.handler.host.terminal.handler.ITerminalHandler;
|
||||||
|
import com.orion.ops.module.asset.handler.host.terminal.handler.TerminalCheckHandler;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.handler.TerminalConnectHandler;
|
import com.orion.ops.module.asset.handler.host.terminal.handler.TerminalConnectHandler;
|
||||||
import com.orion.spring.SpringHolder;
|
import com.orion.spring.SpringHolder;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -17,6 +18,11 @@ import javax.annotation.PostConstruct;
|
|||||||
*/
|
*/
|
||||||
public enum InputOperatorTypeEnum {
|
public enum InputOperatorTypeEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主机连接检查 (临时 token 换实际 token / 检查权限)
|
||||||
|
*/
|
||||||
|
CHECK("ck", TerminalCheckHandler.class),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接主机
|
* 连接主机
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ import lombok.Getter;
|
|||||||
public enum OutputOperatorTypeEnum {
|
public enum OutputOperatorTypeEnum {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接主机成功
|
* 主机连接检查
|
||||||
*/
|
*/
|
||||||
CONNECT_COMPLETE("cc"),
|
CHECK("ck"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接主机失败
|
* 主机连接
|
||||||
*/
|
*/
|
||||||
CONNECT_FAILED("cf"),
|
CONNECT("co"),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* pong
|
* pong
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.orion.ops.module.asset.handler.host.terminal.handler;
|
package com.orion.ops.module.asset.handler.host.terminal.handler;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.orion.ops.framework.websocket.core.utils.WebSockets;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.entity.Message;
|
import com.orion.ops.module.asset.handler.host.terminal.entity.Message;
|
||||||
|
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputOperatorTypeEnum;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,6 +40,46 @@ public abstract class AbstractTerminalHandler<T> implements ITerminalHandler {
|
|||||||
*/
|
*/
|
||||||
protected abstract void handle(WebSocketSession session, Message<T> msg);
|
protected abstract void handle(WebSocketSession session, Message<T> msg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取响应结构
|
||||||
|
*
|
||||||
|
* @param in in
|
||||||
|
* @param type type
|
||||||
|
* @param body body
|
||||||
|
* @param <E> E
|
||||||
|
* @return out
|
||||||
|
*/
|
||||||
|
public <E> Message<E> out(Message<?> in, OutputOperatorTypeEnum type, E body) {
|
||||||
|
return Message.<E>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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取属性
|
* 获取属性
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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<TerminalCheckRequest> {
|
||||||
|
|
||||||
|
@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<TerminalCheckRequest> 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<String, Object> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package com.orion.ops.module.asset.handler.host.terminal.handler;
|
package com.orion.ops.module.asset.handler.host.terminal.handler;
|
||||||
|
|
||||||
import com.orion.lang.id.UUIds;
|
import com.orion.lang.exception.AuthenticationException;
|
||||||
import com.orion.lang.utils.collect.Maps;
|
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.lang.utils.io.Streams;
|
||||||
import com.orion.net.host.SessionStore;
|
import com.orion.net.host.SessionStore;
|
||||||
import com.orion.ops.framework.biz.operator.log.core.service.OperatorLogFrameworkService;
|
import com.orion.ops.framework.common.constant.ErrorMessage;
|
||||||
import com.orion.ops.framework.biz.operator.log.core.uitls.OperatorLogFiller;
|
import com.orion.ops.framework.common.enums.BooleanBit;
|
||||||
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.module.asset.entity.dto.HostTerminalConnectDTO;
|
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.constant.TerminalMessage;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.entity.Message;
|
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.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.manager.TerminalManager;
|
||||||
import com.orion.ops.module.asset.handler.host.terminal.session.TerminalSession;
|
import com.orion.ops.module.asset.handler.host.terminal.session.TerminalSession;
|
||||||
import com.orion.ops.module.asset.service.HostConnectLogService;
|
import com.orion.ops.module.asset.service.HostConnectLogService;
|
||||||
@@ -25,7 +25,6 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 连接主机处理器
|
* 连接主机处理器
|
||||||
@@ -38,18 +37,12 @@ import java.util.Map;
|
|||||||
@Component
|
@Component
|
||||||
public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConnectRequest> {
|
public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConnectRequest> {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private HostDAO hostDAO;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private HostTerminalService hostTerminalService;
|
private HostTerminalService hostTerminalService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private HostConnectLogService hostConnectLogService;
|
private HostConnectLogService hostConnectLogService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private OperatorLogFrameworkService operatorLogFrameworkService;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private TerminalManager terminalManager;
|
private TerminalManager terminalManager;
|
||||||
|
|
||||||
@@ -59,93 +52,93 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handle(WebSocketSession session, Message<TerminalConnectRequest> msg) {
|
protected void handle(WebSocketSession session, Message<TerminalConnectRequest> msg) {
|
||||||
TerminalConnectRequest body = msg.getBody();
|
String token = msg.getSession();
|
||||||
Long hostId = body.getHostId();
|
log.info("TerminalConnectHandler-handle start token: {}", token);
|
||||||
Long userId = this.getAttr(session, ExtraFieldConst.USER_ID);
|
// 获取主机连接信息
|
||||||
log.info("TerminalConnectHandler-handle start userId: {}, hostId: {}", userId, hostId);
|
HostTerminalConnectDTO connect = this.getAttr(session, token);
|
||||||
// 查询主机信息
|
if (connect == null) {
|
||||||
HostDO host = hostDAO.selectById(hostId);
|
log.info("TerminalConnectHandler-handle unknown token: {}", token);
|
||||||
if (host == null) {
|
this.send(session, msg,
|
||||||
// TODO 不存在返回错误信息
|
OutputOperatorTypeEnum.CONNECT,
|
||||||
|
new TerminalConnectResponse(BooleanBit.FALSE.getValue(), ErrorMessage.SESSION_ABSENT));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 日志信息
|
// 移除会话连接信息
|
||||||
long startTime = System.currentTimeMillis();
|
session.getAttributes().remove(token);
|
||||||
String terminalToken = UUIds.random15();
|
|
||||||
TerminalSession terminalSession = null;
|
|
||||||
Exception ex = null;
|
Exception ex = null;
|
||||||
try {
|
try {
|
||||||
// 连接主机
|
// 连接主机
|
||||||
HostTerminalConnectDTO connect = hostTerminalService.getTerminalConnectInfo(userId, host);
|
TerminalSession terminalSession = this.connect(token, connect, session, msg.getBody());
|
||||||
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);
|
|
||||||
// 添加会话到 manager
|
// 添加会话到 manager
|
||||||
terminalManager.addSession(terminalSession);
|
terminalManager.addSession(terminalSession);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("TerminalConnectHandler-handle error userId: {}, hostId: {}, token: {}", userId, hostId, terminalToken, e);
|
|
||||||
ex = 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);
|
Streams.close(terminalSession);
|
||||||
} finally {
|
log.error("TerminalConnectHandler-handle error token: {}", token, e);
|
||||||
// 记录主机日志
|
throw e;
|
||||||
this.saveTerminalLog(session, userId, host, startTime, ex, terminalToken);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录主机日志
|
* 获取建立连接错误信息
|
||||||
*
|
*
|
||||||
* @param session session
|
* @param e e
|
||||||
* @param userId userId
|
* @return errorMessage
|
||||||
* @param host host
|
|
||||||
* @param startTime startTime
|
|
||||||
* @param ex ex
|
|
||||||
* @param terminalToken terminalToken
|
|
||||||
*/
|
*/
|
||||||
private void saveTerminalLog(WebSocketSession session,
|
private String getConnectErrorMessage(Exception e) {
|
||||||
Long userId,
|
if (e == null) {
|
||||||
HostDO host,
|
return null;
|
||||||
long startTime,
|
}
|
||||||
Exception ex,
|
if (Exceptions.isCausedBy(e, TimeoutException.class)) {
|
||||||
String terminalToken) {
|
// 连接超时
|
||||||
Long hostId = host.getId();
|
return TerminalMessage.CONNECTION_TIMEOUT;
|
||||||
String hostName = host.getName();
|
} else if (Exceptions.isCausedBy(e, ConnectionRuntimeException.class)) {
|
||||||
String username = this.getAttr(session, ExtraFieldConst.USERNAME);
|
// 无法连接
|
||||||
// 额外参数
|
return TerminalMessage.UNREACHABLE;
|
||||||
Map<String, Object> extra = Maps.newMap();
|
} else if (Exceptions.isCausedBy(e, AuthenticationException.class)) {
|
||||||
extra.put(OperatorLogs.ID, hostId);
|
// 认证失败
|
||||||
extra.put(OperatorLogs.NAME, hostName);
|
return TerminalMessage.AUTHENTICATION_FAILURE;
|
||||||
extra.put(OperatorLogs.TOKEN, terminalToken);
|
} else if (Exceptions.isCausedBy(e, InvalidArgumentException.class)) {
|
||||||
// 日志参数
|
// 参数错误
|
||||||
OperatorLogFiller logModel = OperatorLogFiller.create()
|
return e.getMessage();
|
||||||
// 填充用户信息
|
} else {
|
||||||
.fillUserInfo(userId, username)
|
// 其他错误
|
||||||
// 填充 traceId
|
return TerminalMessage.UNREACHABLE;
|
||||||
.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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package com.orion.ops.module.asset.handler.host.terminal.session;
|
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.lang.utils.io.Streams;
|
||||||
import com.orion.net.host.SessionStore;
|
import com.orion.net.host.SessionStore;
|
||||||
import com.orion.net.host.ssh.TerminalType;
|
import com.orion.net.host.ssh.TerminalType;
|
||||||
import com.orion.net.host.ssh.shell.ShellExecutor;
|
import com.orion.net.host.ssh.shell.ShellExecutor;
|
||||||
import com.orion.ops.framework.common.constant.Const;
|
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.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.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.web.socket.WebSocketSession;
|
import org.springframework.web.socket.WebSocketSession;
|
||||||
@@ -13,6 +18,7 @@ import org.springframework.web.socket.WebSocketSession;
|
|||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 终端会话
|
* 终端会话
|
||||||
@@ -100,18 +106,23 @@ public class TerminalSession implements ITerminalSession {
|
|||||||
try {
|
try {
|
||||||
while (session.isOpen() && (read = in.read(bs)) != -1) {
|
while (session.isOpen() && (read = in.read(bs)) != -1) {
|
||||||
// 响应
|
// 响应
|
||||||
// byte[] msg = WsProtocol.OK.msg(bs, 0, read);
|
String body = new String(bs, 0, read, StandardCharsets.UTF_8);
|
||||||
// WebSockets.sendText(session, msg);
|
// 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) {
|
} catch (IOException ex) {
|
||||||
log.error("terminal 读取流失败", ex);
|
log.error("terminal 读取流失败", ex);
|
||||||
// WebSockets.close(session, WsCloseCode.READ_EXCEPTION);
|
|
||||||
}
|
}
|
||||||
// eof
|
// eof
|
||||||
if (close) {
|
if (close) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// WebSockets.close(session, WsCloseCode.EOF);
|
|
||||||
log.info("terminal eof回调 {}", token);
|
log.info("terminal eof回调 {}", token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.orion.ops.module.asset.service.impl;
|
package com.orion.ops.module.asset.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.orion.lang.define.wrapper.DataGrid;
|
import com.orion.lang.define.wrapper.DataGrid;
|
||||||
import com.orion.lang.utils.Arrays1;
|
import com.orion.lang.utils.Arrays1;
|
||||||
@@ -37,8 +38,14 @@ public class HostConnectLogServiceImpl implements HostConnectLogService {
|
|||||||
public void create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) {
|
public void create(HostConnectTypeEnum type, HostConnectLogCreateRequest request) {
|
||||||
HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request);
|
HostConnectLogDO record = HostConnectLogConvert.MAPPER.to(request);
|
||||||
record.setType(type.name());
|
record.setType(type.name());
|
||||||
record.setStatus(HostConnectStatusEnum.CONNECTING.name());
|
String status = request.getStatus();
|
||||||
|
record.setStatus(status);
|
||||||
record.setStartTime(new Date());
|
record.setStartTime(new Date());
|
||||||
|
record.setExtraInfo(JSON.toJSONString(request.getExtra()));
|
||||||
|
// 失败直接设置结束时间
|
||||||
|
if (HostConnectStatusEnum.FAILED.name().equals(status)) {
|
||||||
|
record.setEndTime(new Date());
|
||||||
|
}
|
||||||
hostConnectLogDAO.insert(record);
|
hostConnectLogDAO.insert(record);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,6 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
|||||||
log.info("HostConnectService.getHostAccessToken userId: {}", userId);
|
log.info("HostConnectService.getHostAccessToken userId: {}", userId);
|
||||||
String token = UUIds.random32();
|
String token = UUIds.random32();
|
||||||
HostTerminalAccessDTO access = HostTerminalAccessDTO.builder()
|
HostTerminalAccessDTO access = HostTerminalAccessDTO.builder()
|
||||||
.token(token)
|
|
||||||
.userId(userId)
|
.userId(userId)
|
||||||
.build();
|
.build();
|
||||||
// 设置缓存
|
// 设置缓存
|
||||||
@@ -254,7 +253,9 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
|||||||
conn.setHostName(host.getName());
|
conn.setHostName(host.getName());
|
||||||
conn.setHostAddress(host.getAddress());
|
conn.setHostAddress(host.getAddress());
|
||||||
conn.setPort(config.getPort());
|
conn.setPort(config.getPort());
|
||||||
|
conn.setCharset(config.getCharset());
|
||||||
conn.setFileNameCharset(config.getFileNameCharset());
|
conn.setFileNameCharset(config.getFileNameCharset());
|
||||||
|
conn.setFileContentCharset(config.getFileContentCharset());
|
||||||
conn.setTimeout(config.getConnectTimeout());
|
conn.setTimeout(config.getConnectTimeout());
|
||||||
conn.setUsername(config.getUsername());
|
conn.setUsername(config.getUsername());
|
||||||
// 填充身份信息
|
// 填充身份信息
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</a-input-number>
|
</a-input-number>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<!-- 用户名 -->
|
<!-- SSH输出编码 -->
|
||||||
<a-form-item field="charset"
|
<a-form-item field="charset"
|
||||||
label="SSH输出编码"
|
label="SSH输出编码"
|
||||||
:hide-asterisk="true"
|
:hide-asterisk="true"
|
||||||
|
|||||||
Reference in New Issue
Block a user