🔨 文件下载.

This commit is contained in:
lijiahangmax
2024-02-23 00:47:52 +08:00
parent 3d38246c25
commit c19680b213
30 changed files with 686 additions and 224 deletions

View File

@@ -57,6 +57,14 @@ export function getPath(path: string) {
.replace(new RegExp('/+', 'g'), '/');
}
/**
* 获取文件名
*/
export function getFileName(path: string) {
path = getPath(path);
return path.substring(path.lastIndexOf('/') + 1);
}
/**
* 获取父级路径
*/

View File

@@ -156,9 +156,11 @@
// 添加普通文件到下载队列
const normalFiles = files.filter(s => !s.isDir);
transferManager.addDownload(props.tab.hostId as number, currentPath.value, normalFiles);
// 将文件夹转为普通文件
const directoryFiles = files.filter(s => s.isDir);
// TODO
// 将文件夹展开普通文件
const directoryPaths = files.filter(s => s.isDir).map(s => s.path);
if (directoryPaths.length) {
session.value?.downloadFlatDirectory(currentPath.value, directoryPaths);
}
};
// 连接成功回调
@@ -224,6 +226,12 @@
Message.success('保存成功');
};
// 接收下载文件夹展开文件响应
const resolveDownloadFlatDirectory = (currentPath: string, list: Array<SftpFile>) => {
setTableLoading(false);
transferManager.addDownload(props.tab.hostId as number, currentPath, list);
};
// 初始化会话
onMounted(async () => {
// 创建终端处理器
@@ -236,6 +244,7 @@
resolveSftpMove: resolveFileAction,
resolveSftpRemove: resolveFileAction,
resolveSftpChmod: resolveFileAction,
resolveDownloadFlatDirectory,
resolveSftpGetContent,
resolveSftpSetContent,
});

View File

@@ -190,6 +190,7 @@
text-overflow: ellipsis;
width: fit-content;
max-width: 100%;
white-space: nowrap;
}
.transfer-progress, .target-path, .error-message {

View File

@@ -107,6 +107,16 @@ export default class SftpSession implements ISftpSession {
});
};
// 下载文件夹展开文件
downloadFlatDirectory(currentPath: string, path: string[]) {
this.resolver.setLoading(true);
this.channel.send(InputProtocol.SFTP_DOWNLOAD_FLAT_DIRECTORY, {
sessionId: this.sessionId,
currentPath,
path: path.join('|')
});
}
// 获取内容
getContent(path: string) {
this.channel.send(InputProtocol.SFTP_GET_CONTENT, {

View File

@@ -0,0 +1,79 @@
import type { ISftpTransferDownloader, SftpTransferItem } from '../types/terminal.type';
import { TransferOperatorType, TransferStatus } from '../types/terminal.const';
import { getFileName, getPath } from '@/utils/file';
import { saveAs } from 'file-saver';
// sftp 上传器实现
export default class SftpTransferDownloader implements ISftpTransferDownloader {
public abort: boolean;
private blobArr: Array<Blob>;
private client: WebSocket;
private item: SftpTransferItem;
constructor(item: SftpTransferItem, client: WebSocket) {
this.abort = false;
this.blobArr = [];
this.item = item;
this.client = client;
}
// 开始下载
startDownload() {
this.item.status = TransferStatus.TRANSFERRING;
// 发送开始下载信息
this.client?.send(JSON.stringify({
type: TransferOperatorType.DOWNLOAD_START,
path: getPath(this.item.parentPath + '/' + this.item.name),
hostId: this.item.hostId
}));
}
// 接收 blob
resolveBlob(blob: Blob) {
this.blobArr.push(blob);
this.item.currentSize += blob.size;
}
// 下载完成
downloadFinish() {
console.log(this.abort);
if (this.abort) {
// 中断则不触发下载
return;
}
// fixme bug
try {
console.log('saveAs');
// 触发下载
saveAs(new Blob(this.blobArr, {
type: 'application/octet-stream'
}), getFileName(this.item.name));
this.item.status = TransferStatus.SUCCESS;
} catch (e) {
this.item.status = TransferStatus.ERROR;
this.item.errorMessage = '保存失败';
} finally {
this.blobArr = [];
}
}
// 下载失败
downloadError(msg: string | undefined) {
this.blobArr = [];
this.item.status = TransferStatus.ERROR;
this.item.errorMessage = msg || '下载失败';
}
// 下载中断
downloadAbort() {
this.abort = true;
// 发送下载中断信息
this.client?.send(JSON.stringify({
type: TransferOperatorType.DOWNLOAD_ABORT,
hostId: this.item.hostId
}));
}
}

View File

@@ -1,10 +1,11 @@
import type { ISftpTransferManager, ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
import { SftpFile, TransferOperatorResponse } from '../types/terminal.type';
import { ISftpTransferDownloader, SftpFile, TransferOperatorResponse } from '../types/terminal.type';
import { TransferReceiverType, TransferStatus, TransferType } from '../types/terminal.const';
import { Message } from '@arco-design/web-vue';
import { getTerminalAccessToken } from '@/api/asset/host-terminal';
import SftpTransferUploader from '@/views/host/terminal/handler/sftp-transfer-uploader';
import { nextId } from '@/utils';
import SftpTransferUploader from './sftp-transfer-uploader';
import SftpTransferDownloader from './sftp-transfer-downloader';
export const wsBase = import.meta.env.VITE_WS_BASE_URL;
@@ -19,6 +20,8 @@ export default class SftpTransferManager implements ISftpTransferManager {
private currentUploader?: ISftpTransferUploader;
private currentDownloader?: ISftpTransferDownloader;
public transferList: Array<SftpTransferItem>;
constructor() {
@@ -57,7 +60,8 @@ export default class SftpTransferManager implements ISftpTransferManager {
fileId: nextId(10),
type: TransferType.DOWNLOAD,
hostId: hostId,
name: s.path.substring(currentPath.length),
name: s.path.substring(currentPath.length + 1),
parentPath: currentPath,
currentSize: 0,
totalSize: s.size,
status: TransferStatus.WAITING,
@@ -81,6 +85,8 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 传输中则中断传输
if (this.currentUploader) {
this.currentUploader.uploadAbort();
} else if (this.currentDownloader) {
this.currentDownloader.downloadAbort();
}
}
// 从列表中移除
@@ -121,6 +127,7 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 传输下一条任务
private transferNextItem() {
this.currentUploader = undefined;
this.currentDownloader = undefined;
// 释放内存
if (this.currentItem) {
this.currentItem.file = null as unknown as File;
@@ -144,13 +151,27 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 接收消息
private async resolveMessage(message: MessageEvent) {
const data = JSON.parse(message.data) as TransferOperatorResponse;
if (data.type === TransferReceiverType.NEXT_BLOCK) {
// 接收下一块上传数据
await this.resolveNextBlock();
} else if (data.type === TransferReceiverType.NEXT_TRANSFER) {
// 接收接收下一个传输任务处理完成
this.resolveNextTransfer(data);
if (message.data instanceof Blob) {
// 二进制消息 下载数据
this.resolveDownloadBlob(message.data);
} else {
// 文本消息
const data = JSON.parse(message.data) as TransferOperatorResponse;
if (data.type === TransferReceiverType.NEXT_TRANSFER
|| data.type === TransferReceiverType.UPLOAD_FINISH
|| data.type === TransferReceiverType.UPLOAD_ERROR) {
// 执行下一个传输任务
this.resolveNextTransfer(data);
} else if (data.type === TransferReceiverType.UPLOAD_NEXT_BLOCK) {
// 接收下一块上传数据
await this.resolveUploadNextBlock();
} else if (data.type === TransferReceiverType.DOWNLOAD_FINISH) {
// 下载完成
this.resolveDownloadFinish();
} else if (data.type === TransferReceiverType.DOWNLOAD_ERROR) {
// 下载失败
this.resolveDownloadError(data.msg);
}
}
}
@@ -164,11 +185,28 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 下载文件
private uploadDownload() {
// TODO
// 创建下载器
this.currentDownloader = new SftpTransferDownloader(this.currentItem as SftpTransferItem, this.client as WebSocket);
// 开始下载
this.currentDownloader.startDownload();
}
// 接收下一块上传数据
private async resolveNextBlock() {
// 接收下一个传输任务响应
private resolveNextTransfer(data: TransferOperatorResponse) {
if (this.currentItem) {
if (data.success) {
this.currentItem.status = TransferStatus.SUCCESS;
} else {
this.currentItem.status = TransferStatus.ERROR;
this.currentItem.errorMessage = data.msg || '传输失败';
}
}
// 开始下一个传输任务
this.transferNextItem();
}
// 接收下一块上传数据响应
private async resolveUploadNextBlock() {
// 只可能为上传并且成功
if (!this.currentUploader) {
return;
@@ -189,16 +227,21 @@ export default class SftpTransferManager implements ISftpTransferManager {
}
}
// 接收下一个传输任务
private resolveNextTransfer(data: TransferOperatorResponse) {
if (this.currentItem) {
if (data.success) {
this.currentItem.status = TransferStatus.SUCCESS;
} else {
this.currentItem.status = TransferStatus.ERROR;
this.currentItem.errorMessage = data.msg || '上传失败';
}
}
// 接收下载数据
private resolveDownloadBlob(blob: Blob) {
this.currentDownloader?.resolveBlob(blob);
}
// 接收下载完成响应
private resolveDownloadFinish() {
this.currentDownloader?.downloadFinish();
// 开始下一个传输任务
this.transferNextItem();
}
// 接收下载失败响应
private resolveDownloadError(msg: string | undefined) {
this.currentDownloader?.downloadError(msg);
// 开始下一个传输任务
this.transferNextItem();
}

View File

@@ -7,8 +7,8 @@ export const BLOCK_SIZE = 1024 * 1024;
// sftp 上传器实现
export default class SftpTransferUploader implements ISftpTransferUploader {
public abort: boolean;
public finish: boolean;
public abort: boolean;
private currentBlock: number;
private totalBlock: number;
private client: WebSocket;

View File

@@ -154,6 +154,13 @@ export default class TerminalOutputProcessor implements ITerminalOutputProcessor
session && session.resolver.resolveSftpChmod(result, msg);
}
// 处理 SFTP 下载文件夹展开文件
processDownloadFlatDirectory({ sessionId, currentPath, body }: OutputPayload): void {
// 获取会话
const session = this.sessionManager.getSession<ISftpSession>(sessionId);
session && session.resolver.resolveDownloadFlatDirectory(currentPath, JSON.parse(body));
}
// 处理 SFTP 获取文件内容
processSftpGetContent({ sessionId, path, result, content }: OutputPayload): void {
// 获取会话

View File

@@ -304,12 +304,18 @@ export const TransferOperatorType = {
UPLOAD_START: 'uploadStart',
UPLOAD_FINISH: 'uploadFinish',
UPLOAD_ERROR: 'uploadError',
DOWNLOAD_START: 'downloadStart',
DOWNLOAD_ABORT: 'downloadAbort',
};
// 传输响应类型
export const TransferReceiverType = {
NEXT_BLOCK: 'nextBlock',
NEXT_TRANSFER: 'nextTransfer',
UPLOAD_NEXT_BLOCK: 'uploadNextBlock',
UPLOAD_FINISH: 'uploadFinish',
UPLOAD_ERROR: 'uploadError',
DOWNLOAD_FINISH: 'downloadFinish',
DOWNLOAD_ERROR: 'downloadError',
};
// 打开 sshSettingModal key

View File

@@ -60,6 +60,11 @@ export const InputProtocol = {
type: 'cm',
template: ['type', 'sessionId', 'path', 'mod']
},
// SFTP 修改文件权限
SFTP_DOWNLOAD_FLAT_DIRECTORY: {
type: 'df',
template: ['type', 'sessionId', 'currentPath', 'path']
},
// SFTP 获取内容
SFTP_GET_CONTENT: {
type: 'gc',
@@ -140,6 +145,12 @@ export const OutputProtocol = {
template: ['type', 'sessionId', 'result', 'msg'],
processMethod: 'processSftpChmod'
},
// SFTP 修改文件权限
SFTP_DOWNLOAD_FLAT_DIRECTORY: {
type: 'df',
template: ['type', 'sessionId', 'currentPath', 'body'],
processMethod: 'processDownloadFlatDirectory'
},
// SFTP 获取文件内容
SFTP_GET_CONTENT: {
type: 'gc',

View File

@@ -190,6 +190,8 @@ export interface ITerminalOutputProcessor {
processSftpRemove: (payload: OutputPayload) => void;
// 处理 SFTP 修改文件权限
processSftpChmod: (payload: OutputPayload) => void;
// 处理 SFTP 下载文件夹展开文件
processDownloadFlatDirectory: (payload: OutputPayload) => void;
// 处理 SFTP 获取文件内容
processSftpGetContent: (payload: OutputPayload) => void;
// 处理 SFTP 修改文件内容
@@ -325,6 +327,8 @@ export interface ISftpSession extends ITerminalSession {
remove: (path: string[]) => void;
// 修改权限
chmod: (path: string, mod: number) => void;
// 下载文件夹展开文件
downloadFlatDirectory: (currentPath: string, path: string[]) => void;
// 获取内容
getContent: (path: string) => void;
// 修改内容
@@ -349,6 +353,8 @@ export interface ISftpSessionResolver {
resolveSftpRemove: (result: string, msg: string) => void;
// 接收修改文件权限响应
resolveSftpChmod: (result: string, msg: string) => void;
// 接收下载文件夹展开文件响应
resolveDownloadFlatDirectory: (currentPath: string, list: Array<SftpFile>) => void;
// 接收获取文件内容响应
resolveSftpGetContent: (path: string, result: string, content: string) => void;
// 接收修改文件内容响应
@@ -400,6 +406,22 @@ export interface ISftpTransferUploader {
uploadAbort: () => void;
}
// sftp 下载器定义
export interface ISftpTransferDownloader {
// 是否中断
abort: boolean;
// 开始下载
startDownload: () => void;
// 接收 blob
resolveBlob: (blob: Blob) => void;
// 下载完成
downloadFinish: () => void;
// 下载失败
downloadError: (msg: string | undefined) => void;
// 下载中断
downloadAbort: () => void;
}
// sftp 上传文件项
export interface SftpTransferItem {
fileId: string;