🔨 下载文件.

This commit is contained in:
lijiahang
2024-02-22 19:20:14 +08:00
parent 1711981d80
commit 3d38246c25
17 changed files with 221 additions and 51 deletions

View File

@@ -123,6 +123,14 @@ public enum InputTypeEnum {
new String[]{"type", "sessionId", "path", "mod"},
SftpChangeModRequest.class),
/**
* SFTP 下载文件夹 flat
*/
SFTP_DOWNLOAD_DIRECTORY_FLAT("df",
SftpDownloadDirectoryFlatHandler.class,
new String[]{"type", "sessionId", "currentPath", "path"},
SftpDownloadDirectoryFlatRequest.class),
/**
* SFTP 获取内容
*/
@@ -139,10 +147,6 @@ public enum InputTypeEnum {
new String[]{"type", "sessionId", "path", "content"},
SftpSetContentRequest.class),
// TODO
// UPLOAD
// DOWNLOAD
;
private static final char SEPARATOR = '|';

View File

@@ -75,6 +75,11 @@ public enum OutputTypeEnum {
*/
SFTP_CHMOD("cm", "${type}|${sessionId}|${result}|${msg}"),
/**
* SFTP 下载文件夹 flat
*/
SFTP_DOWNLOAD_DIRECTORY_FLAT("df", "${type}|${sessionId}|${currentPath}|${body}"),
/**
* SFTP 获取文件内容
*/

View File

@@ -0,0 +1,51 @@
package com.orion.ops.module.asset.handler.host.terminal.handler;
import com.orion.ops.framework.common.enums.BooleanBit;
import com.orion.ops.module.asset.handler.host.terminal.enums.OutputTypeEnum;
import com.orion.ops.module.asset.handler.host.terminal.model.request.SftpDownloadDirectoryFlatRequest;
import com.orion.ops.module.asset.handler.host.terminal.model.response.SftpDownloadDirectoryFlatResponse;
import com.orion.ops.module.asset.handler.host.terminal.session.ISftpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketSession;
/**
* sftp 下载文件夹 flat
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/2/19 11:13
*/
@Slf4j
@Component
public class SftpDownloadDirectoryFlatHandler extends AbstractTerminalHandler<SftpDownloadDirectoryFlatRequest> {
@Override
public void handle(WebSocketSession channel, SftpDownloadDirectoryFlatRequest payload) {
// 获取会话
ISftpSession session = terminalManager.getSession(channel.getId(), payload.getSessionId());
String path = payload.getPath();
log.info("SftpDownloadDirectoryFlatHandler-handle session: {}, path: {}", payload.getSessionId(), path);
Exception ex = null;
// 获取文件夹内的全部文件
try {
// TODO
} catch (Exception e) {
log.error("SftpDownloadDirectoryFlatHandler-handle error", e);
ex = e;
}
// 返回
this.send(channel,
OutputTypeEnum.SFTP_DOWNLOAD_DIRECTORY_FLAT,
SftpDownloadDirectoryFlatResponse.builder()
.sessionId(payload.getSessionId())
.currentPath(payload.getPath())
// TODO
.body("")
.result(BooleanBit.of(ex == null).getValue())
.msg(this.getErrorMessage(ex))
.build());
}
}

View File

@@ -103,6 +103,7 @@ public class TerminalConnectHandler extends AbstractTerminalHandler<TerminalConn
try {
// 连接配置
TerminalConfig config = TerminalConfig.builder()
.hostId(connect.getHostId())
.charset(connect.getCharset())
.fileNameCharset(connect.getFileNameCharset())
.fileContentCharset(connect.getFileContentCharset())

View File

@@ -22,6 +22,9 @@ import lombok.NoArgsConstructor;
@Schema(name = "TerminalConfig", description = "主机终端连接参数")
public class TerminalConfig {
@Schema(description = "主机id")
private Long hostId;
@Schema(description = "cols")
private Integer cols;

View File

@@ -0,0 +1,30 @@
package com.orion.ops.module.asset.handler.host.terminal.model.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* sftp 下载文件夹 flat 实体对象
* <p>
* i|eff00a1|currentPath|path
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/2/6 13:31
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "SftpDownloadDirectoryFlatRequest", description = "sftp 下载文件夹 flat 实体对象")
public class SftpDownloadDirectoryFlatRequest extends SftpBaseRequest {
@Schema(description = "当前路径")
private Integer currentPath;
}

View File

@@ -0,0 +1,31 @@
package com.orion.ops.module.asset.handler.host.terminal.model.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
/**
* sftp 下载文件夹 flat 实体对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2024/2/6 16:20
*/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@Schema(name = "SftpDownloadDirectoryFlatResponse", description = "sftp 下载文件夹 flat 实体对象")
public class SftpDownloadDirectoryFlatResponse extends SftpBaseResponse {
@Schema(description = "currentPath")
private String currentPath;
@Schema(description = "body")
private String body;
}

View File

@@ -32,8 +32,6 @@ import java.util.stream.Collectors;
@Slf4j
public class SftpSession extends TerminalSession implements ISftpSession {
private final TerminalConfig config;
private final SessionStore sessionStore;
private SftpExecutor executor;
@@ -42,9 +40,8 @@ public class SftpSession extends TerminalSession implements ISftpSession {
WebSocketSession channel,
SessionStore sessionStore,
TerminalConfig config) {
super(sessionId, channel);
super(sessionId, channel, config);
this.sessionStore = sessionStore;
this.config = config;
}
@Override

View File

@@ -9,8 +9,8 @@ import com.orion.ops.module.asset.define.AssetThreadPools;
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.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.SshOutputResponse;
import com.orion.ops.module.asset.handler.host.terminal.model.response.TerminalCloseResponse;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.WebSocketSession;
@@ -29,8 +29,6 @@ import java.io.InputStream;
@Slf4j
public class SshSession extends TerminalSession implements ISshSession {
private final TerminalConfig config;
private final SessionStore sessionStore;
private ShellExecutor executor;
@@ -42,9 +40,8 @@ public class SshSession extends TerminalSession implements ISshSession {
WebSocketSession channel,
SessionStore sessionStore,
TerminalConfig config) {
super(sessionId, channel);
super(sessionId, channel, config);
this.sessionStore = sessionStore;
this.config = config;
}
@Override

View File

@@ -1,6 +1,7 @@
package com.orion.ops.module.asset.handler.host.terminal.session;
import com.orion.ops.module.asset.enums.HostConnectStatusEnum;
import com.orion.ops.module.asset.handler.host.terminal.model.TerminalConfig;
import com.orion.ops.module.asset.service.HostConnectLogService;
import com.orion.spring.SpringHolder;
import lombok.Getter;
@@ -22,11 +23,14 @@ public abstract class TerminalSession implements ITerminalSession {
protected final WebSocketSession channel;
protected final TerminalConfig config;
protected volatile boolean close;
public TerminalSession(String sessionId, WebSocketSession channel) {
public TerminalSession(String sessionId, WebSocketSession channel, TerminalConfig config) {
this.sessionId = sessionId;
this.channel = channel;
this.config = config;
}
/**

View File

@@ -190,7 +190,7 @@
selectedFiles: Array<string>
}>();
const emits = defineEmits(['loadFile']);
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'download']);
const showHiddenFile = ref(false);
const analysisPaths = ref<Array<PathAnalysis>>([]);
@@ -268,8 +268,8 @@
// 下载文件
const downloadFile = () => {
// TODO
console.log(props.selectedFiles);
emits('download', [...props.selectedFiles]);
emits('update:selectedFiles', []);
};
</script>

View File

@@ -68,7 +68,7 @@
</span>
</a-tooltip>
<!-- 编辑内容 -->
<a-tooltip v-if="canEditable(record.size, record.attr)"
<a-tooltip v-if="canEditable(record.size, record.isDir)"
position="top"
:mini="true"
:overlay-inverse="true"
@@ -161,7 +161,7 @@
selectedFiles: Array<string>;
}>();
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'editFile']);
const emits = defineEmits(['update:selectedFiles', 'loadFile', 'editFile', 'download']);
const openSftpMoveModal = inject(openSftpMoveModalKey) as (sessionId: string, path: string) => void;
const openSftpChmodModal = inject(openSftpChmodModalKey) as (sessionId: string, path: string, permission: number) => void;
@@ -204,12 +204,9 @@
};
// 是否可编辑
const canEditable = (size: number, attr: string) => {
const typeValue = formatFileType(attr).value;
// 非文件夹和链接文件 并且文件小于 配置大小(MB) 可以编辑
return FILE_TYPE.DIRECTORY.value !== typeValue
&& FILE_TYPE.LINK_FILE.value !== typeValue
&& size <= previewSize * 1024 * 1024;
const canEditable = (size: number, isDir: boolean) => {
// 非文件夹并且文件小于 配置大小(MB) 可以编辑
return !isDir && size <= previewSize * 1024 * 1024;
};
// 点击文件名称
@@ -235,8 +232,7 @@
// 下载文件
const downloadFile = (path: string) => {
// TODO
console.log(path);
emits('download', [path]);
};
// 移动文件

View File

@@ -112,20 +112,8 @@
return true;
}
// 添加到上传列表
const files = fileList.value.map(s => {
return {
fileId: nextId(10),
type: TransferType.UPLOAD,
hostId: hostId.value,
name: s.file.webkitRelativePath || s.file.name,
currentSize: 0,
totalSize: s.file.size,
status: TransferStatus.WAITING,
parentPath: parentPath.value,
file: s.file
};
});
transferManager.addTransfer(files);
const files = fileList.value.map(s => s.file);
transferManager.addUpload(hostId.value, parentPath.value, files);
Message.success('已开始上传, 点击右侧传输列表查看进度');
// 清空
handlerClear();

View File

@@ -11,10 +11,11 @@
:hide-icon="true">
<!-- 表头 -->
<sftp-table-header class="sftp-table-header"
v-model:selected-files="selectFiles"
:current-path="currentPath"
:session="session"
:selected-files="selectFiles"
@load-file="loadFiles" />
@load-file="loadFiles"
@download="downloadFiles" />
<!-- 表格 -->
<sftp-table class="sftp-table-wrapper"
v-model:selected-files="selectFiles"
@@ -22,7 +23,8 @@
:list="fileList"
:loading="tableLoading"
@load-file="loadFiles"
@edit-file="editFile" />
@edit-file="editFile"
@download="downloadFiles" />
</a-spin>
</template>
<template #second v-if="editorView">
@@ -78,7 +80,7 @@
tab: TerminalTabItem
}>();
const { preference, sessionManager } = useTerminalStore();
const { preference, sessionManager, transferManager } = useTerminalStore();
const { loading: tableLoading, setLoading: setTableLoading } = useLoading(true);
const { loading: editorLoading, setLoading: setEditorLoading } = useLoading();
@@ -140,6 +142,25 @@
editorFilePath.value = '';
};
// 下载文件
const downloadFiles = (paths: Array<string>) => {
if (!paths.length) {
return paths;
}
Message.success('已开始下载, 点击右侧传输列表查看进度');
// 映射为文件
const files = fileList.value.filter(s => paths.includes(s.path))
.map(s => {
return { ...s };
});
// 添加普通文件到下载队列
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 connectCallback = () => {
loadFiles(undefined);

View File

@@ -81,7 +81,7 @@
<!-- 传输状态 -->
<div class="transfer-item-right-progress">
<!-- 等待传输 -->
<icon-loading v-if="item.status === TransferStatus.WAITING" />
<icon-clock-circle v-if="item.status === TransferStatus.WAITING" />
<!-- 传输进度 -->
<a-progress v-else
type="circle"

View File

@@ -1,9 +1,10 @@
import type { ISftpTransferManager, ISftpTransferUploader, SftpTransferItem } from '../types/terminal.type';
import { TransferOperatorResponse } from '../types/terminal.type';
import { 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';
export const wsBase = import.meta.env.VITE_WS_BASE_URL;
@@ -25,8 +26,43 @@ export default class SftpTransferManager implements ISftpTransferManager {
this.transferList = [];
}
// 添加传输
addTransfer(items: Array<SftpTransferItem>): void {
// 添加上传任务
addUpload(hostId: number, parentPath: string, files: Array<File>) {
// 转为上传任务
const items = files.map(s => {
return {
fileId: nextId(10),
type: TransferType.UPLOAD,
hostId: hostId,
name: s.webkitRelativePath || s.name,
currentSize: 0,
totalSize: s.size,
status: TransferStatus.WAITING,
parentPath: parentPath,
file: s
};
});
this.transferList.push(...items);
// 开始传输
if (!this.run) {
this.openClient();
}
}
// 添加下载任务
addDownload(hostId: number, currentPath: string, files: Array<SftpFile>) {
// 转为下载文件
const items = files.map(s => {
return {
fileId: nextId(10),
type: TransferType.DOWNLOAD,
hostId: hostId,
name: s.path.substring(currentPath.length),
currentSize: 0,
totalSize: s.size,
status: TransferStatus.WAITING,
};
}) as Array<SftpTransferItem>;
this.transferList.push(...items);
// 开始传输
if (!this.run) {
@@ -85,6 +121,10 @@ export default class SftpTransferManager implements ISftpTransferManager {
// 传输下一条任务
private transferNextItem() {
this.currentUploader = 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) {

View File

@@ -372,8 +372,10 @@ export interface SftpFile {
// sftp 传输管理器定义
export interface ISftpTransferManager {
transferList: Array<SftpTransferItem>;
// 添加传输
addTransfer: (items: Array<SftpTransferItem>) => void;
// 添加上传任务
addUpload: (hostId: number, parentPath: string, files: Array<File>) => void;
// 添加下载任务
addDownload: (hostId: number, currentPath: string, files: Array<SftpFile>) => void;
// 取消传输
cancelTransfer: (fileId: string) => void;
}