diff --git a/orion-visor-ui/src/utils/file.ts b/orion-visor-ui/src/utils/file.ts index ef1994e7..9fb24bc2 100644 --- a/orion-visor-ui/src/utils/file.ts +++ b/orion-visor-ui/src/utils/file.ts @@ -41,6 +41,17 @@ export function readFileText(e: File, encoding = 'UTF-8'): Promise { }); } +// 关闭 fileReader +export function closeFileReader(reader: FileReader) { + // 清理资源 + if (reader.readyState === FileReader.LOADING) { + reader.abort(); + } + reader.onload = null; + reader.onerror = null; + reader.onabort = null; +} + /** * 解析路径类型 */ diff --git a/orion-visor-ui/src/views/terminal/components/transfer/transfer-drawer.vue b/orion-visor-ui/src/views/terminal/components/transfer/transfer-drawer.vue index fed074de..933547cb 100644 --- a/orion-visor-ui/src/views/terminal/components/transfer/transfer-drawer.vue +++ b/orion-visor-ui/src/views/terminal/components/transfer/transfer-drawer.vue @@ -37,7 +37,7 @@ - {{ transferManager.transferList.filter(s => s.status === option.value).length }} + {{ transferTasks.filter(s => s.state.status === option.value).length }} @@ -48,15 +48,15 @@ max-height="100%" :hoverable="true" :bordered="false" - :data="transferManager.transferList"> + :data="transferTasks"> @@ -70,13 +70,13 @@ diff --git a/orion-visor-ui/src/views/terminal/components/transfer/transfer-item.vue b/orion-visor-ui/src/views/terminal/components/transfer/transfer-task.vue similarity index 65% rename from orion-visor-ui/src/views/terminal/components/transfer/transfer-item.vue rename to orion-visor-ui/src/views/terminal/components/transfer/transfer-task.vue index 691d92e5..43e39d1b 100644 --- a/orion-visor-ui/src/views/terminal/components/transfer/transfer-item.vue +++ b/orion-visor-ui/src/views/terminal/components/transfer/transfer-task.vue @@ -4,46 +4,46 @@
- - + +
- {{ item.name }} + :title="task.fileItem.name" + @click="copy(task.fileItem.name)"> + {{ task.fileItem.name }} - {{ getFileSize(item.currentSize) }} - / + {{ getFileSize(task.state.currentSize) }} + / - {{ getFileSize(item.totalSize) }} + {{ getFileSize(task.state.totalSize) }} - - {{ item.progress }}% + + {{ task.state.progress }}% - {{ item.parentPath }} + :title="task.fileItem.parentPath" + @click="copy(task.fileItem.parentPath)"> + {{ task.fileItem.parentPath }} - + :content="task.state.errorMessage"> - {{ item.errorMessage }} + {{ task.state.errorMessage }}
@@ -52,18 +52,20 @@
- + + + + :status="getDictValue(transferStatusKey, task.state.status, 'status')" + :percent="task.state.currentSize / task.state.totalSize" />
- +
@@ -74,21 +76,21 @@ diff --git a/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-upload-modal.vue b/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-upload-modal.vue index 01aef427..70faef55 100644 --- a/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-upload-modal.vue +++ b/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-upload-modal.vue @@ -121,8 +121,7 @@ // 获取上传的文件 const files = fileList.value.map(s => s.file as File); // 普通上传 - transferManager.addUpload(hostId.value, parentPath.value, files); - Message.success('已开始上传, 点击右侧传输列表查看进度'); + await transferManager.sftp.addUpload(hostId.value, parentPath.value, files); // 清空 handlerClear(); return true; diff --git a/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-view.vue b/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-view.vue index 8631f57d..5d932dbe 100644 --- a/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-view.vue +++ b/orion-visor-ui/src/views/terminal/components/view/sftp/sftp-view.vue @@ -160,7 +160,7 @@ }; // 下载文件 - const downloadFiles = (paths: Array, clear: boolean) => { + const downloadFiles = async (paths: Array, clear: boolean) => { if (!paths.length) { return; } @@ -172,10 +172,9 @@ if (clear) { selectFiles.value = []; } - Message.success('已开始下载, 点击右侧传输列表查看进度'); // 添加普通文件到下载队列 const normalFiles = files.filter(s => !s.isDir); - transferManager.addDownload(props.item.hostId as number, currentPath.value, normalFiles); + await transferManager.sftp.addDownload(props.item.hostId as number, currentPath.value, normalFiles); // 将文件夹展开普通文件 const directoryPaths = files.filter(s => s.isDir).map(s => s.path); if (directoryPaths.length) { @@ -283,7 +282,7 @@ if (!checkResult(result, msg)) { return; } - transferManager.addDownload(props.item.hostId as number, currentPath, list); + transferManager.sftp.addDownload(props.item.hostId as number, currentPath, list); }; // 初始化会话 diff --git a/orion-visor-ui/src/views/terminal/interfaces/session.ts b/orion-visor-ui/src/views/terminal/interfaces/session.ts index 59123bc3..e6b5a1b1 100644 --- a/orion-visor-ui/src/views/terminal/interfaces/session.ts +++ b/orion-visor-ui/src/views/terminal/interfaces/session.ts @@ -113,7 +113,7 @@ export interface ITerminalSession; + + protected constructor(type: string, source: string, + hostId: number, sessionKey: string, + fileItem: FileTransferItem, + state: Partial) { + this.type = type; + this.source = source; + this.fileId = nextId(10); + this.hostId = hostId; + this.sessionKey = sessionKey; + this.fileItem = fileItem; + this.state = reactive({ + currentSize: 0, + totalSize: fileItem.size, + progress: 0, + status: TransferStatus.WAITING, + errorMessage: undefined, + finished: false, + aborted: false, + ...state, + }); + } + + // 开始 + abstract start(): void; + + // 完成 + abstract finish(): void; + + // 失败 + abstract error(): void; + + // 中断 + abstract abort(): void; + + // 传输完成回调 + abstract onFinish(): void; + + // 传输失败回调 + abstract onError(msg: string | undefined): void; + + // 传输中断回调 + abstract onAbort(): void; + +} diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-base-transfer-task.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-base-transfer-task.ts new file mode 100644 index 00000000..5466c9e7 --- /dev/null +++ b/orion-visor-ui/src/views/terminal/service/transfer/sftp-base-transfer-task.ts @@ -0,0 +1,91 @@ +import type { ISetTransferClient, FileTransferItem } from '@/views/terminal/interfaces'; +import { TransferOperator, TransferStatus, TerminalMessages, TransferSource } from '../../types/const'; +import { getPath } from '@/utils/file'; +import BaseFileTransferTask from './base-file-transfer-task'; + +// sftp 传输任务一定义 +export default abstract class SftpBaseTransferTask extends BaseFileTransferTask implements ISetTransferClient { + + protected client?: WebSocket; + + protected constructor(type: string, + hostId: number, + fileItem: FileTransferItem) { + super(type, TransferSource.SFTP, hostId, undefined as unknown as string, fileItem, {}); + } + + // 设置传输客户端 + setClient(client: WebSocket) { + this.client = client; + } + + // 开始 + start() { + this.state.status = TransferStatus.TRANSFERRING; + // 发送开始信息 + this.client?.send(JSON.stringify({ + operator: TransferOperator.START, + type: this.type, + hostId: this.hostId, + path: getPath(this.fileItem.parentPath + '/' + this.fileItem.name), + paths: this.fileItem.paths, + })); + }; + + // 完成 + finish() { + this.state.finished = true; + this.state.progress = 100; + this.state.status = TransferStatus.SUCCESS; + // 发送完成的信息 + this.client?.send(JSON.stringify({ + operator: TransferOperator.FINISH, + type: this.type, + hostId: this.hostId, + })); + }; + + // 失败 + error() { + this.state.finished = true; + this.state.status = TransferStatus.ERROR; + // 发送上传失败的信息 + this.client?.send(JSON.stringify({ + operator: TransferOperator.ERROR, + type: this.type, + hostId: this.hostId, + })); + }; + + // 中断 + abort() { + this.state.aborted = true; + // 发送中断的信息 + this.client?.send(JSON.stringify({ + operator: TransferOperator.ABORT, + type: this.type, + hostId: this.hostId, + })); + } + + // 失败回调 + onError(msg: string | undefined) { + this.state.finished = true; + this.state.status = TransferStatus.ERROR; + this.state.errorMessage = msg || TerminalMessages.fileTransferError; + } + + // 完成回调 + onFinish() { + this.state.finished = true; + this.state.progress = 100; + this.state.status = TransferStatus.SUCCESS; + this.state.currentSize = this.state.totalSize; + }; + + // 中断回调 + onAbort() { + this.state.aborted = true; + }; + +} diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-file-download-task.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-file-download-task.ts new file mode 100644 index 00000000..65d9c70f --- /dev/null +++ b/orion-visor-ui/src/views/terminal/service/transfer/sftp-file-download-task.ts @@ -0,0 +1,57 @@ +import type { FileTransferItem, IFileDownloadTask } from '@/views/terminal/interfaces'; +import { TransferStatus, TerminalMessages } from '../../types/const'; +import { getFileName, openDownloadFile } from '@/utils/file'; +import { saveAs } from 'file-saver'; +import { getDownloadTransferUrl } from '@/api/terminal/terminal-sftp'; +import SftpBaseTransferTask from './sftp-base-transfer-task'; + +// sftp 下载任务实现 +export default class SftpFileDownloadTask extends SftpBaseTransferTask implements IFileDownloadTask { + + constructor(type: string, hostId: number, fileItem: FileTransferItem) { + super(type, hostId, fileItem); + } + + // 开始回调 + onStart(channelId: string, token: string) { + // 获取下载 url + const url = getDownloadTransferUrl(channelId, token); + // 打开 + openDownloadFile(url); + } + + // 进度回调 + onProgress(totalSize: number | undefined, currentSize: number | undefined) { + if (totalSize) { + this.state.totalSize = totalSize; + } + if (currentSize) { + this.state.currentSize = currentSize; + } + }; + + // 完成回调 + onFinish() { + super.onFinish(); + if (this.state.aborted) { + // 中断则不触发下载 + return; + } + if (this.state.totalSize === 0) { + // 空文件直接触发下载 + try { + // 触发下载 + saveAs(new Blob([], { + type: 'application/octet-stream' + }), getFileName(this.fileItem.name)); + this.state.status = TransferStatus.SUCCESS; + } catch (e) { + this.state.status = TransferStatus.ERROR; + this.state.errorMessage = TerminalMessages.fileSaveError; + } + } else { + this.state.status = TransferStatus.SUCCESS; + } + } + +} diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-file-upload-task.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-file-upload-task.ts new file mode 100644 index 00000000..4821534d --- /dev/null +++ b/orion-visor-ui/src/views/terminal/service/transfer/sftp-file-upload-task.ts @@ -0,0 +1,74 @@ +import type { FileTransferItem, IFileUploadTask } from '@/views/terminal/interfaces'; +import { closeFileReader } from '@/utils/file'; +import SftpBaseTransferTask from './sftp-base-transfer-task'; + +// 512 KB +export const PART_SIZE = 512 * 1024; + +// sftp 上传任务实现 +export default class SftpFileUploadTask extends SftpBaseTransferTask implements IFileUploadTask { + + private currentPart: number; + private readonly totalPart: number; + + constructor(type: string, hostId: number, fileItem: FileTransferItem) { + super(type, hostId, fileItem); + this.currentPart = 0; + this.totalPart = Math.ceil(fileItem.size / PART_SIZE); + } + + // 上传完成 + finish() { + super.finish(); + // 释放资源 + this.fileItem.file = undefined; + } + + // 上传下一个分片 + async onNextPart() { + // 完成或者中断直接跳过 + if (this.state.aborted || this.state.finished) { + return; + } + if (this.hasNextPart()) { + try { + // 有下一个分片则上传 + await this.uploadNextPart(); + } catch (e) { + // 读取文件失败 + this.error(); + } + } else { + this.finish(); + } + } + + // 执行上传下一分片 + private async uploadNextPart() { + // 读取数据 + const start = this.currentPart * PART_SIZE; + const end = Math.min(this.fileItem.size, start + PART_SIZE); + const chunk = (this.fileItem.file as File).slice(start, end); + const reader = new FileReader(); + try { + const arrayBuffer = await new Promise((resolve, reject) => { + reader.onload = () => resolve(reader.result); + reader.onerror = (error) => reject(error); + reader.readAsArrayBuffer(chunk); + }); + // 发送数据 + this.client?.send(arrayBuffer as ArrayBuffer); + this.currentPart++; + this.state.currentSize += (end - start); + } finally { + // 释放资源 + closeFileReader(reader); + } + } + + // 是否有下一个分片 + private hasNextPart() { + return this.currentPart < this.totalPart; + } + +} diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-downloader.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-downloader.ts deleted file mode 100644 index 62c67469..00000000 --- a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-downloader.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { SftpTransferItem } from '@/views/terminal/interfaces'; -import { TransferStatus } from '../../types/const'; -import { getFileName, openDownloadFile } from '@/utils/file'; -import { saveAs } from 'file-saver'; -import { getDownloadTransferUrl } from '@/api/terminal/terminal-sftp'; -import SftpTransferHandler from './sftp-transfer-handler'; - -// sftp 下载器实现 -export default class SftpTransferDownloader extends SftpTransferHandler { - - constructor(type: string, item: SftpTransferItem, client: WebSocket) { - super(type, item, client); - } - - // 开始回调 - onStart(channelId: string, token: string) { - super.onStart(channelId, token); - // 获取下载 url - const url = getDownloadTransferUrl(channelId, token); - // 打开 - openDownloadFile(url); - } - - // 完成回调 - onFinish() { - super.onFinish(); - if (this.aborted) { - // 中断则不触发下载 - return; - } - if (this.item.totalSize === 0) { - // 空文件直接触发下载 - try { - // 触发下载 - saveAs(new Blob([], { - type: 'application/octet-stream' - }), getFileName(this.item.name)); - this.item.status = TransferStatus.SUCCESS; - } catch (e) { - this.item.status = TransferStatus.ERROR; - this.item.errorMessage = '保存失败'; - } - } else { - this.item.status = TransferStatus.SUCCESS; - } - } - -} diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-handler.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-handler.ts deleted file mode 100644 index f195cc47..00000000 --- a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-handler.ts +++ /dev/null @@ -1,115 +0,0 @@ -import type { ISftpTransferHandler, SftpTransferItem } from '@/views/terminal/interfaces'; -import { TransferOperator, TransferStatus } from '../../types/const'; -import { getPath } from '@/utils/file'; - -// sftp 传输处理器定义 -export default abstract class SftpTransferHandler implements ISftpTransferHandler { - - public type: string; - public finished: boolean; - public aborted: boolean; - protected client: WebSocket; - protected item: SftpTransferItem; - - protected constructor(type: string, item: SftpTransferItem, client: WebSocket) { - this.type = type; - this.finished = false; - this.aborted = false; - this.item = item; - this.client = client; - } - - // 开始 - start() { - this.item.status = TransferStatus.TRANSFERRING; - // 发送开始信息 - this.client?.send(JSON.stringify({ - operator: TransferOperator.START, - type: this.type, - path: getPath(this.item.parentPath + '/' + this.item.name), - hostId: this.item.hostId, - paths: this.item.paths, - })); - }; - - // 完成 - finish() { - this.finished = true; - this.item.status = TransferStatus.SUCCESS; - // 发送完成的信息 - this.client?.send(JSON.stringify({ - operator: TransferOperator.FINISH, - type: this.type, - hostId: this.item.hostId - })); - }; - - // 失败 - error() { - this.finished = true; - this.item.status = TransferStatus.ERROR; - // 发送上传失败的信息 - this.client?.send(JSON.stringify({ - operator: TransferOperator.ERROR, - type: this.type, - hostId: this.item.hostId - })); - }; - - // 中断 - abort() { - this.aborted = true; - // 发送中断的信息 - this.client?.send(JSON.stringify({ - operator: TransferOperator.ABORT, - type: this.type, - hostId: this.item.hostId - })); - } - - // 是否有下一个分片 - hasNextPart() { - return false; - }; - - // 下一页分片回调 - onNextPart() { - return undefined as unknown as any; - }; - - // 开始回调 - onStart(channelId: string, token: string) { - }; - - // 进度回调 - onProgress(totalSize: number | undefined, currentSize: number | undefined) { - if (this.item) { - if (totalSize) { - this.item.totalSize = totalSize; - } - if (currentSize) { - this.item.currentSize = currentSize; - } - } - }; - - // 失败回调 - onError(msg: string | undefined) { - this.finished = true; - this.item.status = TransferStatus.ERROR; - this.item.errorMessage = msg || '传输失败'; - } - - // 完成回调 - onFinish() { - this.finished = true; - this.item.status = TransferStatus.SUCCESS; - this.item.currentSize = this.item.totalSize; - }; - - // 中断回调 - onAbort() { - this.aborted = true; - }; - -} diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-manager.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-manager.ts index 2ae31cff..1f1d3c34 100644 --- a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-manager.ts +++ b/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-manager.ts @@ -1,105 +1,89 @@ -import type { ISftpTransferHandler, ISftpTransferManager, SftpFile, SftpTransferItem, TransferOperatorResponse } from '@/views/terminal/interfaces'; +import type { + FileTransferTaskType, + ISetTransferClient, + ISftpTransferManager, + MaybeFileTransferTask, + SftpFile, + TransferOperatorResponse +} from '@/views/terminal/interfaces'; import { TerminalMessages, TransferReceiver, TransferStatus, TransferType } from '../../types/const'; import { Message } from '@arco-design/web-vue'; import { getTerminalTransferToken, openTerminalTransferChannel } from '@/api/terminal/terminal'; -import { nextId } from '@/utils'; -import SftpTransferUploader from './sftp-transfer-uploader'; -import SftpTransferDownloader from './sftp-transfer-downloader'; +import BaseTransferManager from './base-transfer-manager'; +import SftpFileUploadTask from './sftp-file-upload-task'; +import SftpFileDownloadTask from './sftp-file-download-task'; // sftp 传输管理器实现 -export default class SftpTransferManager implements ISftpTransferManager { +export default class SftpTransferManager extends BaseTransferManager implements ISftpTransferManager { private client?: WebSocket; private run: boolean; - private progressIntervalId?: any; - - private currentItem?: SftpTransferItem; - - private currentTransfer?: ISftpTransferHandler; - - public transferList: Array; + private currentTask?: FileTransferTaskType; constructor() { + super(); this.run = false; - this.transferList = []; } // 添加上传任务 - addUpload(hostId: number, parentPath: string, files: Array) { - // 转为上传任务 - const items = files.map(s => { - return { - fileId: nextId(10), - type: TransferType.UPLOAD, - hostId: hostId, - name: s.webkitRelativePath || s.name, - currentSize: 0, - totalSize: s.size, - progress: 0, - status: TransferStatus.WAITING, + async addUpload(hostId: number, parentPath: string, files: Array) { + Message.info(TerminalMessages.fileUploading); + // 创建任务 + for (let file of files) { + const task = new SftpFileUploadTask(TransferType.UPLOAD, hostId, { + name: file.webkitRelativePath || file.name, parentPath: parentPath, - file: s - }; - }); + size: file.size, + file, + }); + this.tasks.push(task); + } // 开始传输 - this.startTransfer(items); + await this.startTransfer(); } // 添加下载任务 - addDownload(hostId: number, currentPath: string, files: Array) { + async addDownload(hostId: number, currentPath: string, files: Array) { + Message.info(TerminalMessages.fileDownloading); let pathIndex = currentPath === '/' ? 1 : currentPath.length + 1; - // 转为下载文件 - const items = files.map(s => { - return { - fileId: nextId(10), - type: TransferType.DOWNLOAD, - hostId: hostId, - name: s.path.substring(pathIndex), + for (let file of files) { + // 创建任务 + const task = new SftpFileDownloadTask(TransferType.DOWNLOAD, hostId, { + name: file.path.substring(pathIndex), parentPath: currentPath, - currentSize: 0, - totalSize: s.size, - progress: 0, - status: TransferStatus.WAITING, - }; - }) as Array; + size: file.size, + }); + this.tasks.push(task); + } // 开始传输 - this.startTransfer(items); + await this.startTransfer(); } // 开始传输 - private startTransfer(items: Array) { - this.transferList.push(...items); + private async startTransfer() { // 开始传输 if (!this.run) { - this.openClient(); + await this.openClient(); } + // 开始计算进度 + this.resetProgressTimer(); } // 取消传输 cancelTransfer(fileId: string): void { - const index = this.transferList.findIndex(s => s.fileId === fileId); + const index = this.tasks.findIndex(s => s.fileId === fileId); if (index === -1) { return; } - const item = this.transferList[index]; - if (item.status === TransferStatus.TRANSFERRING) { + const task = this.tasks[index]; + if (task.state.status === TransferStatus.TRANSFERRING) { // 传输中则中断传输 - this.currentTransfer?.abort(); + this.currentTask?.abort(); } // 从列表中移除 - this.transferList.splice(index, 1); - } - - // 取消全部传输 - cancelAllTransfer(): void { - // 从列表中移除非传输中的元素 - this.transferList.reduceRight((_, value: SftpTransferItem, index: number) => { - if (value.status !== TransferStatus.TRANSFERRING) { - this.transferList.splice(index, 1); - } - }, null as any); + this.tasks.splice(index, 1); } // 打开会话 @@ -115,11 +99,10 @@ export default class SftpTransferManager implements ISftpTransferManager { Message.error('会话打开失败'); console.error('transfer error', e); // 将等待中和传输中任务修改为失败状态 - this.transferList.filter(s => { - return s.status === TransferStatus.WAITING - || s.status === TransferStatus.TRANSFERRING; + this.tasks.filter(s => { + return s.state.status === TransferStatus.WAITING || s.state.status === TransferStatus.TRANSFERRING; }).forEach(s => { - s.status = TransferStatus.ERROR; + s.state.status = TransferStatus.ERROR; }); // 关闭会话 this.close(); @@ -132,100 +115,62 @@ export default class SftpTransferManager implements ISftpTransferManager { }; // 处理消息 this.client.onmessage = this.resolveMessage.bind(this); - // 计算传输进度 - this.progressIntervalId = setInterval(this.calcProgress.bind(this), 500); // 打开后自动传输下一个任务 this.transferNextItem(); } - // 计算传输进度 - private calcProgress() { - this.transferList.forEach(item => { - if (item.totalSize != 0) { - item.progress = (item.currentSize / item.totalSize * 100).toFixed(2); - } - }); - } - // 传输下一条任务 private transferNextItem() { - this.currentTransfer = undefined; - // 释放内存 - if (this.currentItem) { - this.currentItem.file = null as unknown as File; - } // 获取任务 - this.currentItem = this.transferList.find(s => s.status === TransferStatus.WAITING); - if (this.currentItem) { - // 创建传输器 - this.currentTransfer = this.createTransfer(); + this.currentTask = this.tasks.find(s => s.state.status === TransferStatus.WAITING); + if (this.currentTask) { + // 设置 client + (this.currentTask as unknown as ISetTransferClient).setClient(this.client as WebSocket); // 开始 - this.currentTransfer?.start(); + this.currentTask?.start(); } else { // 无任务关闭会话 this.client?.close(); } } - // 创建传输器 - private createTransfer(): ISftpTransferHandler | undefined { - if (!this.currentItem) { - return undefined; - } - if (this.currentItem.type === TransferType.UPLOAD) { - // 上传 - return new SftpTransferUploader(TransferType.UPLOAD, this.currentItem, this.client as WebSocket); - } else if (this.currentItem.type === TransferType.DOWNLOAD) { - // 下载 - return new SftpTransferDownloader(TransferType.DOWNLOAD, this.currentItem, this.client as WebSocket); - } - } - // 接收消息 private async resolveMessage(message: MessageEvent) { // 文本消息 const data = JSON.parse(message.data) as TransferOperatorResponse; if (data.type === TransferReceiver.NEXT_PART) { // 接收下一块数据回调 - await this.currentTransfer?.onNextPart(); + await (this.currentTask as MaybeFileTransferTask)?.onNextPart?.(); } else if (data.type === TransferReceiver.START) { - // 开始回调 - this.currentTransfer?.onStart(data.channelId as string, data.transferToken as string); + // 开始下载回调 + (this.currentTask as MaybeFileTransferTask)?.onStart?.(data.channelId as string, data.transferToken as string); } else if (data.type === TransferReceiver.PROGRESS) { - // 进度回调 - this.currentTransfer?.onProgress(data.totalSize, data.currentSize); + // 下载进度回调 + (this.currentTask as MaybeFileTransferTask)?.onProgress?.(data.totalSize, data.currentSize); } else if (data.type === TransferReceiver.FINISH) { // 完成回调 - this.currentTransfer?.onFinish(); + this.currentTask?.onFinish(); // 开始下一个传输任务 this.transferNextItem(); } else if (data.type === TransferReceiver.ERROR) { // 失败回调 - this.currentTransfer?.onError(data.msg); + this.currentTask?.onError(data.msg); // 开始下一个传输任务 this.transferNextItem(); } else if (data.type === TransferReceiver.ABORT) { // 中断回调 - this.currentTransfer?.onAbort(); + this.currentTask?.onAbort(); // 开始下一个传输任务 this.transferNextItem(); } } // 关闭 释放资源 - private close() { + protected close() { // 重置 run 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 = TerminalMessages.sessionClosed; - } - }); + // 关闭 + super.close(); } } diff --git a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-uploader.ts b/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-uploader.ts deleted file mode 100644 index c507b1d4..00000000 --- a/orion-visor-ui/src/views/terminal/service/transfer/sftp-transfer-uploader.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { SftpTransferItem } from '@/views/terminal/interfaces'; -import SftpTransferHandler from './sftp-transfer-handler'; - -// 512 KB -export const PART_SIZE = 512 * 1024; - -// sftp 上传器实现 -export default class SftpTransferUploader extends SftpTransferHandler { - - private currentPart: number; - private readonly totalPart: number; - private file: File; - - constructor(type: string, item: SftpTransferItem, client: WebSocket) { - super(type, item, client); - this.file = item.file; - this.currentPart = 0; - this.totalPart = Math.ceil(item.file.size / PART_SIZE); - } - - // 是否有下一个分片 - hasNextPart() { - return this.currentPart < this.totalPart; - } - - // 上传下一个分片 - async onNextPart() { - super.onNextPart(); - // 完成或者中断直接跳过 - if (this.aborted || this.finished) { - return; - } - if (this.hasNextPart()) { - try { - // 有下一个分片则上传 - await this.doUploadNextPart(); - } catch (e) { - // 读取文件失败 - this.error(); - } - } else { - this.finish(); - } - } - - // 执行上传下一分片 - private async doUploadNextPart() { - // 读取数据 - const start = this.currentPart * PART_SIZE; - const end = Math.min(this.file.size, start + PART_SIZE); - const chunk = this.file.slice(start, end); - const reader = new FileReader(); - const arrayBuffer = await new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = (error) => reject(error); - reader.readAsArrayBuffer(chunk); - }); - // 发送数据 - this.client?.send(arrayBuffer as ArrayBuffer); - this.currentPart++; - this.item.currentSize += (end - start); - } - -} diff --git a/orion-visor-ui/src/views/terminal/types/const.ts b/orion-visor-ui/src/views/terminal/types/const.ts index 66327d8b..4750ebc2 100644 --- a/orion-visor-ui/src/views/terminal/types/const.ts +++ b/orion-visor-ui/src/views/terminal/types/const.ts @@ -98,6 +98,10 @@ export const TerminalMessages = { waitingReconnect: '输入回车重新连接...', loggedElsewhere: '该账号已在另一台设备登录', rdpConnectTimeout: '请检查远程计算机网络及其他配置是否正常', + fileTransferError: '传输失败', + fileSaveError: '保存失败', + fileUploading: '已开始上传, 点击右侧传输列表查看进度', + fileDownloading: '已开始下载, 点击右侧传输列表查看进度', }; // 文件类型 @@ -457,6 +461,12 @@ export const TransferType = { DOWNLOAD: 'download', }; +// 传输来源 +export const TransferSource = { + SFTP: 'sftp', + RDP: 'rdp', +}; + // 传输操作 export const TransferOperator = { START: 'start',