🐛 修复 windows 文件备份失败.

This commit is contained in:
lijiahangmax
2024-07-14 21:24:53 +08:00
parent 849e010bc3
commit c20c83245f
12 changed files with 68 additions and 20 deletions

View File

@@ -29,10 +29,11 @@ public class SftpRemoveHandler extends AbstractTerminalHandler<SftpBaseRequest>
@Override @Override
public void handle(WebSocketSession channel, SftpBaseRequest payload) { public void handle(WebSocketSession channel, SftpBaseRequest payload) {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
// 获取会话 String path = payload.getPath();
String sessionId = payload.getSessionId(); String sessionId = payload.getSessionId();
// 获取会话
ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId); ISftpSession session = hostTerminalManager.getSession(channel.getId(), sessionId);
String[] paths = payload.getPath().split("\\|"); String[] paths = path.split("\\|");
log.info("SftpRemoveHandler-handle start sessionId: {}, path: {}", sessionId, Arrays.toString(paths)); log.info("SftpRemoveHandler-handle start sessionId: {}, path: {}", sessionId, Arrays.toString(paths));
Exception ex = null; Exception ex = null;
// 删除 // 删除
@@ -53,7 +54,7 @@ public class SftpRemoveHandler extends AbstractTerminalHandler<SftpBaseRequest>
.build()); .build());
// 保存操作日志 // 保存操作日志
Map<String, Object> extra = Maps.newMap(); Map<String, Object> extra = Maps.newMap();
extra.put(OperatorLogs.PATH, payload.getPath()); extra.put(OperatorLogs.PATH, path);
this.saveOperatorLog(payload, channel, this.saveOperatorLog(payload, channel,
extra, HostTerminalOperatorType.SFTP_REMOVE, extra, HostTerminalOperatorType.SFTP_REMOVE,
startTime, ex); startTime, ex);

View File

@@ -2,6 +2,7 @@ package com.orion.visor.module.asset.handler.host.transfer.utils;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.orion.lang.exception.argument.InvalidArgumentException; import com.orion.lang.exception.argument.InvalidArgumentException;
import com.orion.lang.utils.Strings;
import com.orion.visor.framework.common.constant.ErrorMessage; import com.orion.visor.framework.common.constant.ErrorMessage;
import com.orion.visor.framework.websocket.core.utils.WebSockets; import com.orion.visor.framework.websocket.core.utils.WebSockets;
import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiverType; import com.orion.visor.module.asset.handler.host.transfer.enums.TransferReceiverType;
@@ -64,7 +65,11 @@ public class TransferUtils {
if (ex == null) { if (ex == null) {
return null; return null;
} else if (ex instanceof InvalidArgumentException) { } else if (ex instanceof InvalidArgumentException) {
return ex.getMessage(); String message = ex.getMessage();
if (Strings.isBlank(message)) {
return ErrorMessage.OPERATE_ERROR;
}
return message;
} else if (ex instanceof ClientAbortException) { } else if (ex instanceof ClientAbortException) {
return ErrorMessage.CLIENT_ABORT; return ErrorMessage.CLIENT_ABORT;
} }

View File

@@ -60,7 +60,10 @@ public class HostSftpServiceImpl implements HostSftpService {
vo.setHostId(extra.getLong(ExtraFieldConst.HOST_ID)); vo.setHostId(extra.getLong(ExtraFieldConst.HOST_ID));
vo.setHostName(extra.getString(ExtraFieldConst.HOST_NAME)); vo.setHostName(extra.getString(ExtraFieldConst.HOST_NAME));
vo.setHostAddress(extra.getString(ExtraFieldConst.ADDRESS)); vo.setHostAddress(extra.getString(ExtraFieldConst.ADDRESS));
vo.setPaths(extra.getString(ExtraFieldConst.PATH).split("\\|")); String[] paths = Optional.ofNullable(extra.getString(ExtraFieldConst.PATH))
.map(p -> p.split("\\|"))
.orElse(new String[0]);
vo.setPaths(paths);
vo.setExtra(extra); vo.setExtra(extra);
return vo; return vo;
}); });

View File

@@ -1,8 +1,10 @@
package com.orion.visor.module.asset.utils; package com.orion.visor.module.asset.utils;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.jcraft.jsch.SftpException;
import com.orion.lang.utils.Booleans; import com.orion.lang.utils.Booleans;
import com.orion.lang.utils.Strings; import com.orion.lang.utils.Strings;
import com.orion.lang.utils.io.Files1;
import com.orion.net.host.sftp.SftpExecutor; import com.orion.net.host.sftp.SftpExecutor;
import com.orion.net.host.sftp.SftpFile; import com.orion.net.host.sftp.SftpFile;
import com.orion.visor.module.asset.define.config.AppSftpConfig; import com.orion.visor.module.asset.define.config.AppSftpConfig;
@@ -39,7 +41,12 @@ public class SftpUtils {
SftpFileBackupParams backupParams = new SftpFileBackupParams(file.getName(), System.currentTimeMillis()); SftpFileBackupParams backupParams = new SftpFileBackupParams(file.getName(), System.currentTimeMillis());
String target = Strings.format(config.getBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams))); String target = Strings.format(config.getBackupFileName(), JSON.parseObject(JSON.toJSONString(backupParams)));
// 移动 // 移动
executor.move(path, target); try {
executor.getChannel().rename(path, Files1.getPath(Files1.normalize(Files1.getPath(path + "/../" + target))));
} catch (SftpException ignored) {
}
// FIXME kit
// executor.move(path, target);
} }
} }

View File

@@ -41,6 +41,7 @@ export interface HostSftpLogQueryResponse extends TableData {
export interface HostSftpLogExtra { export interface HostSftpLogExtra {
mod: number; mod: number;
target: string; target: string;
maxCount: number;
} }
/** /**

View File

@@ -107,10 +107,14 @@
{{ getDictValue(sftpOperatorTypeKey, record.type) }} {{ getDictValue(sftpOperatorTypeKey, record.type) }}
</a-tag> </a-tag>
</template> </template>
<!-- 文件数量 -->
<template #fileCount="{ record }">
<b class="span-blue">{{ record.paths.length }}</b> 个
</template>
<!-- 操作文件 --> <!-- 操作文件 -->
<template #paths="{ record }"> <template #paths="{ record }">
<div class="paths-wrapper"> <div class="paths-wrapper">
<span v-for="path in record.paths" <span v-for="path in record.paths.slice(0, record.extra.maxCount)"
class="path-wrapper text-ellipsis text-copy" class="path-wrapper text-ellipsis text-copy"
:title="path" :title="path"
@click="copy(path)"> @click="copy(path)">
@@ -125,6 +129,13 @@
提权 {{ record.extra?.mod }} {{ permission10toString(record.extra?.mod as number) }} 提权 {{ record.extra?.mod }} {{ permission10toString(record.extra?.mod as number) }}
</span> </span>
</div> </div>
<!-- 查看更多-->
<div v-if="record.paths.length > record.extra.maxCount"
class="paths-wrapper span-blue pointer"
title="查看更多"
@click="() => record.extra.maxCount = record.paths.length">
查看更多
</div>
</template> </template>
<!-- 执行结果 --> <!-- 执行结果 -->
<template #result="{ record }"> <template #result="{ record }">
@@ -175,7 +186,7 @@
import type { HostSftpLogQueryRequest, HostSftpLogQueryResponse } from '@/api/asset/host-sftp'; import type { HostSftpLogQueryRequest, HostSftpLogQueryResponse } from '@/api/asset/host-sftp';
import { reactive, ref, onMounted } from 'vue'; import { reactive, ref, onMounted } from 'vue';
import { getHostSftpLogPage, deleteHostSftpLog } from '@/api/asset/host-sftp'; import { getHostSftpLogPage, deleteHostSftpLog } from '@/api/asset/host-sftp';
import { sftpOperatorTypeKey, sftpOperatorResultKey, SftpOperatorType } from '../types/const'; import { sftpOperatorTypeKey, sftpOperatorResultKey, SftpOperatorType, showPathMaxCount } from '../types/const';
import { usePagination, useRowSelection } from '@/types/table'; import { usePagination, useRowSelection } from '@/types/table';
import { useDictStore } from '@/store'; import { useDictStore } from '@/store';
import { Message } from '@arco-design/web-vue'; import { Message } from '@arco-design/web-vue';
@@ -186,14 +197,13 @@
import UserSelector from '@/components/user/user/selector/index.vue'; import UserSelector from '@/components/user/user/selector/index.vue';
import HostSelector from '@/components/asset/host/selector/index.vue'; import HostSelector from '@/components/asset/host/selector/index.vue';
const tableRenderData = ref<HostSftpLogQueryResponse[]>([]);
const selectedKeys = ref<number[]>([]);
const pagination = usePagination(); const pagination = usePagination();
const rowSelection = useRowSelection(); const rowSelection = useRowSelection();
const { loading, setLoading } = useLoading(); const { loading, setLoading } = useLoading();
const { toOptions, getDictValue } = useDictStore(); const { toOptions, getDictValue } = useDictStore();
const tableRenderData = ref<Array<HostSftpLogQueryResponse>>([]);
const selectedKeys = ref<Array<number>>([]);
const formModel = reactive<HostSftpLogQueryRequest>({ const formModel = reactive<HostSftpLogQueryRequest>({
userId: undefined, userId: undefined,
hostId: undefined, hostId: undefined,
@@ -206,7 +216,12 @@
const doFetchTableData = async (request: HostSftpLogQueryRequest) => { const doFetchTableData = async (request: HostSftpLogQueryRequest) => {
try { try {
setLoading(true); setLoading(true);
// 查询
const { data } = await getHostSftpLogPage(request); const { data } = await getHostSftpLogPage(request);
// 设置最大数量
data.rows.forEach(s => {
s.extra.maxCount = showPathMaxCount;
});
tableRenderData.value = data.rows; tableRenderData.value = data.rows;
pagination.total = data.total; pagination.total = data.total;
pagination.current = request.page; pagination.current = request.page;
@@ -240,13 +255,11 @@
}; };
// 删除当前行 // 删除当前行
const deleteRow = async ({ id }: { const deleteRow = async (record: HostSftpLogQueryResponse) => {
id: number
}) => {
try { try {
setLoading(true); setLoading(true);
// 调用删除接口 // 调用删除接口
await deleteHostSftpLog([id]); await deleteHostSftpLog([record.id]);
Message.success('删除成功'); Message.success('删除成功');
selectedKeys.value = []; selectedKeys.value = [];
// 重新加载数据 // 重新加载数据

View File

@@ -4,6 +4,9 @@ export const SftpOperatorType = {
SFTP_CHMOD: 'host-terminal:sftp-chmod', SFTP_CHMOD: 'host-terminal:sftp-chmod',
}; };
// 最大展示数量
export const showPathMaxCount = 5;
// sftp 操作类型 字典项 // sftp 操作类型 字典项
export const sftpOperatorTypeKey = 'sftpOperatorType'; export const sftpOperatorTypeKey = 'sftpOperatorType';

View File

@@ -29,6 +29,12 @@ const columns = [
slotName: 'type', slotName: 'type',
width: 116, width: 116,
align: 'left', align: 'left',
}, {
title: '文件数量',
dataIndex: 'fileCount',
slotName: 'fileCount',
align: 'left',
width: 100,
}, { }, {
title: '操作文件', title: '操作文件',
dataIndex: 'paths', dataIndex: 'paths',

View File

@@ -50,7 +50,11 @@
// 打开新增 // 打开新增
const open = (session: string, path: string, isTouch: boolean) => { const open = (session: string, path: string, isTouch: boolean) => {
sessionId.value = session; sessionId.value = session;
formModel.value.path = path; if (path === '/') {
formModel.value.path = path;
} else {
formModel.value.path = path + '/';
}
touch.value = isTouch; touch.value = isTouch;
setVisible(true); setVisible(true);
// 自动聚焦 // 自动聚焦

View File

@@ -285,12 +285,12 @@
// 创建文件 // 创建文件
const createFile = () => { const createFile = () => {
openSftpCreateModal(props.session?.sessionId as string, props.currentPath + '/', true); openSftpCreateModal(props.session?.sessionId as string, props.currentPath, true);
}; };
// 创建文件夹 // 创建文件夹
const createDir = () => { const createDir = () => {
openSftpCreateModal(props.session?.sessionId as string, props.currentPath + '/', false); openSftpCreateModal(props.session?.sessionId as string, props.currentPath, false);
}; };
// 删除选中文件 // 删除选中文件

View File

@@ -106,7 +106,8 @@
return false; return false;
} }
if (!fileList.value.length) { if (!fileList.value.length) {
return true; Message.error('请选择文件');
return false;
} }
// 添加到上传列表 // 添加到上传列表
const files = fileList.value.map(s => s.file as File); const files = fileList.value.map(s => s.file as File);
@@ -168,6 +169,10 @@
margin-right: 0 !important; margin-right: 0 !important;
} }
:deep(.arco-upload-list-item-name-link) {
width: 100%;
}
:deep(.arco-upload-list-item-name-text) { :deep(.arco-upload-list-item-name-text) {
width: 100%; width: 100%;
} }

View File

@@ -3,7 +3,7 @@ import { TransferOperatorType, TransferStatus } from '../types/terminal.const';
import { getFileName, getPath } from '@/utils/file'; import { getFileName, getPath } from '@/utils/file';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
// sftp 上传器实现 // sftp 下载器实现
export default class SftpTransferDownloader implements ISftpTransferDownloader { export default class SftpTransferDownloader implements ISftpTransferDownloader {
public abort: boolean; public abort: boolean;