From 2939613be873711c59b2706d18271efbe7d4444e Mon Sep 17 00:00:00 2001 From: lilin Date: Tue, 1 Apr 2025 17:22:26 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TsFilesController.java | 56 +- .../experimentalData/domain/TreeDTO.java | 3 + .../service/ITsFilesService.java | 40 +- .../service/impl/TsFilesServiceImpl.java | 538 ++++++++++++++---- .../base/AbstractS3BaseFileService.java | 169 +++--- .../storage/service/base/BaseFileService.java | 22 +- .../service/impl/LocalServiceImpl.java | 147 ++--- 7 files changed, 682 insertions(+), 293 deletions(-) diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java index 0644eeb..8a97f6a 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java @@ -377,23 +377,58 @@ public class TsFilesController { /********************************** - * 用途说明: 查询本地和备份空间结构树 + * 用途说明: 查询本地结构树 * 参数说明 taskId 节点ID * 参数说明 nodeId 任务ID + * 参数说明 id 文件夹ID * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 ***********************************/ - @Log(module = "实验数据管理", value = "查询本地和备份空间结构树!") - @PostMapping("/listLocalAndBackup") - @ApiOperation("查询本地和备份空间结构树") - public ResponseResult listLocalAndBackup(String taskId, String nodeId) { + @Log(module = "实验数据管理", value = "查询本地结构树!") + @PostMapping("/listLocalTree") + @ApiOperation("查询本地结构树") + public ResponseResult listLocalTree(String taskId, String nodeId,String id) { + + //查询本地树和minio树 + DualTreeResponse response = tsFilesService.listLocalTree(taskId, nodeId,id); + return ResponseResult.successData(response); + } - if (StrUtil.isBlank(taskId) && StrUtil.isBlank(nodeId)) { + /********************************** + * 用途说明: 查询备份空间结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 参数说明 id 文件夹ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + @Log(module = "实验数据管理", value = "查询备份空间结构树!") + @PostMapping("/listBackupTree") + @ApiOperation("查询备份空间结构树") + public ResponseResult listBackupTree(String taskId, String nodeId,String id) { + + //查询本地树和minio树 + DualTreeResponse response = tsFilesService.listBackupTree(taskId, nodeId,id); + return ResponseResult.successData(response); + } + + + + /********************************** + * 用途说明: 文件自动备份 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Log(module = "实验数据管理", value = "文件自动备份!") + @PostMapping("/automaticFileBackup") + @ApiOperation("自动备份本地文件到备份空间") + public ResponseResult automaticFileBackup(String taskId, String nodeId) { + + + if (StrUtil.isEmpty(taskId) || StrUtil.isEmpty(nodeId)) { return ResponseResult.error("参数为空"); } - //查询本地树和minio树 - DualTreeResponse response = tsFilesService.listLocalAndBackup(taskId, nodeId); - return ResponseResult.successData(response); + return ResponseResult.success(tsFilesService.automaticFileBackup(taskId,nodeId)); } @@ -430,6 +465,7 @@ public class TsFilesController { /********************************** * 用途说明: 查询文件内容接口 * 参数说明 id 文件的ID + * 参数说明 type 查看本地还是minio local minio * 返回值说明: com.yfd.platform.config.ResponseResult文件内容的纯文本(UTF-8 编码) ***********************************/ @Log(module = "实验数据管理", value = "查询文件内容!") @@ -437,7 +473,7 @@ public class TsFilesController { @ApiOperation("查询文件内容") public ResponseResult getFileContent(@RequestParam String id) { try { - if (StrUtil.isBlank(id)) { + if (StrUtil.isBlank(id) ) { return ResponseResult.error("参数为空"); } String content = tsFilesService.readFileContent(id); diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java index c906519..6db81e9 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/domain/TreeDTO.java @@ -101,4 +101,7 @@ public class TreeDTO { // 子节点列表 // 初始化 children 为一个空列表 private List children = new ArrayList<>(); + + @TableField(exist = false) + private int number; } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java index 5c60d19..e40b216 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java @@ -140,13 +140,14 @@ public interface ITsFilesService extends IService { */ String copyFileFolder(MoveCopyFileFolderRequest request)throws IOException; - /********************************** - * 用途说明: 查询本地和备份空间结构树 - * 参数说明 taskId 节点ID - * 参数说明 nodeId 任务ID - * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 - ***********************************/ - DualTreeResponse listLocalAndBackup(String taskId, String nodeId); +// /********************************** +// * 用途说明: 查询本地和备份空间结构树 +// * 参数说明 taskId 节点ID +// * 参数说明 nodeId 任务ID +// * 参数说明 id 文件夹ID +// * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 +// ***********************************/ +// DualTreeResponse listLocalAndBackup(String taskId, String nodeId,String id); /** @@ -181,4 +182,29 @@ public interface ITsFilesService extends IService { * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 ***********************************/ void batchUpdateFile(String id, List modifications) throws IOException; + + + /********************************** + * 用途说明: 文件自动备份 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + String automaticFileBackup(String taskId, String nodeId); + /********************************** + * 用途说明: 查询本地结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 参数说明 id 文件夹ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + DualTreeResponse listLocalTree(String taskId, String nodeId, String id); + /********************************** + * 用途说明: 查询备份空间结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 参数说明 id 文件夹ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + DualTreeResponse listBackupTree(String taskId, String nodeId, String id); } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java index 1ff890e..ae76023 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsFilesServiceImpl.java @@ -3,8 +3,7 @@ package com.yfd.platform.modules.experimentalData.service.impl; import cn.hutool.core.collection.CollUtil; -import java.nio.channels.FileChannel; -import java.nio.channels.FileLock; + import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; @@ -15,6 +14,7 @@ import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.*; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -33,7 +33,6 @@ import com.opencsv.exceptions.CsvValidationException; import com.yfd.platform.component.ServerSendEventServer; import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.experimentalData.domain.*; -import com.yfd.platform.modules.experimentalData.enums.CompressionFormat; import com.yfd.platform.modules.experimentalData.mapper.TsFilesMapper; import com.yfd.platform.modules.experimentalData.service.ITsFilesService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -51,7 +50,6 @@ import com.yfd.platform.system.domain.SysDictionaryItems; import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; import com.yfd.platform.utils.StringUtils; import io.netty.channel.ChannelInboundHandlerAdapter; -import net.sf.jsqlparser.expression.LongValue; import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; import org.apache.commons.compress.archivers.sevenz.SevenZFile; import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; @@ -68,9 +66,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.beans.factory.annotation.Value; // 正确 + import javax.annotation.Resource; -import javax.xml.crypto.Data; import java.io.*; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -91,6 +88,7 @@ import org.apache.commons.codec.binary.Hex; public class TsFilesServiceImpl extends ServiceImpl implements ITsFilesService { private static final Logger LOGGER = LoggerFactory.getLogger(ChannelInboundHandlerAdapter.class); + private int time = 1; //试验任务文档表 Mapper @@ -115,7 +113,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl private final Set addedEntries = new HashSet<>(); - /********************************** * 用途说明: 分页查询试验数据管理-文档内容 * 参数说明 @@ -2502,8 +2499,15 @@ public class TsFilesServiceImpl extends ServiceImpl impl TsFiles tsFiles = new TsFiles(); try { // 获取 MinIO 和本地文件列表(并行处理) - List fileItemListMinio = Collections.synchronizedList(new ArrayList<>()); - List fileItemListLocal = Collections.synchronizedList(new ArrayList<>()); +// List fileItemListMinio = Collections.synchronizedList(new ArrayList<>()); +// List fileItemListLocal = Collections.synchronizedList(new ArrayList<>()); + CopyOnWriteArrayList fileItemListMinio = new CopyOnWriteArrayList<>(); + CopyOnWriteArrayList fileItemListLocal = new CopyOnWriteArrayList<>(); + + + // 优化点3: 使用 CompletableFuture 进行并行任务管理 +// final List minioFiles = Collections.synchronizedList(new ArrayList<>()); +// final List localFiles = Collections.synchronizedList(new ArrayList<>()); if (StringUtils.isNoneEmpty(nodeId, taskId)) { // 并行查询数据库记录 @@ -2558,6 +2562,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl private QueryWrapper buildTaskQuery(String nodeId, String taskId) { return new QueryWrapper() .eq("task_id", taskId) + .eq("parent_id", "00") .eq("node_id", nodeId); //.and(wrapper -> wrapper.eq("work_path", "/").or().eq("backup_path", "/")); } @@ -2569,51 +2574,31 @@ public class TsFilesServiceImpl extends ServiceImpl impl } // 辅助方法:获取文件列表并添加到集合 + // 辅助方法:获取文件列表并添加到集合(优化版) + + // 优化点7:提取文件处理核心逻辑 private void processFileList(String path, String fileName, String storageKey, List targetList, String isFile, String nodeId, String taskId) throws Exception { if (StringUtils.isNotEmpty(path)) { AbstractBaseFileService service = storageSourceContext.getByStorageKey(storageKey); + String folderPath = path + fileName + "/"; + Map backupPathMap = preloadTsFiles(nodeId, taskId, "backup_path"); + Map workPathMap = preloadTsFiles(nodeId, taskId, "work_path"); + if (storageKey.equals("minio")) { - List files = service.fileListData(path, fileName + "/"); + + List files = new ArrayList<>(); + if ("FILE".equals(isFile)) { + files = service.fileListData(path, fileName + "/"); + + } else { + files = service.fileLists(folderPath); + } if (files != null) { // 对每个文件的路径进行规范化 -// for (FileItemResult file : files) { -// String normalizedPath = ensurePathFormat(file.getPath()); -// String ProcessingPath = processingPath(normalizedPath, nodeId); -// file.setPath(ProcessingPath); -// if (file.getSize() == null) { -// file.setSize((long) 0); -// } else { -// // 获取文件大小(字节) -// long fileSizeInBytes = file.getSize(); -// // 转换为 MB 并保留两位小数 -// double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0); -// String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 -// // 判断是否为 "0.00",如果是,则直接设置为 0 -// if ("0.00".equals(fileSizeFormatted)) { -// file.setSize((long) 0); // 如果文件大小为 0.00,直接设置为 0 -// } else { -// // 否则,将文件大小转换为 long(去掉小数部分) -// file.setSize((long) Double.parseDouble(fileSizeFormatted)); -// } -// } -// -// QueryWrapper queryWrapper = new QueryWrapper<>(); -// queryWrapper.eq("node_id", nodeId); -// queryWrapper.eq("task_id", taskId); -// queryWrapper.eq("backup_path", normalizedPath); -// queryWrapper.eq("file_name", file.getName()); -// TsFiles tsFiles1 = tsFilesMapper.selectOne(queryWrapper); -// if (tsFiles1 != null) { -// file.setParentId(tsFiles1.getParentId()); -// file.setId(tsFiles1.getId()); -// } -// } - - files.forEach(file -> { String normalizedPath = ensurePathFormat(file.getPath()); - String ProcessingPath = processingPath(normalizedPath, nodeId); - file.setPath(ProcessingPath); +// String ProcessingPath = processingPath(normalizedPath, nodeId); + file.setPath(normalizedPath); if (file.getSize() == null) { file.setSize((long) 0); } else { @@ -2631,15 +2616,18 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("node_id", nodeId); - queryWrapper.eq("task_id", taskId); - queryWrapper.eq("backup_path", normalizedPath); - queryWrapper.eq("file_name", file.getName()); - TsFiles tsFiles1 = tsFilesMapper.selectOne(queryWrapper); - if (tsFiles1 != null) { - file.setParentId(tsFiles1.getParentId()); - file.setId(tsFiles1.getId()); +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("node_id", nodeId); +// queryWrapper.eq("task_id", taskId); +// queryWrapper.eq("backup_path", normalizedPath); +// queryWrapper.eq("file_name", file.getName()); +// TsFiles tsFiles1 = tsFilesMapper.selectOne(queryWrapper); + + TsFiles tsFileFromBackup = backupPathMap.get(normalizedPath + "|" + file.getName()); // 从 backupPathMap 中获取 TsFiles 对象 + + if (tsFileFromBackup != null) { + file.setParentId(tsFileFromBackup.getParentId()); + file.setId(tsFileFromBackup.getId()); } }); // 同步添加(线程安全) @@ -2648,14 +2636,34 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } else { - List files = service.fileListData(path, fileName); + + List files = new ArrayList<>(); + if ("FILE".equals(isFile)) { + files = service.fileListData(path, fileName + "/"); + + } else { + files = service.fileLists(folderPath); + + } if (files != null) { // 对每个文件的路径进行规范化 files.forEach(file -> { String normalizedPath = ensurePathFormat(file.getPath()); +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// queryWrapper.eq("node_id", nodeId); +// queryWrapper.eq("task_id", taskId); +// queryWrapper.eq("work_path", normalizedPath); +// queryWrapper.eq("file_name", file.getName()); +// TsFiles tsFiles1 = tsFilesMapper.selectOne(queryWrapper); + TsFiles tsFileFromWork = workPathMap.get(normalizedPath + "|" + file.getName()); // 从 backupPathMap 中获取 TsFiles 对象 - String ProcessingPath = processingPath(normalizedPath, nodeId); - file.setPath(ProcessingPath); + if (tsFileFromWork != null) { + file.setParentId(tsFileFromWork.getParentId()); + file.setId(tsFileFromWork.getId()); + } + +// String ProcessingPath = processingPath(normalizedPath, nodeId); + file.setPath(normalizedPath); if (file.getSize() == null) { file.setSize((long) 0); } else { @@ -2672,20 +2680,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl file.setSize((long) Double.parseDouble(fileSizeFormatted)); } } -// file.setPath(normalizedPath); - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("node_id", nodeId); - queryWrapper.eq("task_id", taskId); - queryWrapper.eq("work_path", normalizedPath); - queryWrapper.eq("file_name", file.getName()); - TsFiles tsFiles1 = tsFilesMapper.selectOne(queryWrapper); - if (tsFiles1 != null) { - file.setParentId(tsFiles1.getParentId()); - file.setId(tsFiles1.getId()); - } }); - // 同步添加(线程安全) - targetList.addAll(files); } @@ -2694,6 +2689,33 @@ public class TsFilesServiceImpl extends ServiceImpl impl } + // 批量查询TsFiles记录(按路径类型分类) + // 批量查询TsFiles记录(按路径类型分类) + private Map preloadTsFiles(String nodeId, String taskId, String pathType) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_id", nodeId) + .eq("task_id", taskId) + .isNotNull(pathType); // 确保路径字段不为空 + + return tsFilesMapper.selectList(queryWrapper) + .stream() + .filter(tsFile -> { + // 确保路径字段不为空,不管是 backup_path 还是 work_path + String path = pathType.equals("backup_path") ? tsFile.getBackupPath() : tsFile.getWorkPath(); + return path != null && !path.isEmpty(); // 如果路径为空,跳过 + }) + .collect(Collectors.toMap( + tsFile -> { + String path = pathType.equals("backup_path") + ? tsFile.getBackupPath() + : tsFile.getWorkPath(); + return path + "|" + tsFile.getFileName(); + }, + Function.identity() + )); + } + + /** * 确保路径格式为以 "/" 开头和结尾(例如 "/data/test/") * 若路径为空或非法,返回根路径 "/" @@ -2717,12 +2739,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 辅助方法:去重文件列表(并行安全) private void deduplicateFileList(List fileList) { -// ConcurrentMap seenKeys = new ConcurrentHashMap<>(); -// fileList.removeIf(file -> { -// String key = normalizePath(file.getPath()) + file.getName(); -// return seenKeys.putIfAbsent(key, Boolean.TRUE) != null; -// }); - ConcurrentHashMap seenKeys = new ConcurrentHashMap<>(); List uniqueFiles = fileList.parallelStream() .filter(file -> seenKeys.putIfAbsent(generateMapKey(file), true) == null) @@ -2834,8 +2850,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl List md5Mismatches, String nodeId) { - minioFile.setPath("/" + nodeId + minioFile.getPath()); - localFile.setPath("/" + nodeId + localFile.getPath()); +// minioFile.setPath( minioFile.getPath()); +// localFile.setPath(localFile.getPath()); File localFileObj = new File(basePath + minioFile.getPath(), localFile.getName()); if (localFileObj.isDirectory()) { LOGGER.warn("跳过文件夹: {}", localFileObj.getAbsolutePath()); @@ -3119,6 +3135,68 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } + /********************************** + * public String path; + * public String size; + * public String name; + * public String type; + * 用途说明: 文件自动备份 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回成功或者失败 + ***********************************/ + @Override + public String automaticFileBackup(String taskId, String nodeId) { + //首先查询节点下面所有 备份路径为空的数据 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("task_id", taskId); + queryWrapper.eq("node_id", nodeId); + queryWrapper.and(wrapper -> wrapper.isNull("backup_path").or().eq("backup_path", "")); + queryWrapper.isNotNull("work_path").ne("work_path", ""); + List tsFilelist = tsFilesMapper.selectList(queryWrapper); + int FileCount = 0, FolderCount = 0; + int BackupsFileCount = 0, BackupsFolderCount = 0; + + if (tsFilelist.isEmpty()) { + return "本地有新增文件 " + FileCount + " 个, 文件夹 " + FolderCount + " 个, 已将 " + BackupsFileCount + " 个文件, " + BackupsFolderCount + " 个文件夹备份成功"; + } + + for (TsFiles tsFiles : tsFilelist) { + ParameterList parameterList = new ParameterList(); + Parameter fileParameter = new Parameter(); + Parameter FolderParameter = new Parameter(); + List fileParameterlist = new ArrayList<>(); + List FolderParameterlist = new ArrayList<>(); + + if ("FILE".equals(tsFiles.getIsFile())) { + parameterList.setName(tsFiles.getFileName()); + parameterList.setPath(tsFiles.getWorkPath()); + parameterList.setSize(tsFiles.getFileSize()); + parameterList.setType(tsFiles.getIsFile()); + fileParameterlist.add(parameterList); + fileParameter.setParameterLists(fileParameterlist); + Boolean value = uploadToBackup(fileParameter); + if (value) { + BackupsFileCount++; + } + FileCount++; + } else { + parameterList.setName(tsFiles.getFileName()); + parameterList.setPath(tsFiles.getWorkPath()); + parameterList.setSize(tsFiles.getFileSize()); + parameterList.setType(tsFiles.getIsFile()); + FolderParameterlist.add(parameterList); + FolderParameter.setParameterLists(FolderParameterlist); + Boolean value = uploadToBackup(FolderParameter); + if (value) { + BackupsFolderCount++; + } + FolderCount++; + } + } + return "本地有新增文件 " + FileCount + " 个, 新增文件夹 " + FolderCount + " 个, 已将 " + BackupsFileCount + " 个文件, " + BackupsFolderCount + " 个文件夹备份成功"; + } + /********************************** * 用途说明: 从备份空间下载到工作空间 @@ -3945,58 +4023,267 @@ public class TsFilesServiceImpl extends ServiceImpl impl /**********************************************************复制文件或者文件夹结束***********************************************************************************/ /********************************** - * 用途说明: 查询本地和备份空间结构树 + * 用途说明: 查询本地结构树 * 参数说明 taskId 节点ID * 参数说明 nodeId 任务ID + * 参数说明 id 文件夹ID * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 ***********************************/ @Override - public DualTreeResponse listLocalAndBackup(String taskId, String nodeId) { + public DualTreeResponse listLocalTree(String taskId, String nodeId, String id) { // 记录方法入参 LOGGER.info("Starting to build dual trees for taskId={}, nodeId={}", taskId, nodeId); // 1. 批量查询所有相关节点 QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("task_id", taskId) - .eq("node_id", nodeId); + if (taskId != null && !taskId.isEmpty()) { + queryWrapper.eq("task_id", taskId); + } + if (nodeId != null && !nodeId.isEmpty()) { + queryWrapper.eq("node_id", nodeId); + } + queryWrapper.isNotNull("work_path").ne("work_path", ""); + ; + + if (id != null && !id.isEmpty()) { + // 查询 ID 等于给定 ID 的文件 或者 父节点等于给定 ID 的文件 + queryWrapper.eq("parent_id", id); +// queryWrapper.eq("id", id).or().eq("parent_id", id); + } else { + queryWrapper.eq("parent_id", "00"); + } List allNodes = tsFilesMapper.selectList(queryWrapper); - // 2. 构建内存索引提升查询效率 + // 2. 双树构建容器 + List localTrees = new ArrayList<>(); + if (allNodes.isEmpty()) { + return new DualTreeResponse(localTrees, null); + } + // 3. 构建内存索引提升查询效率 Map> parentChildrenMap = allNodes.stream() .collect(Collectors.groupingBy(TsFiles::getParentId)); LOGGER.debug("使用{}个条目构建父子映射", parentChildrenMap.size()); - // 3. 双树构建容器 - List localTrees = new ArrayList<>(); - List minioTrees = new ArrayList<>(); + if (id == null || id == "") { + List rootNodes = parentChildrenMap.get("00"); + if (rootNodes == null || rootNodes.isEmpty()) { + LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); + return new DualTreeResponse(localTrees, null); + } else { - // 4. 从顶级节点(parentId为00)开始构建树 - List rootNodes = parentChildrenMap.get("00"); - if (rootNodes == null || rootNodes.isEmpty()) { - LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); - return new DualTreeResponse(localTrees, minioTrees); - } - - if (rootNodes != null) { - for (TsFiles rootNode : rootNodes) { - // 构建本地树 - TreeDTO localTree = buildTree(rootNode, parentChildrenMap, true); - if (localTree != null) { - localTrees.add(localTree); - LOGGER.debug("添加了本地树节点: {}", localTree.getId()); + if (rootNodes != null) { + for (TsFiles rootNode : rootNodes) { + // 构建本地树 + TreeDTO localTree = buildFatherTree(rootNode, parentChildrenMap, true); + if (localTree != null) { + localTrees.add(localTree); + LOGGER.debug("添加了本地树节点: {}", localTree.getId()); + } + } } + LOGGER.info("Tree construction completed. Local nodes: {}", localTrees.size()); + } - // 构建Minio树 - TreeDTO minioTree = buildTree(rootNode, parentChildrenMap, false); - if (minioTree != null) { - minioTrees.add(minioTree); - LOGGER.debug("添加了MINIO树节点: {}", minioTree.getId()); + } else { + // 4. 从顶级节点(parentId为00)开始构建树 + List rootNodes = parentChildrenMap.get(id); + if (rootNodes == null || rootNodes.isEmpty()) { + LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); + return new DualTreeResponse(localTrees, null); + } + + if (rootNodes != null) { + for (TsFiles rootNode : rootNodes) { + // 构建本地树 + TreeDTO localTree = buildFatherTree(rootNode, parentChildrenMap, true); + if (localTree != null) { + localTrees.add(localTree); + LOGGER.debug("添加了本地树节点: {}", localTree.getId()); + } } } + LOGGER.info("Tree construction completed. Local nodes: {}", localTrees.size()); } - LOGGER.info("Tree construction completed. Local nodes: {}, MinIO nodes: {}", localTrees.size(), minioTrees.size()); - return new DualTreeResponse(localTrees, minioTrees); + return new DualTreeResponse(localTrees, null); } + + /********************************** + * 用途说明: 查询备份空间结构树 + * 参数说明 taskId 节点ID + * 参数说明 nodeId 任务ID + * 参数说明 id 文件夹ID + * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 + ***********************************/ + @Override + public DualTreeResponse listBackupTree(String taskId, String nodeId, String id) { + // 记录方法入参 + LOGGER.info("Starting to build dual trees for taskId={}, nodeId={}", taskId, nodeId); + // 1. 批量查询所有相关节点 + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (taskId != null && !taskId.isEmpty()) { + queryWrapper.eq("task_id", taskId); + } + if (nodeId != null && !nodeId.isEmpty()) { + queryWrapper.eq("node_id", nodeId); + } + queryWrapper.isNotNull("backup_path").ne("backup_path", ""); + if (id != null && !id.isEmpty()) { + // 查询 ID 等于给定 ID 的文件 或者 父节点等于给定 ID 的文件 + queryWrapper.eq("parent_id", id); +// queryWrapper.eq("id", id).or().eq("parent_id", id); + } else { + queryWrapper.eq("parent_id", "00"); + } + List allNodes = tsFilesMapper.selectList(queryWrapper); + + // 2. 双树构建容器 + List minioTrees = new ArrayList<>(); + // 3. 构建内存索引提升查询效率 + Map> parentChildrenMap = allNodes.stream() + .collect(Collectors.groupingBy(TsFiles::getParentId)); + LOGGER.debug("使用{}个条目构建父子映射", parentChildrenMap.size()); + if (allNodes.isEmpty()) { + return new DualTreeResponse(null, minioTrees); + } + + if (id == null || id == "") { + List rootNodes = parentChildrenMap.get("00"); + if (rootNodes == null || rootNodes.isEmpty()) { + LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); + return new DualTreeResponse(null, minioTrees); + } else { + + if (rootNodes != null) { + for (TsFiles rootNode : rootNodes) { + // 构建Minio树 + TreeDTO minioTree = buildFatherTree(rootNode, parentChildrenMap, false); + if (minioTree != null) { + minioTrees.add(minioTree); + LOGGER.debug("添加了MINIO树节点: {}", minioTree.getId()); + } + } + } + LOGGER.info("Tree construction completed. MinIO nodes: {}", minioTrees.size()); + } + + } else { + // 4. 从顶级节点(parentId为00)开始构建树 + List rootNodes = parentChildrenMap.get(id); + if (rootNodes == null || rootNodes.isEmpty()) { + LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); + return new DualTreeResponse(null, minioTrees); + } + + if (rootNodes != null) { + for (TsFiles rootNode : rootNodes) { + // 构建Minio树 + TreeDTO minioTree = buildFatherTree(rootNode, parentChildrenMap, false); + if (minioTree != null) { + minioTrees.add(minioTree); + LOGGER.debug("添加了MINIO树节点: {}", minioTree.getId()); + } + } + } + LOGGER.info("Tree construction completed. MinIO nodes: {}", minioTrees.size()); + } + return new DualTreeResponse(null, minioTrees); + } + + +// /********************************** +// * 用途说明: 查询本地和备份空间结构树 +// * 参数说明 taskId 节点ID +// * 参数说明 nodeId 任务ID +// * 返回值说明: com.yfd.platform.config.ResponseResult 返回双树数据 +// ***********************************/ +// @Override +// public DualTreeResponse listLocalAndBackup(String taskId, String nodeId, String id) { +// // 记录方法入参 +// LOGGER.info("Starting to build dual trees for taskId={}, nodeId={}", taskId, nodeId); +// // 1. 批量查询所有相关节点 +// QueryWrapper queryWrapper = new QueryWrapper<>(); +// if (taskId != null && !taskId.isEmpty()) { +// queryWrapper.eq("task_id", taskId); +// } +// if (nodeId != null && !nodeId.isEmpty()) { +// queryWrapper.eq("node_id", nodeId); +// } +// +// if (id != null && !id.isEmpty()) { +// // 查询 ID 等于给定 ID 的文件 或者 父节点等于给定 ID 的文件 +// queryWrapper.eq("parent_id", id); +//// queryWrapper.eq("id", id).or().eq("parent_id", id); +// } else { +// queryWrapper.eq("parent_id", "00"); +// } +// List allNodes = tsFilesMapper.selectList(queryWrapper); +// +// // 2. 构建内存索引提升查询效率 +// Map> parentChildrenMap = allNodes.stream() +// .collect(Collectors.groupingBy(TsFiles::getParentId)); +// LOGGER.debug("使用{}个条目构建父子映射", parentChildrenMap.size()); +// +// // 3. 双树构建容器 +// List localTrees = new ArrayList<>(); +// List minioTrees = new ArrayList<>(); +// +// if (id == null || id == "") { +// List rootNodes = parentChildrenMap.get("00"); +// if (rootNodes == null || rootNodes.isEmpty()) { +// LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); +// return new DualTreeResponse(localTrees, minioTrees); +// } else { +// +// if (rootNodes != null) { +// for (TsFiles rootNode : rootNodes) { +// // 构建本地树 +// TreeDTO localTree = buildFatherTree(rootNode, parentChildrenMap, true); +// if (localTree != null) { +// localTrees.add(localTree); +// LOGGER.debug("添加了本地树节点: {}", localTree.getId()); +// } +// +// // 构建Minio树 +// TreeDTO minioTree = buildFatherTree(rootNode, parentChildrenMap, false); +// if (minioTree != null) { +// minioTrees.add(minioTree); +// LOGGER.debug("添加了MINIO树节点: {}", minioTree.getId()); +// } +// } +// } +// LOGGER.info("Tree construction completed. Local nodes: {}, MinIO nodes: {}", localTrees.size(), minioTrees.size()); +// } +// +// } else { +// // 4. 从顶级节点(parentId为00)开始构建树 +// List rootNodes = parentChildrenMap.get("00"); +// if (rootNodes == null || rootNodes.isEmpty()) { +// LOGGER.warn("找不到的根节点 taskId={}, nodeId={}", taskId, nodeId); +// return new DualTreeResponse(localTrees, minioTrees); +// } +// +// if (rootNodes != null) { +// for (TsFiles rootNode : rootNodes) { +// // 构建本地树 +// TreeDTO localTree = buildTree(rootNode, parentChildrenMap, true); +// if (localTree != null) { +// localTrees.add(localTree); +// LOGGER.debug("添加了本地树节点: {}", localTree.getId()); +// } +// +// // 构建Minio树 +// TreeDTO minioTree = buildTree(rootNode, parentChildrenMap, false); +// if (minioTree != null) { +// minioTrees.add(minioTree); +// LOGGER.debug("添加了MINIO树节点: {}", minioTree.getId()); +// } +// } +// } +// LOGGER.info("Tree construction completed. Local nodes: {}, MinIO nodes: {}", localTrees.size(), minioTrees.size()); +// } +// return new DualTreeResponse(localTrees, minioTrees); +// } + /** * 递归构建树形结构(内存操作,不再查询数据库) * @@ -4027,6 +4314,25 @@ public class TsFilesServiceImpl extends ServiceImpl impl return dto; } + + /** + * 递归构建父节点树形结构(内存操作,不再查询数据库) + * + * @param current 当前节点 + * @param parentChildrenMap 父子关系映射表 + * @param isLocal 是否是本地树 + * @return 有效的树节点(路径不为空) + */ + private TreeDTO buildFatherTree(TsFiles current, Map> parentChildrenMap, boolean isLocal) { + // 1. 转换为DTO并检查路径有效性 + TreeDTO dto = convertToDTO(current, isLocal); + if (dto.getPath() == null || dto.getPath().trim().isEmpty()) { + LOGGER.warn("由于路径为空,跳过节点{}", current.getId()); + return null; + } + return dto; + } + /** * 将 FileNode 转换为 TreeDTO * @@ -4040,7 +4346,18 @@ public class TsFilesServiceImpl extends ServiceImpl impl queryWrapperSysDictionary.eq("parentcode", "compressType"); queryWrapperSysDictionary.orderByAsc("orderno"); List sysDictionaryItems = sysDictionaryItemsMapper.selectList(queryWrapperSysDictionary); + int count = 0; + if ("FOLDER".equals(node.getIsFile())) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("parent_id", node.getId()); + if (isLocal) { + queryWrapper.isNotNull("work_path").ne("work_path", ""); + } else { + queryWrapper.isNotNull("backup_path").ne("backup_path", ""); + } + count = tsFilesMapper.selectCount(queryWrapper); + } TreeDTO dto = new TreeDTO(); // 复制公共字段 @@ -4056,7 +4373,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl dto.setUploadTime(node.getUploadTime()); dto.setUploader(node.getUploader()); dto.setUpdateTime(node.getUpdateTime()); - + dto.setNumber(count); //查询本地 if (isLocal) { String workPath = node.getWorkPath(); @@ -4069,6 +4386,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl String path = workPath + fileNameData; //准备获取文件的信息 if ("FILE".equals(node.getIsFile())) { +// dto.setUrl(null); +// dto.setType(null); + LOGGER.info("查询本地树的时候" + path); AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); FileItemResult fileItemResult = fileService.getFileItem(path); diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java b/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java index 1b456bf..86b25be 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/base/AbstractS3BaseFileService.java @@ -59,10 +59,10 @@ public abstract class AbstractS3BaseFileService

extends A return s3FileList(folderPath); } -// @Override -// public List fileLists(String folderPath) { -// return s3FileLists(folderPath); -// } + @Override + public List fileLists(String folderPath) { + return s3FileLists(folderPath); + } @Override public List fileListData(String path, String name ) { return s3FileListData(path,name); @@ -353,86 +353,87 @@ public abstract class AbstractS3BaseFileService

extends A return path.startsWith("/") ? path : "/" + path; } //这个方法有用 获取指定路径下的所有的文件以及文件夹 -// public List s3FileLists(String path) { -// String bucketName = param.getBucketName(); -// path = StringUtils.trimStartSlashes(path); // 去掉路径开头的斜杠 -// String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR)); -// -// List fileItemList = new ArrayList<>(); -// -// // 调用递归方法获取文件列表 -// listFilesInDirectory(bucketName, fullPath, path, fileItemList); -// -// return fileItemList; -// } -// -// private void listFilesInDirectory(String bucketName, String fullPath, String path, List fileItemList) { -// ListObjectsRequest listObjectsRequest = new ListObjectsRequest() -// .withBucketName(bucketName) -// .withPrefix(fullPath) // 设置前缀为当前路径 -// .withMaxKeys(1000) // 每次最多返回 1000 个对象 -// .withDelimiter("/"); // 使用 "/" 作为分隔符 -// -// ObjectListing objectListing = s3Client.listObjects(listObjectsRequest); -// boolean isFirstWhile = true; -// -// do { -// if (!isFirstWhile) { -// objectListing = s3Client.listNextBatchOfObjects(objectListing); // 处理分页 -// } -// -// // 处理文件 -// for (S3ObjectSummary s : objectListing.getObjectSummaries()) { -// FileItemResult fileItemResult = new FileItemResult(); -// -// // 跳过当前目录本身 -// if (s.getKey().equals(fullPath)) { -// continue; -// } -// -// // 获取文件名并去除前导斜杠 -// String fileName = s.getKey().substring(fullPath.length()); -// if (fileName.startsWith(ZFileConstant.PATH_SEPARATOR)) { -// fileName = fileName.substring(1); // 去掉开头的斜杠 -// } -// -// fileItemResult.setName(fileName); -// fileItemResult.setSize(s.getSize()); -// fileItemResult.setTime(s.getLastModified()); -// fileItemResult.setType(FileTypeEnum.FILE); -// fileItemResult.setPath(path); // 当前路径 -// -// // 构造完整路径并生成下载 URL -// String fullPathAndName = StringUtils.concat(path, fileItemResult.getName()); -// fileItemResult.setUrl(getDownloadUrl(fullPathAndName)); -// -// fileItemList.add(fileItemResult); -// } -// -// // 处理文件夹 -// for (String commonPrefix : objectListing.getCommonPrefixes()) { -// FileItemResult fileItemResult = new FileItemResult(); -// -// // 获取文件夹名称,去掉前导路径并修正末尾斜杠 -// String folderName = commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1); -// if (StrUtil.isEmpty(folderName) || StrUtil.equals(folderName, StringUtils.DELIMITER_STR)) { -// continue; // 跳过无效的文件夹名称 -// } -// -// fileItemResult.setName(folderName); -// fileItemResult.setType(FileTypeEnum.FOLDER); -// fileItemResult.setPath(path); // 当前路径 -// fileItemList.add(fileItemResult); -// -// // 递归处理子文件夹 -// String subFolderPath = path + folderName + ZFileConstant.PATH_SEPARATOR; // 修正路径拼接 -// String subFolderFullPath = commonPrefix; // 子文件夹的完整路径 -// listFilesInDirectory(bucketName, subFolderFullPath, subFolderPath, fileItemList); -// } -// -// isFirstWhile = false; -// } while (objectListing.isTruncated()); // 处理分页 -// } + public List s3FileLists(String path) { + String bucketName = param.getBucketName(); + path = StringUtils.trimStartSlashes(path); // 去掉路径开头的斜杠 + String fullPath = StringUtils.trimStartSlashes(StringUtils.concat(param.getBasePath(), path, ZFileConstant.PATH_SEPARATOR)); + + List fileItemList = new ArrayList<>(); + + // 调用递归方法获取文件列表 + listFilesInDirectory(bucketName, fullPath, path, fileItemList); + + return fileItemList; + } + + private void listFilesInDirectory(String bucketName, String fullPath, String path, List fileItemList) { + ListObjectsRequest listObjectsRequest = new ListObjectsRequest() + .withBucketName(bucketName) + .withPrefix(fullPath) // 设置前缀为当前路径 + .withMaxKeys(1000) // 每次最多返回 1000 个对象 + .withDelimiter("/"); // 使用 "/" 作为分隔符 + + ObjectListing objectListing = s3Client.listObjects(listObjectsRequest); + boolean isFirstWhile = true; + + do { + if (!isFirstWhile) { + objectListing = s3Client.listNextBatchOfObjects(objectListing); // 处理分页 + } + + // 处理文件 + for (S3ObjectSummary s : objectListing.getObjectSummaries()) { + FileItemResult fileItemResult = new FileItemResult(); + + // 跳过当前目录本身 + if (s.getKey().equals(fullPath)) { + continue; + } + + // 获取文件名并去除前导斜杠 + String fileName = s.getKey().substring(fullPath.length()); + if (fileName.startsWith(ZFileConstant.PATH_SEPARATOR)) { + fileName = fileName.substring(1); // 去掉开头的斜杠 + } + + fileItemResult.setName(fileName); + fileItemResult.setSize(s.getSize()); + fileItemResult.setTime(s.getLastModified()); + fileItemResult.setType(FileTypeEnum.FILE); + fileItemResult.setPath(path); // 当前路径 + + // 构造完整路径并生成下载 URL + String fullPathAndName = StringUtils.concat(path, fileItemResult.getName()); + fileItemResult.setUrl(getDownloadUrl(fullPathAndName)); + + fileItemList.add(fileItemResult); + } + + // 处理文件夹 + for (String commonPrefix : objectListing.getCommonPrefixes()) { + FileItemResult fileItemResult = new FileItemResult(); + + // 获取文件夹名称,去掉前导路径并修正末尾斜杠 + String folderName = commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1); + if (StrUtil.isEmpty(folderName) || StrUtil.equals(folderName, StringUtils.DELIMITER_STR)) { + continue; // 跳过无效的文件夹名称 + } + + fileItemResult.setName(folderName); + fileItemResult.setType(FileTypeEnum.FOLDER); + fileItemResult.setPath(path); // 当前路径 + + fileItemList.add(fileItemResult); + + // 递归处理子文件夹 + String subFolderPath = path + folderName + ZFileConstant.PATH_SEPARATOR; // 修正路径拼接 + String subFolderFullPath = commonPrefix; // 子文件夹的完整路径 + listFilesInDirectory(bucketName, subFolderFullPath, subFolderPath, fileItemList); + } + + isFirstWhile = false; + } while (objectListing.isTruncated()); // 处理分页 + } @Override diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java b/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java index 8c8dbf0..e758ca1 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/base/BaseFileService.java @@ -262,17 +262,17 @@ public interface BaseFileService { */ S3Object getObject(String bucketName, String key); -// /*** -// * 获取指定路径下的文件及文件夹 -// * -// * @param folderPath -// * 文件夹路径 -// * -// * @return 文件及文件夹列表 -// * -// * @throws Exception 获取文件列表中出现的异常 / -// */ -// List fileLists(String folderPath) throws Exception; + /*** + * 获取指定路径下的文件及文件夹 + * + * @param folderPath + * 文件夹路径 + * + * @return 文件及文件夹列表 + * + * @throws Exception 获取文件列表中出现的异常 / + */ + List fileLists(String folderPath) throws Exception; /*** * 获取指定路径下的文件及文件夹 diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java index 11721f7..681fa5e 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/LocalServiceImpl.java @@ -359,79 +359,82 @@ public class LocalServiceImpl extends AbstractProxyTransferService { } //以下是通过路径获取所有的文件以及文件夹 -// @Override -// public List fileLists(String folderPath) throws Exception { -// checkPathSecurity(folderPath); -// -// List fileItemList = new ArrayList<>(); -// -// String fullPath = StringUtils.concat(param.getFilePath() + folderPath); -// -// File file = new File(fullPath); -// -// if (!file.exists()) { -// throw new FileNotFoundException("文件不存在"); -// } -// -// // 调用递归方法处理文件夹及其内容(跳过第一个文件夹) -// listFilesInDirectory(file, folderPath, fileItemList, true); -// -// return fileItemList; -// } + @Override + public List fileLists(String folderPath) throws Exception { + checkPathSecurity(folderPath); -// private void listFilesInDirectory(File file, String folderPath, List fileItemList, boolean skipFirstFolder) throws IOException { -// // 跳过第一个文件夹(folderPath),从它下面的内容开始处理 -// if (skipFirstFolder) { -// // 如果当前文件是 folderPath, 直接跳过,开始处理它的子文件夹 -// if (file.isDirectory()) { -// File[] files = file.listFiles(); -// if (files != null) { -// for (File f : files) { -// // 递归进入下一级文件夹,路径应该是 folderPath + 当前文件夹名 -// listFilesInDirectory(f, folderPath, fileItemList, false); -// } -// } -// } -// } else { -// // 处理当前的文件夹或文件 -// if (file.isDirectory()) { -// // 对于文件夹,路径是 folderPath -// fileItemList.add(fileToFileItems(file, folderPath)); -// -// // 递归处理当前文件夹内部的文件和文件夹 -// File[] files = file.listFiles(); -// if (files != null) { -// for (File f : files) { -// // 递归进入下一级文件夹,路径是 folderPath + 当前文件夹名 -// String newPath = folderPath + file.getName(); -// listFilesInDirectory(f, newPath, fileItemList, false); -// } -// } -// } else { -// // 对于文件,路径是 folderPath -// fileItemList.add(fileToFileItems(file, folderPath)); -// } -// } -// } -// //lilin增加 -// private FileItemResult fileToFileItems(File file, String folderPath) { -// FileItemResult result = new FileItemResult(); -// result.setName(file.getName()); -// result.setPath(folderPath); // 这里返回的是父目录路径,不包含文件名 -// -// // 使用 FileTypeEnum 设置文件类型 -// if (file.isDirectory()) { -// result.setType(FileTypeEnum.FOLDER); -// } else { -// result.setType(FileTypeEnum.FILE); -// } -// -// result.setSize(file.length()); -// // 设置文件的修改时间为 Date 类型 -// result.setTime(new Date(file.lastModified())); -// -// return result; -// } + List fileItemList = new ArrayList<>(); + + String fullPath = StringUtils.concat(param.getFilePath() + folderPath); + + File file = new File(fullPath); + + if (!file.exists()) { + throw new FileNotFoundException("文件不存在"); + } + + // 调用递归方法处理文件夹及其内容(跳过第一个文件夹) + listFilesInDirectory(file, folderPath, fileItemList, true); + + return fileItemList; + } + + private void listFilesInDirectory(File file, String folderPath, List fileItemList, boolean skipFirstFolder) throws IOException { + // 跳过第一个文件夹(folderPath),从它下面的内容开始处理 + if (skipFirstFolder) { + // 如果当前文件是 folderPath, 直接跳过,开始处理它的子文件夹 + if (file.isDirectory()) { + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + // 递归进入下一级文件夹,路径应该是 folderPath + 当前文件夹名 + listFilesInDirectory(f, folderPath, fileItemList, false); + } + } + } + } else { + // 处理当前的文件夹或文件 + if (file.isDirectory()) { + // 对于文件夹,路径是 folderPath + fileItemList.add(fileToFileItems(file, folderPath)); + + // 递归处理当前文件夹内部的文件和文件夹 + File[] files = file.listFiles(); + if (files != null) { + for (File f : files) { + // 递归进入下一级文件夹,路径是 folderPath + 当前文件夹名 + String newPath = folderPath + file.getName(); + listFilesInDirectory(f, newPath, fileItemList, false); + } + } + } else { + // 对于文件,路径是 folderPath + fileItemList.add(fileToFileItems(file, folderPath)); + } + } + } + //lilin增加 + private FileItemResult fileToFileItems(File file, String folderPath) { + FileItemResult result = new FileItemResult(); + result.setName(file.getName()); + result.setPath(folderPath); // 这里返回的是父目录路径,不包含文件名 + + // 使用 FileTypeEnum 设置文件类型 + if (file.isDirectory()) { + result.setType(FileTypeEnum.FOLDER); + } else { + result.setType(FileTypeEnum.FILE); + if (file.isFile()) { + result.setUrl(getDownloadUrl(folderPath + file.getName())); + } + } + + result.setSize(file.length()); + // 设置文件的修改时间为 Date 类型 + result.setTime(new Date(file.lastModified())); + + return result; + } @Override