优化会话关闭处理逻辑.

This commit is contained in:
lijiahang
2024-06-11 12:31:16 +08:00
parent 4b060a864a
commit ae03460a33
10 changed files with 131 additions and 65 deletions

View File

@@ -65,7 +65,7 @@ export function deleteHostSftpLog(idList: Array<number>) {
/**
* 下载文件
*/
export function downloadWithTransferToken(channelId: string, transferToken: string) {
window.open(`${httpBaseUrl}/asset/host-sftp/download?channelId=${channelId}&transferToken=${transferToken}`, 'newWindow');
export function getDownloadTransferUrl(channelId: string, transferToken: string) {
return `${httpBaseUrl}/asset/host-sftp/download?channelId=${channelId}&transferToken=${transferToken}`;
}

View File

@@ -84,6 +84,27 @@ export function getParentPath(path: string) {
return parent;
}
/**
* 打开下载文件
*/
export function openDownloadFile(url: string) {
try {
// 创建隐藏的可下载链接
let element = document.createElement('a');
element.setAttribute('href', url);
element.setAttribute('download', '');
element.style.display = 'none';
// 将其附加到文档中
document.body.appendChild(element);
// 点击该下载链接
element.click();
// 移除已下载的链接
document.body.removeChild(element);
} catch (e) {
window.open(url, 'newWindow');
}
}
/**
* 下载文件
*/

View File

@@ -1,2 +1,2 @@
// 执行
export const historyCount = 20;
// 历史数量
export const historyCount = 15;

View File

@@ -1,12 +1,13 @@
import type { ISftpTransferManager, ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
import { ISftpTransferDownloader, SftpFile, TransferOperatorResponse } from '../types/terminal.type';
import { TransferReceiverType, TransferStatus, TransferType } from '../types/terminal.const';
import { sessionCloseMsg, TransferReceiverType, TransferStatus, TransferType } from '../types/terminal.const';
import { Message } from '@arco-design/web-vue';
import { getTerminalAccessToken, openHostTransferChannel } from '@/api/asset/host-terminal';
import { nextId } from '@/utils';
import { downloadWithTransferToken } from '@/api/asset/host-sftp';
import { getDownloadTransferUrl } from '@/api/asset/host-sftp';
import SftpTransferUploader from './sftp-transfer-uploader';
import SftpTransferDownloader from './sftp-transfer-downloader';
import { openDownloadFile } from '@/utils/file';
// sftp 传输管理器实现
export default class SftpTransferManager implements ISftpTransferManager {
@@ -257,7 +258,10 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 接收开始下载响应
private resolveDownloadStart(data: TransferOperatorResponse) {
downloadWithTransferToken(data.channelId as string, data.transferToken as string);
// 获取下载 url
const url = getDownloadTransferUrl(data.channelId as string, data.transferToken as string);
// 打开
openDownloadFile(url);
}
// 接收下载进度响应
@@ -287,6 +291,14 @@ export default class SftpTransferManager implements ISftpTransferManager {
this.run = false;
// 关闭传输进度
clearInterval(this.progressIntervalId);
// 进行中和等待中的文件改为失败
this.transferList.forEach(s => {
if (s.status === TransferStatus.WAITING ||
s.status === TransferStatus.TRANSFERRING) {
s.status = TransferStatus.ERROR;
s.errorMessage = sessionCloseMsg;
}
});
}
}

View File

@@ -1,5 +1,6 @@
import type { InputPayload, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, OutputPayload, Protocol, } from '../types/terminal.type';
import { OutputProtocol } from '../types/terminal.protocol';
import type { InputPayload, ITerminalChannel, ITerminalOutputProcessor, ITerminalSessionManager, Protocol, } from '../types/terminal.type';
import { format, OutputProtocol, parse } from '../types/terminal.protocol';
import { sessionCloseMsg } from '../types/terminal.const';
import { getTerminalAccessToken, openHostTerminalChannel } from '@/api/asset/host-terminal';
import { Message } from '@arco-design/web-vue';
import TerminalOutputProcessor from './terminal-output-processor';
@@ -9,9 +10,12 @@ export default class TerminalChannel implements ITerminalChannel {
private client?: WebSocket;
private readonly sessionManager: ITerminalSessionManager;
private readonly processor: ITerminalOutputProcessor;
constructor(sessionManager: ITerminalSessionManager) {
this.sessionManager = sessionManager;
this.processor = new TerminalOutputProcessor(sessionManager, this);
}
@@ -29,6 +33,8 @@ export default class TerminalChannel implements ITerminalChannel {
}
this.client.onclose = event => {
console.warn('terminal close', event);
// 关闭回调
this.closeCallback();
};
this.client.onmessage = this.handlerMessage.bind(this);
}
@@ -66,6 +72,25 @@ export default class TerminalChannel implements ITerminalChannel {
}
}
// 关闭回调
private closeCallback(): void {
// 关闭时将手动触发 close 消息, 有可能是其他原因关闭的, 没有接收到 close 消息, 导致已断开是终端还是显示已连接
Object.values(this.sessionManager.sessions).forEach(s => {
if (!s?.connected) {
return;
}
// close 消息
const data = format(OutputProtocol.CLOSE, {
type: OutputProtocol.CLOSE.type,
sessionId: s.sessionId,
forceClose: 0,
msg: sessionCloseMsg,
});
// 触发 close 消息
this.handlerMessage({ data } as MessageEvent);
});
}
// 关闭
close(): void {
// 关闭 client
@@ -78,55 +103,3 @@ export default class TerminalChannel implements ITerminalChannel {
}
}
// 分隔符
export const SEPARATOR = '|';
// 解析参数
export const parse = (payload: string) => {
const protocols = Object.values(OutputProtocol);
const useProtocol = protocols.find(p => payload.startsWith(p.type + SEPARATOR) || p.type === payload);
if (!useProtocol) {
return undefined;
}
const template = useProtocol.template;
const res = {} as OutputPayload;
let curr = 0;
let len = payload.length;
for (let i = 0, pl = template.length; i < pl; i++) {
if (i == pl - 1) {
// 最后一次
res[template[i]] = payload.substring(curr, len);
} else {
// 非最后一次
let tmp = '';
for (; curr < len; curr++) {
const c = payload.charAt(curr);
if (c == SEPARATOR) {
res[template[i]] = tmp;
curr++;
break;
} else {
tmp += c;
}
}
}
}
return res;
};
// 格式化参数
export const format = (protocol: Protocol, payload: InputPayload) => {
payload.type = protocol.type;
return protocol.template
.map(i => getPayloadValueString(payload[i]))
.join(SEPARATOR);
};
// 获取默认值
export const getPayloadValueString = (value: unknown): any => {
if (value === undefined || value === null) {
return '';
}
return value;
};

View File

@@ -85,7 +85,7 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
}
// 处理关闭消息
processClose({ sessionId, msg, forceClose }: OutputPayload): void {
processClose({ sessionId, forceClose, msg }: OutputPayload): void {
const session = this.sessionManager.getSession(sessionId);
// 无需处理 (直接关闭 tab)
if (!session) {

View File

@@ -19,17 +19,17 @@ import SftpSession from './sftp-session';
// 终端会话管理器实现
export default class TerminalSessionManager implements ITerminalSessionManager {
private readonly channel: ITerminalChannel;
public sessions: Record<string, ITerminalSession>;
private sessions: Record<string, ITerminalSession>;
private readonly channel: ITerminalChannel;
private keepAliveTaskId?: any;
private readonly dispatchResizeFn: () => {};
constructor() {
this.channel = new TerminalChannel(this);
this.sessions = {};
this.channel = new TerminalChannel(this);
this.dispatchResizeFn = useDebounceFn(this.dispatchResize).bind(this);
}

View File

@@ -352,6 +352,9 @@ export const TransferReceiverType = {
DOWNLOAD_ERROR: 'downloadError',
};
// 会话关闭信息
export const sessionCloseMsg = 'session closed...';
// 打开 settingModal key
export const openSettingModalKey = Symbol();

View File

@@ -1,3 +1,8 @@
import type { InputPayload, OutputPayload, Protocol } from './terminal.type';
// 分隔符
export const SEPARATOR = '|';
// 输入协议
export const InputProtocol = {
// 主机连接检查
@@ -164,3 +169,52 @@ export const OutputProtocol = {
processMethod: 'processSftpSetContent'
},
};
// 解析参数
export const parse = (payload: string) => {
const protocols = Object.values(OutputProtocol);
const useProtocol = protocols.find(p => payload.startsWith(p.type + SEPARATOR) || p.type === payload);
if (!useProtocol) {
return undefined;
}
const template = useProtocol.template;
const res = {} as OutputPayload;
let curr = 0;
let len = payload.length;
for (let i = 0, pl = template.length; i < pl; i++) {
if (i == pl - 1) {
// 最后一次
res[template[i]] = payload.substring(curr, len);
} else {
// 非最后一次
let tmp = '';
for (; curr < len; curr++) {
const c = payload.charAt(curr);
if (c == SEPARATOR) {
res[template[i]] = tmp;
curr++;
break;
} else {
tmp += c;
}
}
}
}
return res;
};
// 格式化参数
export const format = (protocol: Protocol, payload: InputPayload | OutputPayload) => {
payload.type = protocol.type;
return protocol.template
.map(i => getPayloadValueString(payload[i]))
.join(SEPARATOR);
};
// 获取默认值
export const getPayloadValueString = (value: unknown): any => {
if (value === undefined || value === null) {
return '';
}
return value;
};

View File

@@ -153,6 +153,9 @@ export interface ITerminalPanelManager {
// 终端会话管理器定义
export interface ITerminalSessionManager {
// 全部会话
sessions: Record<string, ITerminalSession>;
// 打开 ssh 会话
openSsh: (tab: TerminalPanelTabItem, domRef: XtermDomRef) => Promise<ISshSession>;
// 打开 sftp 会话