feat: 连接主机终端.
This commit is contained in:
@@ -39,4 +39,6 @@ public interface FieldConst {
|
||||
|
||||
String TARGET = "target";
|
||||
|
||||
String TOKEN = "token";
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.orion.lang.utils.Strings;
|
||||
import com.orion.lang.utils.json.matcher.ReplacementFormatters;
|
||||
import com.orion.ops.framework.biz.operator.log.core.config.OperatorLogConfig;
|
||||
import com.orion.ops.framework.biz.operator.log.core.enums.ReturnType;
|
||||
import com.orion.ops.framework.biz.operator.log.core.factory.OperatorTypeHolder;
|
||||
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
|
||||
import com.orion.ops.framework.biz.operator.log.core.model.OperatorType;
|
||||
import com.orion.ops.framework.common.entity.RequestIdentity;
|
||||
@@ -159,6 +160,17 @@ public class OperatorLogFiller implements Gettable<OperatorLogModel> {
|
||||
return this.fillResult(null, null, exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充结果
|
||||
*
|
||||
* @param ret ret
|
||||
* @param exception exception
|
||||
* @return this
|
||||
*/
|
||||
public OperatorLogFiller fillResult(Object ret, Throwable exception) {
|
||||
return this.fillResult(ReturnType.JSON, ret, exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充结果
|
||||
*
|
||||
@@ -209,6 +221,17 @@ public class OperatorLogFiller implements Gettable<OperatorLogModel> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充日志信息
|
||||
*
|
||||
* @param extra extra
|
||||
* @param type type
|
||||
* @return this
|
||||
*/
|
||||
public OperatorLogFiller fillLogInfo(Map<String, Object> extra, String type) {
|
||||
return this.fillLogInfo(extra, OperatorTypeHolder.get(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充日志信息
|
||||
*
|
||||
|
||||
@@ -21,7 +21,7 @@ public class HostTerminalOperatorType extends InitializingOperatorTypes {
|
||||
@Override
|
||||
public OperatorType[] types() {
|
||||
return new OperatorType[]{
|
||||
new OperatorType(L, CONNECT, "连接主机终端 <sb>${hostName}</sb>"),
|
||||
new OperatorType(L, CONNECT, "连接主机终端 <sb>${name}</sb>"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,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;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public class TerminalMessageDispatcher extends TextWebSocketHandler {
|
||||
InputOperatorTypeEnum type = InputOperatorTypeEnum.of(message.getType());
|
||||
if (type != null) {
|
||||
// 处理消息
|
||||
type.getHandler().process(session, message, payload);
|
||||
type.getHandler().process(session, message);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("TerminalDispatchHandler-handleMessage-error msg: {}", payload, e);
|
||||
|
||||
@@ -21,10 +21,16 @@ import lombok.NoArgsConstructor;
|
||||
@Schema(name = "TerminalConnectRequest", description = "终端连接请求 实体对象")
|
||||
public class TerminalConnectRequest {
|
||||
|
||||
// 连接主机 {"t":"co","s": "1001","b":{"h":1}}
|
||||
// 连接主机 {"t":"co","s": "1001","b":{"h":1,"cols":100,"rows":20}}
|
||||
|
||||
@JSONField(name = "h")
|
||||
@Schema(description = "主机id")
|
||||
private String hostId;
|
||||
private Long hostId;
|
||||
|
||||
@Schema(description = "列数")
|
||||
private Integer cols;
|
||||
|
||||
@Schema(description = "行数")
|
||||
private Integer rows;
|
||||
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ public abstract class AbstractTerminalHandler<T> implements ITerminalHandler {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void process(WebSocketSession session, Message<?> message, String payload) {
|
||||
public void process(WebSocketSession session, Message<?> message) {
|
||||
Message<T> res = (Message<T>) message;
|
||||
res.setBody(((JSONObject) message.getBody()).toJavaObject(convert));
|
||||
this.onMessage(session, res);
|
||||
this.handle(session, res);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,6 +36,19 @@ public abstract class AbstractTerminalHandler<T> implements ITerminalHandler {
|
||||
* @param session session
|
||||
* @param msg msg
|
||||
*/
|
||||
protected abstract void onMessage(WebSocketSession session, Message<T> msg);
|
||||
protected abstract void handle(WebSocketSession session, Message<T> msg);
|
||||
|
||||
/**
|
||||
* 获取属性
|
||||
*
|
||||
* @param session session
|
||||
* @param attr attr
|
||||
* @param <T> T
|
||||
* @return T
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T getAttr(WebSocketSession session, String attr) {
|
||||
return (T) session.getAttributes().get(attr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,7 @@ public interface ITerminalHandler {
|
||||
*
|
||||
* @param session session
|
||||
* @param message message
|
||||
* @param payload payload
|
||||
*/
|
||||
void process(WebSocketSession session, Message<?> message, String payload);
|
||||
void process(WebSocketSession session, Message<?> message);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,29 @@
|
||||
package com.orion.ops.module.asset.handler.host.terminal.handler;
|
||||
|
||||
import com.orion.lang.utils.collect.Maps;
|
||||
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.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.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.manager.TerminalSession;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 连接主机处理器
|
||||
*
|
||||
@@ -12,16 +31,114 @@ import org.springframework.web.socket.WebSocketSession;
|
||||
* @version 1.0.0
|
||||
* @since 2023/12/29 15:32
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConnectRequest> {
|
||||
|
||||
@Resource
|
||||
private HostDAO hostDAO;
|
||||
|
||||
@Resource
|
||||
private HostTerminalService hostTerminalService;
|
||||
|
||||
@Resource
|
||||
private HostConnectLogService hostConnectLogService;
|
||||
|
||||
@Resource
|
||||
private OperatorLogFrameworkService operatorLogFrameworkService;
|
||||
|
||||
public TerminalConnectHandler() {
|
||||
super(TerminalConnectRequest.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMessage(WebSocketSession session, Message<TerminalConnectRequest> msg) {
|
||||
System.out.println(msg);
|
||||
protected void handle(WebSocketSession session, Message<TerminalConnectRequest> 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 不存在返回错误信息
|
||||
|
||||
return;
|
||||
}
|
||||
// 日志信息
|
||||
long startTime = System.currentTimeMillis();
|
||||
Exception ex = null;
|
||||
String terminalToken = null;
|
||||
try {
|
||||
// 连接主机
|
||||
HostTerminalConnectDTO connect = hostTerminalService.getTerminalConnectInfo(userId, host);
|
||||
terminalToken = connect.getToken();
|
||||
SessionStore sessionStore = hostTerminalService.openSessionStore(connect);
|
||||
TerminalSession terminalSession = new TerminalSession(session, connect, sessionStore);
|
||||
terminalSession.connect(body.getCols(), body.getRows());
|
||||
log.info("TerminalConnectHandler-handle success userId: {}, hostId: {}", userId, hostId);
|
||||
// TODO 添加到 manager
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("TerminalConnectHandler-handle error userId: {}, hostId: {}", userId, hostId, e);
|
||||
ex = e;
|
||||
} finally {
|
||||
// 记录主机日志
|
||||
this.saveTerminalLog(session, userId, host, startTime, ex, terminalToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录主机日志
|
||||
*
|
||||
* @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);
|
||||
// 日志参数
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.orion.ops.module.asset.handler.host.terminal.manager;
|
||||
|
||||
import com.orion.lang.able.SafeCloseable;
|
||||
import com.orion.net.host.SessionStore;
|
||||
import com.orion.net.host.ssh.shell.ShellExecutor;
|
||||
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
|
||||
/**
|
||||
* 终端会话
|
||||
*
|
||||
* @author Jiahang Li
|
||||
* @version 1.0.0
|
||||
* @since 2024/1/2 17:28
|
||||
*/
|
||||
public class TerminalSession implements SafeCloseable {
|
||||
|
||||
private final WebSocketSession session;
|
||||
|
||||
private final HostTerminalConnectDTO connect;
|
||||
|
||||
private final SessionStore sessionStore;
|
||||
|
||||
private ShellExecutor executor;
|
||||
|
||||
public TerminalSession(WebSocketSession session,
|
||||
HostTerminalConnectDTO connect,
|
||||
SessionStore sessionStore) {
|
||||
this.session = session;
|
||||
this.connect = connect;
|
||||
this.sessionStore = sessionStore;
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接
|
||||
*
|
||||
* @param cols cols
|
||||
* @param rows rows
|
||||
*/
|
||||
public void connect(int cols, int rows) {
|
||||
this.executor = sessionStore.getShellExecutor();
|
||||
executor.size(cols, rows);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,6 +35,7 @@ public class TerminalAccessInterceptor implements HandshakeInterceptor {
|
||||
String token = Urls.getUrlSource(request.getURI().getPath());
|
||||
log.info("TerminalInterceptor-beforeHandshake start token: {}", token);
|
||||
attributes.put(ExtraFieldConst.USER_ID, 1L);
|
||||
attributes.put(ExtraFieldConst.USERNAME, "1");
|
||||
attributes.put(ExtraFieldConst.TRACE_ID, TraceIdHolder.get());
|
||||
attributes.put(ExtraFieldConst.IDENTITY, Requests.getIdentity());
|
||||
// 获取连接数据
|
||||
@@ -45,6 +46,7 @@ public class TerminalAccessInterceptor implements HandshakeInterceptor {
|
||||
// }
|
||||
// // 设置参数
|
||||
// attributes.put(ExtraFieldConst.USER_ID, access.getUserId());
|
||||
// attributes.put(ExtraFieldConst.USERNAME, access.getUsername());
|
||||
// attributes.put(ExtraFieldConst.TRACE_ID, TraceIdHolder.get());
|
||||
// attributes.put(ExtraFieldConst.IDENTITY, Requests.getIdentity());
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.orion.ops.module.asset.service;
|
||||
|
||||
import com.orion.net.host.SessionStore;
|
||||
import com.orion.ops.module.asset.entity.domain.HostDO;
|
||||
import com.orion.ops.module.asset.entity.dto.HostTerminalAccessDTO;
|
||||
import com.orion.ops.module.asset.entity.dto.HostTerminalConnectDTO;
|
||||
|
||||
@@ -30,7 +31,7 @@ public interface HostTerminalService {
|
||||
HostTerminalAccessDTO getAccessInfoByToken(String token);
|
||||
|
||||
/**
|
||||
* 使用用户配置打开获取连接信息
|
||||
* 使用用户配置获取连接信息
|
||||
*
|
||||
* @param hostId hostId
|
||||
* @param userId userId
|
||||
@@ -38,6 +39,15 @@ public interface HostTerminalService {
|
||||
*/
|
||||
HostTerminalConnectDTO getTerminalConnectInfo(Long userId, Long hostId);
|
||||
|
||||
/**
|
||||
* 使用用户配置获取连接信息
|
||||
*
|
||||
* @param host host
|
||||
* @param userId userId
|
||||
* @return session
|
||||
*/
|
||||
HostTerminalConnectDTO getTerminalConnectInfo(Long userId, HostDO host);
|
||||
|
||||
/**
|
||||
* 使用默认配置打开主机会话
|
||||
*
|
||||
|
||||
@@ -82,7 +82,7 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
||||
@Override
|
||||
public String getHostTerminalAccessToken(Long userId) {
|
||||
log.info("HostConnectService.getHostAccessToken userId: {}", userId);
|
||||
String token = UUIds.random19();
|
||||
String token = UUIds.random32();
|
||||
HostTerminalAccessDTO access = HostTerminalAccessDTO.builder()
|
||||
.token(token)
|
||||
.userId(userId)
|
||||
@@ -107,10 +107,16 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
||||
|
||||
@Override
|
||||
public HostTerminalConnectDTO getTerminalConnectInfo(Long userId, Long hostId) {
|
||||
log.info("HostConnectService.getTerminalConnectInfo hostId: {}, userId: {}", hostId, userId);
|
||||
// 查询主机
|
||||
HostDO host = hostDAO.selectById(hostId);
|
||||
Valid.notNull(host, ErrorMessage.HOST_ABSENT);
|
||||
return this.getTerminalConnectInfo(userId, host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostTerminalConnectDTO getTerminalConnectInfo(Long userId, HostDO host) {
|
||||
Long hostId = host.getId();
|
||||
log.info("HostConnectService.getTerminalConnectInfo hostId: {}, userId: {}", hostId, userId);
|
||||
// 查询用户
|
||||
SystemUserDTO user = systemUserApi.getUserById(userId);
|
||||
Valid.notNull(user, ErrorMessage.USER_ABSENT);
|
||||
@@ -143,6 +149,7 @@ public class HostTerminalServiceImpl implements HostTerminalService {
|
||||
}
|
||||
}
|
||||
// 获取连接配置
|
||||
// TODO 看看需不需要 不需要的话就修改位置
|
||||
HostTerminalConnectDTO connect = this.getHostConnectInfo(host, config, extra);
|
||||
connect.setUserId(userId);
|
||||
connect.setToken(UUIds.random15());
|
||||
@@ -251,6 +258,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());
|
||||
// 填充身份信息
|
||||
|
||||
Reference in New Issue
Block a user