feat: 连接主机终端.

This commit is contained in:
lijiahang
2024-01-02 18:51:07 +08:00
parent 90ffc472fa
commit f7867a8bcb
13 changed files with 254 additions and 14 deletions

View File

@@ -39,4 +39,6 @@ public interface FieldConst {
String TARGET = "target";
String TOKEN = "token";
}

View File

@@ -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));
}
/**
* 填充日志信息
*

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
/**
* 使用默认配置打开主机会话
*

View File

@@ -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());
// 填充身份信息