feat: 关闭主机回调.

This commit is contained in:
lijiahangmax
2024-01-07 01:29:17 +08:00
parent b160c6317e
commit 48156ebb0d
23 changed files with 287 additions and 160 deletions

View File

@@ -24,7 +24,7 @@ public enum InputTypeEnum {
*/ */
CHECK("ck", CHECK("ck",
TerminalCheckHandler.class, TerminalCheckHandler.class,
new String[]{"type", "session", "hostId"}, new String[]{"type", "sessionId", "hostId"},
TerminalCheckRequest.class), TerminalCheckRequest.class),
/** /**
@@ -32,7 +32,7 @@ public enum InputTypeEnum {
*/ */
CONNECT("co", CONNECT("co",
TerminalConnectHandler.class, TerminalConnectHandler.class,
new String[]{"type", "session", "cols", "rows"}, new String[]{"type", "sessionId", "cols", "rows"},
TerminalConnectRequest.class), TerminalConnectRequest.class),
/** /**
@@ -40,7 +40,7 @@ public enum InputTypeEnum {
*/ */
CLOSE("cl", CLOSE("cl",
TerminalCloseHandler.class, TerminalCloseHandler.class,
new String[]{"type", "session"}, new String[]{"type", "sessionId"},
TerminalBasePayload.class), TerminalBasePayload.class),
/** /**
@@ -56,7 +56,7 @@ public enum InputTypeEnum {
*/ */
RESIZE("rs", RESIZE("rs",
TerminalResizeHandler.class, TerminalResizeHandler.class,
new String[]{"type", "session", "cols", "rows"}, new String[]{"type", "sessionId", "cols", "rows"},
TerminalResizeRequest.class), TerminalResizeRequest.class),
/** /**
@@ -64,7 +64,7 @@ public enum InputTypeEnum {
*/ */
EXEC("e", EXEC("e",
TerminalExecHandler.class, TerminalExecHandler.class,
new String[]{"type", "session", "command"}, new String[]{"type", "sessionId", "command"},
TerminalExecRequest.class), TerminalExecRequest.class),
/** /**
@@ -72,7 +72,7 @@ public enum InputTypeEnum {
*/ */
INPUT("i", INPUT("i",
TerminalInputHandler.class, TerminalInputHandler.class,
new String[]{"type", "session", "command"}, new String[]{"type", "sessionId", "command"},
TerminalInputRequest.class), TerminalInputRequest.class),
; ;

View File

@@ -18,12 +18,17 @@ public enum OutputTypeEnum {
/** /**
* 主机连接检查 * 主机连接检查
*/ */
CHECK("ck", "${type}|${session}|${result}|${errorMessage}"), CHECK("ck", "${type}|${sessionId}|${result}|${msg}"),
/** /**
* 主机连接 * 主机连接
*/ */
CONNECT("co", "${type}|${session}|${result}|${errorMessage}"), CONNECT("co", "${type}|${sessionId}|${result}|${msg}"),
/**
* 关闭连接
*/
CLOSE("cl", "${type}|${sessionId}|${msg}"),
/** /**
* pong * pong
@@ -33,7 +38,7 @@ public enum OutputTypeEnum {
/** /**
* 输出 * 输出
*/ */
OUTPUT("o", "${type}|${session}|${body}"), OUTPUT("o", "${type}|${sessionId}|${body}"),
; ;

View File

@@ -59,7 +59,7 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
Long hostId = payload.getHostId(); Long hostId = payload.getHostId();
Long userId = this.getAttr(channel, ExtraFieldConst.USER_ID); Long userId = this.getAttr(channel, ExtraFieldConst.USER_ID);
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
String sessionId = payload.getSession(); String sessionId = payload.getSessionId();
log.info("TerminalCheckHandler-handle start userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId); log.info("TerminalCheckHandler-handle start userId: {}, hostId: {}, sessionId: {}", userId, hostId, sessionId);
// 检查 session 是否存在 // 检查 session 是否存在
if (this.checkSession(channel, payload)) { if (this.checkSession(channel, payload)) {
@@ -89,9 +89,9 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
this.send(channel, this.send(channel,
OutputTypeEnum.CHECK, OutputTypeEnum.CHECK,
TerminalCheckResponse.builder() TerminalCheckResponse.builder()
.session(payload.getSession()) .sessionId(payload.getSessionId())
.result(BooleanBit.of(ex == null).getValue()) .result(BooleanBit.of(ex == null).getValue())
.errorMessage(ex == null ? null : ex.getMessage()) .msg(ex == null ? null : ex.getMessage())
.build()); .build());
} }
@@ -103,7 +103,7 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
* @return 是否存在 * @return 是否存在
*/ */
private boolean checkSession(WebSocketSession channel, TerminalCheckRequest payload) { private boolean checkSession(WebSocketSession channel, TerminalCheckRequest payload) {
ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSession()); ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSessionId());
if (terminalSession != null) { if (terminalSession != null) {
this.sendCheckFailedMessage(channel, payload, ErrorMessage.SESSION_PRESENT); this.sendCheckFailedMessage(channel, payload, ErrorMessage.SESSION_PRESENT);
return true; return true;
@@ -137,13 +137,13 @@ public class TerminalCheckHandler extends AbstractTerminalHandler<TerminalCheckR
* @param msg msg * @param msg msg
*/ */
private void sendCheckFailedMessage(WebSocketSession channel, TerminalCheckRequest payload, String msg) { private void sendCheckFailedMessage(WebSocketSession channel, TerminalCheckRequest payload, String msg) {
TerminalCheckResponse build = TerminalCheckResponse.builder() TerminalCheckResponse resp = TerminalCheckResponse.builder()
.session(payload.getSession()) .sessionId(payload.getSessionId())
.result(BooleanBit.FALSE.getValue()) .result(BooleanBit.FALSE.getValue())
.errorMessage(msg) .msg(msg)
.build(); .build();
// 发送 // 发送
this.send(channel, OutputTypeEnum.CHECK, build); this.send(channel, OutputTypeEnum.CHECK, resp);
} }
/** /**

View File

@@ -24,9 +24,9 @@ public class TerminalCloseHandler extends AbstractTerminalHandler<TerminalBasePa
@Override @Override
public void handle(WebSocketSession channel, TerminalBasePayload payload) { public void handle(WebSocketSession channel, TerminalBasePayload payload) {
log.info("TerminalCloseHandler-handle start session: {}", payload.getSession()); log.info("TerminalCloseHandler-handle start session: {}", payload.getSessionId());
// 关闭会话 // 关闭会话
terminalManager.closeSession(channel.getId(), payload.getSession()); terminalManager.closeSession(channel.getId(), payload.getSessionId());
} }
} }

View File

@@ -48,7 +48,7 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
@Override @Override
public void handle(WebSocketSession channel, TerminalConnectRequest payload) { public void handle(WebSocketSession channel, TerminalConnectRequest payload) {
String sessionId = payload.getSession(); String sessionId = payload.getSessionId();
log.info("TerminalConnectHandler-handle start sessionId: {}", sessionId); log.info("TerminalConnectHandler-handle start sessionId: {}", sessionId);
// 获取主机连接信息 // 获取主机连接信息
HostTerminalConnectDTO connect = this.getAttr(channel, sessionId); HostTerminalConnectDTO connect = this.getAttr(channel, sessionId);
@@ -57,9 +57,9 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
this.send(channel, this.send(channel,
OutputTypeEnum.CONNECT, OutputTypeEnum.CONNECT,
TerminalConnectResponse.builder() TerminalConnectResponse.builder()
.session(payload.getSession()) .sessionId(payload.getSessionId())
.result(BooleanBit.FALSE.getValue()) .result(BooleanBit.FALSE.getValue())
.errorMessage(ErrorMessage.SESSION_ABSENT) .msg(ErrorMessage.SESSION_ABSENT)
.build()); .build());
return; return;
} }
@@ -80,9 +80,9 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
this.send(channel, this.send(channel,
OutputTypeEnum.CONNECT, OutputTypeEnum.CONNECT,
TerminalConnectResponse.builder() TerminalConnectResponse.builder()
.session(payload.getSession()) .sessionId(payload.getSessionId())
.result(BooleanBit.of(ex == null).getValue()) .result(BooleanBit.of(ex == null).getValue())
.errorMessage(this.getConnectErrorMessage(ex)) .msg(this.getConnectErrorMessage(ex))
.build()); .build());
} }

View File

@@ -26,7 +26,7 @@ public class TerminalExecHandler extends AbstractTerminalHandler<TerminalExecReq
@Override @Override
public void handle(WebSocketSession channel, TerminalExecRequest payload) { public void handle(WebSocketSession channel, TerminalExecRequest payload) {
// 获取会话 // 获取会话
ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSession()); ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSessionId());
if (terminalSession != null) { if (terminalSession != null) {
// 执行命令 // 执行命令
terminalSession.write(payload.getCommand()); terminalSession.write(payload.getCommand());

View File

@@ -26,7 +26,7 @@ public class TerminalInputHandler extends AbstractTerminalHandler<TerminalInputR
@Override @Override
public void handle(WebSocketSession channel, TerminalInputRequest payload) { public void handle(WebSocketSession channel, TerminalInputRequest payload) {
// 获取会话 // 获取会话
ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSession()); ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSessionId());
if (terminalSession != null) { if (terminalSession != null) {
// 处理输入 // 处理输入
terminalSession.write(payload.getCommand()); terminalSession.write(payload.getCommand());

View File

@@ -1,11 +1,17 @@
package com.orion.ops.module.asset.handler.host.terminal.handler; package com.orion.ops.module.asset.handler.host.terminal.handler;
import com.orion.lang.utils.collect.Maps;
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum; import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.ops.module.asset.handler.host.terminal.manager.TerminalManager;
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalBasePayload; import com.orion.ops.module.asset.handler.host.terminal.model.TerminalBasePayload;
import com.orion.ops.module.asset.handler.host.terminal.session.ITerminalSession;
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.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import javax.annotation.Resource;
import java.util.Map;
/** /**
* ping 处理器 * ping 处理器
* *
@@ -17,10 +23,20 @@ import org.springframework.web.socket.WebSocketSession;
@Component @Component
public class TerminalPingHandler extends AbstractTerminalHandler<TerminalBasePayload> { public class TerminalPingHandler extends AbstractTerminalHandler<TerminalBasePayload> {
@Resource
private TerminalManager terminalManager;
@Override @Override
public void handle(WebSocketSession channel, TerminalBasePayload payload) { public void handle(WebSocketSession channel, TerminalBasePayload payload) {
// 发送 pong // 发送 pong
this.send(channel, OutputTypeEnum.PONG.getType()); this.send(channel, OutputTypeEnum.PONG.getType());
// 活跃 terminal
Map<String, ITerminalSession> sessions = terminalManager.getSession(channel.getId());
if (!Maps.isEmpty(sessions)) {
for (ITerminalSession session : sessions.values()) {
session.keepAlive();
}
}
} }
} }

View File

@@ -26,7 +26,7 @@ public class TerminalResizeHandler extends AbstractTerminalHandler<TerminalResiz
@Override @Override
public void handle(WebSocketSession channel, TerminalResizeRequest payload) { public void handle(WebSocketSession channel, TerminalResizeRequest payload) {
// 获取会话 // 获取会话
ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSession()); ITerminalSession terminalSession = terminalManager.getSession(channel.getId(), payload.getSessionId());
if (terminalSession != null) { if (terminalSession != null) {
// 修改大小 // 修改大小
terminalSession.resize(payload.getCols(), payload.getRows()); terminalSession.resize(payload.getCols(), payload.getRows());

View File

@@ -7,6 +7,7 @@ import com.orion.ops.module.asset.handler.host.terminal.session.ITerminalSession
import com.orion.ops.module.asset.handler.host.terminal.session.TerminalSession; import com.orion.ops.module.asset.handler.host.terminal.session.TerminalSession;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@@ -33,17 +34,6 @@ public class TerminalManager {
channelSessions.put(session.getChannel().getId(), session.getSessionId(), session); channelSessions.put(session.getChannel().getId(), session.getSessionId(), session);
} }
/**
* 获取会话
*
* @param channelId channelId
* @param sessionId sessionId
* @return session
*/
public ITerminalSession getSession(String channelId, String sessionId) {
return channelSessions.get(channelId, sessionId);
}
/** /**
* 关闭会话 * 关闭会话
* *
@@ -58,6 +48,27 @@ public class TerminalManager {
} }
} }
/**
* 获取会话
*
* @param channelId channelId
* @param sessionId sessionId
* @return session
*/
public ITerminalSession getSession(String channelId, String sessionId) {
return channelSessions.get(channelId, sessionId);
}
/**
* 获取会话
*
* @param channelId channelId
* @return session
*/
public Map<String, ITerminalSession> getSession(String channelId) {
return channelSessions.get(channelId);
}
/** /**
* 关闭全部会话 * 关闭全部会话
* *

View File

@@ -21,7 +21,7 @@ import lombok.experimental.SuperBuilder;
public class TerminalBasePayload { public class TerminalBasePayload {
@Schema(description = "会话id") @Schema(description = "会话id")
private String session; private String sessionId;
@Schema(description = "消息类型") @Schema(description = "消息类型")
private String type; private String type;

View File

@@ -27,6 +27,6 @@ public class TerminalCheckResponse extends TerminalBasePayload {
private Integer result; private Integer result;
@Schema(description = "错误信息") @Schema(description = "错误信息")
private String errorMessage; private String msg;
} }

View File

@@ -0,0 +1,29 @@
package com.orion.ops.module.asset.handler.host.terminal.model.response;
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalBasePayload;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* 主机连接关闭响应 实体对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023/12/29 16:20
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "TerminalCloseResponse", description = "主机连接关闭响应 实体对象")
public class TerminalCloseResponse extends TerminalBasePayload {
@Schema(description = "关闭信息")
private String msg;
}

View File

@@ -27,6 +27,6 @@ public class TerminalConnectResponse extends TerminalBasePayload {
private Integer result; private Integer result;
@Schema(description = "错误信息") @Schema(description = "错误信息")
private String errorMessage; private String msg;
} }

View File

@@ -41,4 +41,9 @@ public interface ITerminalSession extends SafeCloseable {
*/ */
void write(byte[] b); void write(byte[] b);
/**
* 活跃回话
*/
void keepAlive();
} }

View File

@@ -2,14 +2,15 @@ package com.orion.ops.module.asset.handler.host.terminal.session;
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.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.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.enums.HostConnectStatusEnum; 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.enums.OutputTypeEnum; import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig; import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig;
import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalCloseResponse;
import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalOutputResponse; import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalOutputResponse;
import com.orion.ops.module.asset.service.HostConnectLogService; import com.orion.ops.module.asset.service.HostConnectLogService;
import com.orion.spring.SpringHolder; import com.orion.spring.SpringHolder;
@@ -64,9 +65,9 @@ public class TerminalSession implements ITerminalSession {
config.setRows(rows); config.setRows(rows);
// 打开 shell // 打开 shell
this.executor = sessionStore.getShellExecutor(); this.executor = sessionStore.getShellExecutor();
executor.terminalType(TerminalType.XTERM_256_COLOR);
executor.size(cols, rows); executor.size(cols, rows);
executor.streamHandler(this::streamHandler); executor.streamHandler(this::streamHandler);
executor.callback(this::eofCallback);
executor.connect(); executor.connect();
// 开始监听输出 // 开始监听输出
AssetThreadPools.TERMINAL_SCHEDULER.execute(executor); AssetThreadPools.TERMINAL_SCHEDULER.execute(executor);
@@ -97,8 +98,19 @@ public class TerminalSession implements ITerminalSession {
executor.write(b); executor.write(b);
} }
@Override
public void keepAlive() {
try {
// 发送个信号 保证 socket 不自动关闭
executor.sendSignal(Const.EMPTY);
} catch (Exception e) {
log.error("terminal keep-alive error {}", sessionId, e);
}
}
@Override @Override
public void close() { public void close() {
log.info("terminal close {}", sessionId);
if (close) { if (close) {
return; return;
} }
@@ -128,8 +140,8 @@ public class TerminalSession implements ITerminalSession {
String body = lastLine = new String(bs, 0, read, config.getCharset()); String body = lastLine = new String(bs, 0, read, config.getCharset());
// 响应 // 响应
TerminalOutputResponse resp = TerminalOutputResponse.builder() TerminalOutputResponse resp = TerminalOutputResponse.builder()
.session(sessionId)
.type(OutputTypeEnum.OUTPUT.getType()) .type(OutputTypeEnum.OUTPUT.getType())
.sessionId(sessionId)
.body(body) .body(body)
.build(); .build();
WebSockets.sendText(channel, OutputTypeEnum.OUTPUT.format(resp)); WebSockets.sendText(channel, OutputTypeEnum.OUTPUT.format(resp));
@@ -137,10 +149,22 @@ public class TerminalSession implements ITerminalSession {
} catch (IOException ex) { } catch (IOException ex) {
log.error("terminal 读取流失败", ex); log.error("terminal 读取流失败", ex);
} }
// eof }
if (close) {
log.info("terminal eof回调 {}", sessionId); /**
} * eof 回调
*/
private void eofCallback() {
log.info("terminal eof回调 {}, forClose: {}", sessionId, this.close);
// 发送关闭信息
TerminalCloseResponse resp = TerminalCloseResponse.builder()
.type(OutputTypeEnum.CLOSE.getType())
.sessionId(this.sessionId)
.msg(TerminalMessage.CLOSED_CONNECTION)
.build();
WebSockets.sendText(channel, OutputTypeEnum.CLOSE.format(resp));
// 需要调用关闭 - 可能是 logout 需要手动触发
this.close();
} }
} }

View File

@@ -21,7 +21,7 @@ public class TerminalPreferenceStrategy implements IPreferenceStrategy<TerminalP
.newConnectionType("group") .newConnectionType("group")
.displaySetting(TerminalPreferenceModel.DisplaySettingModel.builder() .displaySetting(TerminalPreferenceModel.DisplaySettingModel.builder()
.fontFamily("_") .fontFamily("_")
.fontSize(15) .fontSize(12)
.lineHeight(1.00) .lineHeight(1.00)
.fontWeight("normal") .fontWeight("normal")
.fontWeightBold("bold") .fontWeightBold("bold")

View File

@@ -2,6 +2,7 @@ import type {
InputPayload, InputPayload,
ITerminalChannel, ITerminalChannel,
ITerminalOutputProcessor, ITerminalOutputProcessor,
ITerminalSessionManager,
OutputPayload, OutputPayload,
Protocol, Protocol,
} from '../types/terminal.type'; } from '../types/terminal.type';
@@ -9,6 +10,7 @@ import { OutputProtocol } from '../types/terminal.protocol';
import { getHostTerminalAccessToken } from '@/api/asset/host-terminal'; import { getHostTerminalAccessToken } from '@/api/asset/host-terminal';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
import { sleep } from '@/utils'; import { sleep } from '@/utils';
import TerminalOutputProcessor from './terminal-output-processor';
export const wsBase = import.meta.env.VITE_WS_BASE_URL; export const wsBase = import.meta.env.VITE_WS_BASE_URL;
@@ -17,10 +19,10 @@ export default class TerminalChannel implements ITerminalChannel {
private client?: WebSocket; private client?: WebSocket;
private readonly processor; private readonly processor: ITerminalOutputProcessor;
constructor(processor: ITerminalOutputProcessor) { constructor(sessionManager: ITerminalSessionManager) {
this.processor = processor; this.processor = new TerminalOutputProcessor(sessionManager, this);
} }
// 初始化 // 初始化
@@ -68,26 +70,14 @@ export default class TerminalChannel implements ITerminalChannel {
if (!payload) { if (!payload) {
return; return;
} }
// 消息调度 // 获取消息处理方法
switch (payload.type) { const processMethod = Object.values(OutputProtocol)
case OutputProtocol.CHECK.type: .find(protocol => protocol.type === payload.type)
// 检查 回调 ?.processMethod;
this.processor.processCheck.call(this.processor, payload); // 处理消息
break; if (processMethod) {
case OutputProtocol.CONNECT.type: const processMethodFn = this.processor[processMethod as keyof ITerminalOutputProcessor] as Function;
// 连接 回调 processMethodFn && processMethodFn.call(this.processor, payload);
this.processor.processConnect.call(this.processor, payload);
break;
case OutputProtocol.PONG.type:
// pong 回调
this.processor.processPong.call(this.processor, payload);
break;
case OutputProtocol.OUTPUT.type:
// 输出 回调
this.processor.processOutput.call(this.processor, payload);
break;
default:
break;
} }
} }

View File

@@ -0,0 +1,74 @@
import {
ITerminalChannel,
ITerminalOutputProcessor,
ITerminalSessionManager,
OutputPayload
} from '../types/terminal.type';
import { InputProtocol } from '../types/terminal.protocol';
// 终端输出消息体处理器实现
export default class TerminalOutputProcessor implements ITerminalOutputProcessor {
private readonly sessionManager: ITerminalSessionManager;
private readonly channel: ITerminalChannel;
constructor(sessionManager: ITerminalSessionManager, channel: ITerminalChannel) {
this.sessionManager = sessionManager;
this.channel = channel;
}
// 处理检查消息
processCheck({ sessionId, result, msg }: OutputPayload): void {
const success = !!Number.parseInt(result);
const session = this.sessionManager.getSession(sessionId);
// 未成功展示错误信息
if (!success) {
session.write(`${msg || ''}`);
return;
}
// 发送 connect 命令
this.channel.send(InputProtocol.CONNECT, { sessionId, cols: session.inst.cols, rows: session.inst.rows });
}
// 处理连接消息
processConnect({ sessionId, result, msg }: OutputPayload): void {
const success = !!Number.parseInt(result);
const session = this.sessionManager.getSession(sessionId);
// 未成功展示错误信息
if (!success) {
session.write(`${msg || ''}`);
return;
}
// 设置可写
session.setCanWrite(true);
// 执行连接逻辑
session.connect();
}
// 处理关闭消息
processClose({ sessionId, msg }: OutputPayload): void {
const session = this.sessionManager.getSession(sessionId);
// 关闭 tab 则无需处理
if (session) {
// 提示消息
session.write(`\r\n${msg || ''}`);
// 设置状态
session.connected = false;
// 设置不可写
session.setCanWrite(false);
}
}
// 处理 pong 消息
processPong(payload: OutputPayload): void {
console.log('pong');
}
// 处理输出消息
processOutput({ sessionId, body }: OutputPayload): void {
const session = this.sessionManager.getSession(sessionId);
session && session.write(body);
}
}

View File

@@ -1,20 +1,13 @@
import type { import type { ITerminalChannel, ITerminalSession, ITerminalSessionManager, TerminalTabItem } from '../types/terminal.type';
ITerminalChannel,
ITerminalSession,
ITerminalSessionManager,
ITerminalOutputProcessor,
OutputPayload,
TerminalTabItem
} from '../types/terminal.type';
import { sleep } from '@/utils'; import { sleep } from '@/utils';
import { InputProtocol } from '../types/terminal.protocol'; import { InputProtocol } from '../types/terminal.protocol';
import TerminalSession from './terminal-session';
import { useDebounceFn } from '@vueuse/core'; import { useDebounceFn } from '@vueuse/core';
import TerminalChannel from '@/views/host/terminal/handler/terminal-channel';
import { addEventListen, removeEventListen } from '@/utils/event'; import { addEventListen, removeEventListen } from '@/utils/event';
import TerminalSession from './terminal-session';
import TerminalChannel from './terminal-channel';
// 终端会话管理器实现 // 终端会话管理器实现
export default class TerminalSessionManager implements ITerminalSessionManager, ITerminalOutputProcessor { export default class TerminalSessionManager implements ITerminalSessionManager {
private readonly channel: ITerminalChannel; private readonly channel: ITerminalChannel;
@@ -50,11 +43,33 @@ export default class TerminalSessionManager implements ITerminalSessionManager,
this.sessions[sessionId] = session; this.sessions[sessionId] = session;
// 发送会话初始化请求 // 发送会话初始化请求
this.channel.send(InputProtocol.CHECK, { this.channel.send(InputProtocol.CHECK, {
session: sessionId, sessionId,
hostId: hostId hostId
}); });
} }
// 获取终端会话
getSession(sessionId: string): ITerminalSession {
return this.sessions[sessionId];
}
// 关闭终端会话
closeSession(sessionId: string): void {
// 发送关闭消息
this.channel?.send(InputProtocol.CLOSE, { sessionId });
// 关闭 session
const session = this.sessions[sessionId];
if (session) {
session.close();
}
// 移除 session
this.sessions[sessionId] = undefined as unknown as ITerminalSession;
// session 全部关闭后 关闭 channel
if (Object.values(this.sessions).filter(Boolean).every(s => !s?.connected)) {
this.reset();
}
}
// 初始化 channel // 初始化 channel
private async initChannel() { private async initChannel() {
// 检查 channel 是否已经初始化 // 检查 channel 是否已经初始化
@@ -71,28 +86,6 @@ export default class TerminalSessionManager implements ITerminalSessionManager,
}, 15000); }, 15000);
} }
// 获取终端会话
getSession(sessionId: string): ITerminalSession {
return this.sessions[sessionId];
}
// 关闭终端会话
closeSession(sessionId: string): void {
// 发送关闭消息
this.channel?.send(InputProtocol.CLOSE, { session: sessionId });
// 关闭 session
const session = this.sessions[sessionId];
if (session) {
session.close();
}
// 移除 session
this.sessions[sessionId] = undefined as unknown as ITerminalSession;
// session 全部关闭后 关闭 channel
if (Object.values(this.sessions).filter(Boolean).every(s => !s?.connected)) {
this.reset();
}
}
// 调度重置大小 // 调度重置大小
private dispatchResize() { private dispatchResize() {
// 对所有已连接的会话重置大小 // 对所有已连接的会话重置大小
@@ -101,45 +94,6 @@ export default class TerminalSessionManager implements ITerminalSessionManager,
.forEach(h => h.fit()); .forEach(h => h.fit());
} }
// 处理检查消息
processCheck({ session: sessionId, result, errorMessage }: OutputPayload): void {
const success = !!Number.parseInt(result);
const session = this.sessions[sessionId];
// 未成功展示错误信息
if (!success) {
session.write('' + errorMessage + '');
return;
}
// 发送 connect 命令
this.channel.send(InputProtocol.CONNECT, { session: sessionId, cols: session.inst.cols, rows: session.inst.rows });
}
// 处理连接消息
processConnect({ session: sessionId, result, errorMessage }: OutputPayload): void {
const success = !!Number.parseInt(result);
const session = this.sessions[sessionId];
// 未成功展示错误信息
if (!success) {
session.write('' + errorMessage + '');
return;
}
// 设置可写
session.setCanWrite(true);
// 执行连接逻辑
session.connect();
}
// 处理 pong 消息
processPong(payload: OutputPayload): void {
console.log('pong');
}
// 处理输出消息
processOutput({ session: sessionId, body }: OutputPayload): void {
const session = this.sessions[sessionId];
session && session.write(body);
}
// 重置 // 重置
reset(): void { reset(): void {
this.sessions = {}; this.sessions = {};

View File

@@ -73,14 +73,14 @@ export default class TerminalSession implements ITerminalSession {
} }
// 输入 // 输入
this.channel.send(InputProtocol.INPUT, { this.channel.send(InputProtocol.INPUT, {
session: this.sessionId, sessionId: this.sessionId,
command: s command: s
}); });
}); });
// 注册 resize 事件 // 注册 resize 事件
this.inst.onResize(({ cols, rows }) => { this.inst.onResize(({ cols, rows }) => {
this.channel.send(InputProtocol.RESIZE, { this.channel.send(InputProtocol.RESIZE, {
session: this.sessionId, sessionId: this.sessionId,
cols, cols,
rows rows
}); });
@@ -90,6 +90,11 @@ export default class TerminalSession implements ITerminalSession {
// 设置是否可写 // 设置是否可写
setCanWrite(canWrite: boolean): void { setCanWrite(canWrite: boolean): void {
this.canWrite = canWrite; this.canWrite = canWrite;
if (canWrite) {
this.inst.options.cursorBlink = useTerminalStore().preference.displaySetting.cursorBlink;
} else {
this.inst.options.cursorBlink = false;
}
} }
// 写入数据 // 写入数据

View File

@@ -3,17 +3,17 @@ export const InputProtocol = {
// 主机连接检查 // 主机连接检查
CHECK: { CHECK: {
type: 'ck', type: 'ck',
template: ['type', 'session', 'hostId'] template: ['type', 'sessionId', 'hostId']
}, },
// 连接主机 // 连接主机
CONNECT: { CONNECT: {
type: 'co', type: 'co',
template: ['type', 'session', 'cols', 'rows'] template: ['type', 'sessionId', 'cols', 'rows']
}, },
// 关闭连接 // 关闭连接
CLOSE: { CLOSE: {
type: 'cl', type: 'cl',
template: ['type', 'session'] template: ['type', 'sessionId']
}, },
// ping // ping
PING: { PING: {
@@ -23,17 +23,17 @@ export const InputProtocol = {
// 修改大小 // 修改大小
RESIZE: { RESIZE: {
type: 'rs', type: 'rs',
template: ['type', 'session', 'cols', 'rows'] template: ['type', 'sessionId', 'cols', 'rows']
}, },
// 执行 // 执行
EXEC: { EXEC: {
type: 'e', type: 'e',
template: ['type', 'session', 'command'] template: ['type', 'sessionId', 'command']
}, },
// 输入 // 输入
INPUT: { INPUT: {
type: 'i', type: 'i',
template: ['type', 'session', 'command'] template: ['type', 'sessionId', 'command']
} }
}; };
@@ -42,21 +42,31 @@ export const OutputProtocol = {
// 主机连接检查 // 主机连接检查
CHECK: { CHECK: {
type: 'ck', type: 'ck',
template: ['type', 'session', 'result', 'errorMessage'] template: ['type', 'sessionId', 'result', 'msg'],
processMethod: 'processCheck'
}, },
// 主机连接 // 主机连接
CONNECT: { CONNECT: {
type: 'co', type: 'co',
template: ['type', 'session', 'result', 'errorMessage'] template: ['type', 'sessionId', 'result', 'msg'],
processMethod: 'processConnect'
},
// 主机连接关闭
CLOSE: {
type: 'cl',
template: ['type', 'sessionId', 'msg'],
processMethod: 'processClose'
}, },
// pong // pong
PONG: { PONG: {
type: 'p', type: 'p',
template: ['type'] template: ['type'],
processMethod: 'processPong'
}, },
// 输出 // 输出
OUTPUT: { OUTPUT: {
type: 'o', type: 'o',
template: ['type', 'session', 'body'] template: ['type', 'sessionId', 'body'],
processMethod: 'processOutput'
}, },
}; };

View File

@@ -13,12 +13,14 @@ export interface TerminalTabItem {
export interface Protocol { export interface Protocol {
type: string; type: string;
template: string[]; template: string[];
[key: string]: unknown;
} }
// 终端输入消息内容 // 终端输入消息内容
export interface InputPayload { export interface InputPayload {
type?: string; type?: string;
session?: string; sessionId?: string;
[key: string]: unknown; [key: string]: unknown;
} }
@@ -26,7 +28,7 @@ export interface InputPayload {
// 终端输出消息内容 // 终端输出消息内容
export interface OutputPayload { export interface OutputPayload {
type: string; type: string;
session: string; sessionId: string;
[key: string]: string; [key: string]: string;
} }
@@ -78,6 +80,8 @@ export interface ITerminalOutputProcessor {
processCheck: (payload: OutputPayload) => void; processCheck: (payload: OutputPayload) => void;
// 处理连接消息 // 处理连接消息
processConnect: (payload: OutputPayload) => void; processConnect: (payload: OutputPayload) => void;
// 处理关闭消息
processClose: (payload: OutputPayload) => void;
// 处理 pong 消息 // 处理 pong 消息
processPong: (payload: OutputPayload) => void; processPong: (payload: OutputPayload) => void;
// 处理输出消息 // 处理输出消息