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 194a854..f2bead1 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 @@ -171,11 +171,11 @@ public class TsFilesController { @PostMapping("/deleteTsFilesByIds") @ApiOperation("批量删除试验数据管理文档内容") @PreAuthorize("@el.check('del:tsFiles')") - public ResponseResult deleteTsFilesByIds(@RequestParam String ids, @RequestParam String type) { - if (StrUtil.isBlank(ids)) { + public ResponseResult deleteTsFilesByIds(@RequestParam String id, @RequestParam String type) { + if (StrUtil.isBlank(id)) { return ResponseResult.error("参数为空"); } - String[] splitIds = ids.split(","); + String[] splitIds = id.split(","); // 数组转集合 List dataset = Arrays.asList(splitIds); return ResponseResult.success(tsFilesService.deleteTsFilesByIds(dataset, type)); 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 7d6dffc..6d8fbd4 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 @@ -6,6 +6,9 @@ import cn.hutool.core.collection.CollUtil; import java.nio.file.*; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -44,6 +47,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import javax.xml.crypto.Data; import java.io.*; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -176,6 +180,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl //准备获取文件的信息 AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); FileItemResult fileItemResult = fileService.getFileItem(path); + if (fileItemResult == null || fileItemResult.getName() == null) { + LOGGER.error("{}文件没有上传到工作空间,请重新选择上传", fileNameData); + } tsFiles.setUrl(fileItemResult.getUrl()); //如果是压缩文件 类型就给zip boolean isValid = hasValidExtension(fileItemResult.getName()); @@ -199,7 +206,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl return false; // 如果传入的文件名为空,返回 false } // 判断文件名后缀是否是 .zip 或 .tar - return name.endsWith(".zip") || name.endsWith(".tar") || name.endsWith(".rar")|| name.endsWith(".xz")|| name.endsWith(".tar.gz")|| name.endsWith(".gz")|| name.endsWith(".bz2"); + return name.endsWith(".zip") || name.endsWith(".tar") || name.endsWith(".rar") || name.endsWith(".xz") || name.endsWith(".tar.gz") || name.endsWith(".gz") || name.endsWith(".bz2"); } // 递归获取所有子节点ID(包含隔代子节点) @@ -278,6 +285,14 @@ public class TsFilesServiceImpl extends ServiceImpl impl String name = names.get(i).trim(); String sizeStr = sizes.get(i).trim(); + String pathAndName = tsFiles.getWorkPath() + name; + //准备获取文件的信息 + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); + FileItemResult fileItemResult = fileService.getFileItem(pathAndName); + if (fileItemResult == null || fileItemResult.getName() == null) { + return ResponseResult.error(name + "文件没有上传到工作空间,请重新选择上传!"); + } + //通过节点ID 任务ID 路径 上级ID 文件名称 查询是否重复 QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("node_id", tsFiles.getNodeId()); @@ -634,7 +649,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl BatchDeleteRequest.DeleteItem deleteItemData = new BatchDeleteRequest.DeleteItem(); deleteItemData.setName(files.getFileName()); deleteItemData.setPassword(""); - deleteItemData.setPath(files.getWorkPath()); + if ("local".equals(type)) { + deleteItemData.setPath(files.getWorkPath()); + } else { + deleteItemData.setPath(files.getBackupPath()); + } deleteItemData.setType(FileTypeEnum.FILE); deleteItemList.add(deleteItemData); //首先通过ID集合查询所有的内容 然后放到List deleteItems里面 放好以后删除数据库 然后删除minio @@ -767,32 +786,34 @@ public class TsFilesServiceImpl extends ServiceImpl impl public int deleteFiles(String id, String type) { int deletedCount = 0; TsFiles tsFilesData = tsFilesMapper.selectById(id); - //判断是删除本地还是minio - if ("local".equals(type)) { - //删除本地的时候判断minio路径是否为空 如果为空直接删除 如果不为空把workPath修改成空 - if (StringUtils.isNotEmpty(tsFilesData.getBackupPath())) { - tsFilesData.setWorkPath(""); - tsFilesMapper.updateById(tsFilesData); - deletedCount++; - } else { - // 删除子文件记录(从数据库中删除) - tsFilesMapper.deleteById(tsFilesData.getId()); - deletedCount++; + if (tsFilesData != null) { + //判断是删除本地还是minio + if ("local".equals(type)) { + //删除本地的时候判断minio路径是否为空 如果为空直接删除 如果不为空把workPath修改成空 + if (StringUtils.isNotEmpty(tsFilesData.getBackupPath())) { + tsFilesData.setWorkPath(""); + tsFilesMapper.updateById(tsFilesData); + deletedCount++; + } else { + // 删除子文件记录(从数据库中删除) + tsFilesMapper.deleteById(tsFilesData.getId()); + deletedCount++; - } - - } else { - //删除minio的时候判断本地路径是否为空 如果为空直接删除 如果不为空把BackupPath修改成空 - if (StringUtils.isNotEmpty(tsFilesData.getWorkPath())) { - tsFilesData.setBackupPath(""); - tsFilesMapper.updateById(tsFilesData); - deletedCount++; + } } else { - // 删除子文件记录(从数据库中删除) - tsFilesMapper.deleteById(tsFilesData.getId()); - deletedCount++; + //删除minio的时候判断本地路径是否为空 如果为空直接删除 如果不为空把BackupPath修改成空 + if (StringUtils.isNotEmpty(tsFilesData.getWorkPath())) { + tsFilesData.setBackupPath(""); + tsFilesMapper.updateById(tsFilesData); + deletedCount++; + } else { + // 删除子文件记录(从数据库中删除) + tsFilesMapper.deleteById(tsFilesData.getId()); + deletedCount++; + + } } } return deletedCount; @@ -1123,6 +1144,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl } return "解压存储成功"; } catch (Exception e) { + LOGGER.error("解压失败: " + e.getMessage()); throw new RuntimeException("操作失败: " + e.getMessage()); } } @@ -1273,14 +1295,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } - /** - * 路径标准化(确保以/结尾) - */ - private String buildFolderPathq(String path) { - path = path.replaceAll("/+", "/"); // 合并多个斜杠 - return path.startsWith("/") ? path : "/" + path; // 确保绝对路径 - } - // 文件夹路径标准化(确保以/结尾) private String buildFolderPath(String path) { return path.endsWith("/") ? path : path + "/"; @@ -1489,6 +1503,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl try { processFileLists(tsFile, fileItemListMinio, fileItemListLocal); } catch (Exception e) { + LOGGER.error("通过nodeId和taskId 获取本地和minio列表失败: {}", e.getMessage(), e); e.printStackTrace(); } }); @@ -1500,6 +1515,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl try { processFileLists(tsFile, fileItemListMinio, fileItemListLocal); } catch (Exception e) { + LOGGER.error("通过ID 获取本地和minio列表失败: {}", e.getMessage(), e); e.printStackTrace(); } }); @@ -1537,28 +1553,40 @@ public class TsFilesServiceImpl extends ServiceImpl impl } // 辅助方法:处理文件列表(并行安全) - private void processFileLists(TsFiles tsFile, - List minioList, - List localList) throws Exception { - processFileList(tsFile.getBackupPath(), tsFile.getFileName(), "minio", minioList); - processFileList(tsFile.getWorkPath(), tsFile.getFileName(), "local", localList); + private void processFileLists(TsFiles tsFile, List minioList, List localList) throws Exception { + processFileList(tsFile.getBackupPath(), tsFile.getFileName(), "minio", minioList, tsFile.getIsFile()); + processFileList(tsFile.getWorkPath(), tsFile.getFileName(), "local", localList, tsFile.getIsFile()); } // 辅助方法:获取文件列表并添加到集合 - private void processFileList(String path, String fileName, String storageKey, - List targetList) throws Exception { + private void processFileList(String path, String fileName, String storageKey, List targetList, String isFile) throws Exception { if (StringUtils.isNotEmpty(path)) { AbstractBaseFileService service = storageSourceContext.getByStorageKey(storageKey); - List files = service.fileListData(path, fileName); - if (files != null) { - // 对每个文件的路径进行规范化 - files.forEach(file -> { - String normalizedPath = ensurePathFormat(file.getPath()); - file.setPath(normalizedPath); - }); - // 同步添加(线程安全) - synchronized (targetList) { - targetList.addAll(files); + if (isFile.equals("FOLDER") && storageKey.equals("minio")) { + List files = service.fileListData(path, fileName + "/"); + if (files != null) { + // 对每个文件的路径进行规范化 + files.forEach(file -> { + String normalizedPath = ensurePathFormat(file.getPath()); + file.setPath(normalizedPath); + }); + // 同步添加(线程安全) + synchronized (targetList) { + targetList.addAll(files); + } + } + } else { + List files = service.fileListData(path, fileName); + if (files != null) { + // 对每个文件的路径进行规范化 + files.forEach(file -> { + String normalizedPath = ensurePathFormat(file.getPath()); + file.setPath(normalizedPath); + }); + // 同步添加(线程安全) + synchronized (targetList) { + targetList.addAll(files); + } } } } @@ -1617,10 +1645,12 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 检查独有文件(并行过滤) List localOnly = localFiles.parallelStream() .filter(file -> !minioMap.containsKey(generateMapKey(file))) + .map(file -> formatFileTime(file)) // 格式化时间字段 .collect(Collectors.toList()); List minioOnly = minioFiles.parallelStream() .filter(file -> !localMap.containsKey(generateMapKey(file))) + .map(file -> formatFileTime(file)) // 格式化时间字段 .collect(Collectors.toList()); // 检查不一致文件(并行处理) @@ -1646,6 +1676,20 @@ public class TsFilesServiceImpl extends ServiceImpl impl return result; } + // 格式化 FileItemResult 中的时间字段 + private FileItemResult formatFileTime(FileItemResult file) { + // 假设 time 字段是 Date 类型 + Date time = file.getTime(); + if (time != null) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String formattedDate = sdf.format(time); + + // 设置格式化后的时间到 file 中 + file.setFormattedTime(formattedDate); + } + return file; + } + // 辅助方法:生成 Map Key private String generateMapKey(FileItemResult file) { return normalizePath(file.getPath()) + file.getName(); @@ -1653,6 +1697,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 辅助方法:丰富文件元数据 private FileItemResult enrichFileMetadata(FileItemResult file, String pathType) { + String aaaa = normalizePath(file.getPath()); TsFiles dbRecord = tsFilesMapper.selectOne( new QueryWrapper() .eq("file_name", file.getName()) @@ -3035,38 +3080,48 @@ public class TsFilesServiceImpl extends ServiceImpl impl ***********************************/ @Override public DualTreeResponse listLocalAndBackup(String taskId, String nodeId) { - // 1. 查询符合条件的所有节点(一次性加载,避免递归查询数据库) + // 记录方法入参 + 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); List allNodes = tsFilesMapper.selectList(queryWrapper); - // 2. 构建内存中的父子关系映射(提高查询效率) + // 2. 构建内存索引提升查询效率 Map> parentChildrenMap = allNodes.stream() .collect(Collectors.groupingBy(TsFiles::getParentId)); + LOGGER.debug("使用{}个条目构建父子映射", parentChildrenMap.size()); - // 3. 构建本地树和Minio树 + // 3. 双树构建容器 List localTrees = new ArrayList<>(); List minioTrees = new ArrayList<>(); // 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); } @@ -3083,6 +3138,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl TreeDTO dto = convertToDTO(current, isLocal); String path = isLocal ? current.getWorkPath() : current.getBackupPath(); if (dto.getPath() == null || dto.getPath().trim().isEmpty()) { + LOGGER.warn("由于路径为空,跳过节点{}", current.getId()); return null; } @@ -3137,14 +3193,17 @@ public class TsFilesServiceImpl extends ServiceImpl impl if ("FILE".equals(node.getIsFile())) { AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); FileItemResult fileItemResult = fileService.getFileItem(path); - dto.setUrl(fileItemResult.getUrl()); - //如果是压缩文件 类型就给zip - boolean isValid = hasValidExtension(fileItemResult.getName()); - if (isValid) { - dto.setType("ZIP"); - } else { - dto.setType(fileItemResult.getType().getValue()); + if (fileItemResult != null || fileItemResult.getName() != null) { + dto.setUrl(fileItemResult.getUrl()); + //如果是压缩文件 类型就给zip + boolean isValid = hasValidExtension(fileItemResult.getName()); + if (isValid) { + dto.setType("ZIP"); + } else { + dto.setType(fileItemResult.getType().getValue()); + } } + } } else { String backupPath = node.getBackupPath(); @@ -3159,13 +3218,15 @@ public class TsFilesServiceImpl extends ServiceImpl impl if ("FILE".equals(node.getIsFile())) { AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("minio"); FileItemResult fileItemResult = fileService.getFileItem(path); - dto.setUrl(fileItemResult.getUrl()); - //如果是压缩文件 类型就给zip - boolean isValid = hasValidExtension(fileItemResult.getName()); - if (isValid) { - dto.setType("ZIP"); - } else { - dto.setType(fileItemResult.getType().getValue()); + if (fileItemResult != null || fileItemResult.getName() != null) { + dto.setUrl(fileItemResult.getUrl()); + //如果是压缩文件 类型就给zip + boolean isValid = hasValidExtension(fileItemResult.getName()); + if (isValid) { + dto.setType("ZIP"); + } else { + dto.setType(fileItemResult.getType().getValue()); + } } } } diff --git a/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java b/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java index 058e48e..2cf6e52 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/model/result/FileItemResult.java @@ -38,10 +38,11 @@ public class FileItemResult implements Serializable { @ApiModelProperty(value = "下载地址", example = "http://www.example.com/a.mp4") private String url; - //用于对比Md5文件 + //用于对比Md5文件 用于展示 private String locatMd5; private String minioMd5; private String id; + private String formattedTime; /** * 获取路径和名称的组合, 并移除重复的路径分隔符 /. 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 0399a7f..3ca674c 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 @@ -164,107 +164,148 @@ public abstract class AbstractS3BaseFileService

extends A public List s3FileListData(String path, String name) { List fileItemList = new ArrayList<>(); String bucketName = param.getBucketName(); - if (path == null || StringUtils.isEmpty(path)) { - return fileItemList; + if (path == null || StringUtils.isEmpty(path)) return fileItemList; + + // 规范路径格式 + path = ensurePathWithSlash(path); + + // 明确判断是否为文件夹(仅根据 name 是否以斜杠结尾) + boolean isFolder = name.endsWith("/"); + String targetPath = buildTargetPath(path, name, isFolder); + + if (isFolder) { + // 处理文件夹 + String fullPrefix = StringUtils.trimStartSlashes( + StringUtils.concat(param.getBasePath(), targetPath) + ); + listFolderContents(bucketName, fullPrefix, targetPath, fileItemList); + } else { + // 处理文件 + FileItemResult fileItem = getFileItem(bucketName, targetPath); + if (fileItem != null) fileItemList.add(fileItem); } - - // 确保路径格式正确,并拼接目标文件夹名称(name) - path = ensurePathWithSlash(path); // 输入 path="/",处理为 "/" - String targetPath = path + name + ZFileConstant.PATH_SEPARATOR; // 目标路径:/431/ - String fullPath = StringUtils.trimStartSlashes( - StringUtils.concat(param.getBasePath(), targetPath) - ); - - // 调用时传入 name=null,确保递归时不再匹配其他同名文件夹 - listFilesInDirectory(bucketName, fullPath, targetPath, null, fileItemList, false); return fileItemList; } - private void listFilesInDirectory( - String bucketName, - String fullPath, - String path, - String name, - List fileItemList, - boolean includeAll - ) { - ListObjectsRequest listObjectsRequest = new ListObjectsRequest() - .withBucketName(bucketName) - .withPrefix(fullPath) // 关键修复:直接通过 Prefix 限定目标路径 - .withMaxKeys(1000) - .withDelimiter("/"); - - ObjectListing objectListing = s3Client.listObjects(listObjectsRequest); - boolean isFirstWhile = true; - - do { - if (!isFirstWhile) { - objectListing = s3Client.listNextBatchOfObjects(objectListing); - } - - // 处理文件 - for (S3ObjectSummary s : objectListing.getObjectSummaries()) { - if (s.getKey().equals(fullPath)) continue; - - String fileName = s.getKey().substring(fullPath.length()); - if (fileName.startsWith(ZFileConstant.PATH_SEPARATOR)) { - fileName = fileName.substring(1); - } - - // 包含条件:强制包含 或 名称匹配(仅在初始调用时检查 name) - if (includeAll || name == null || StrUtil.isEmpty(name) || fileName.equals(name)) { - FileItemResult item = new FileItemResult(); - item.setName(fileName); - item.setSize(s.getSize()); - item.setTime(s.getLastModified()); - item.setType(FileTypeEnum.FILE); - item.setPath(path); - item.setUrl(getDownloadUrl(ensurePathWithSlash(path) + fileName)); - fileItemList.add(item); - } - } - - // 处理文件夹 - for (String commonPrefix : objectListing.getCommonPrefixes()) { - String folderName = commonPrefix.substring(fullPath.length(), commonPrefix.length() - 1); - if (StrUtil.isEmpty(folderName) || folderName.equals(StringUtils.DELIMITER_STR)) continue; - - // 匹配条件:仅在初始调用时检查 name,递归时 name=null 直接包含 - boolean matchFolder = includeAll || name == null || StrUtil.isEmpty(name) || folderName.equals(name); - - if (matchFolder) { - FileItemResult folderItem = new FileItemResult(); - folderItem.setName(folderName); - folderItem.setType(FileTypeEnum.FOLDER); - folderItem.setPath(path); - fileItemList.add(folderItem); - } - - // 递归处理子文件夹时,name=null,强制包含所有子项 - String subPath = ensurePathWithSlash(path) + folderName + ZFileConstant.PATH_SEPARATOR; - String subFullPath = commonPrefix; - listFilesInDirectory( - bucketName, - subFullPath, - subPath, - null, // 关键修复:递归时不再传递 name,避免深层匹配 - fileItemList, - matchFolder // 如果父级匹配,则强制包含子项 - ); - } - - isFirstWhile = false; - } while (objectListing.isTruncated()); + /** + * 构建目标路径(确保文件夹以斜杠结尾) + */ + private String buildTargetPath(String path, String name, boolean isFolder) { + String targetPath = path + name; + // 如果是文件夹且未以斜杠结尾,则补全 + if (isFolder && !targetPath.endsWith("/")) { + targetPath += "/"; + } + return targetPath; } - // 确保路径以斜杠开头 - private String ensurePathWithSlash(String path) { - if (path.startsWith(ZFileConstant.PATH_SEPARATOR)) { - return path; - } else { - return ZFileConstant.PATH_SEPARATOR + path; + /** + * 获取单个文件信息 + */ + private FileItemResult getFileItem(String bucketName, String targetPath) { + String fullKey = StringUtils.trimStartSlashes( + StringUtils.concat(param.getBasePath(), targetPath) + ); + + try { + // 精确匹配文件键 + S3ObjectSummary objectSummary = s3Client.listObjects(bucketName, fullKey) + .getObjectSummaries() + .stream() + .filter(s -> s.getKey().equals(fullKey)) + .findFirst() + .orElse(null); + + if (objectSummary == null) return null; + + FileItemResult item = new FileItemResult(); + item.setName(extractFileName(targetPath)); + item.setSize(objectSummary.getSize()); + item.setTime(objectSummary.getLastModified()); + item.setType(FileTypeEnum.FILE); + item.setPath(extractParentPath(targetPath)); + item.setUrl(getDownloadUrl(targetPath)); + return item; + } catch (Exception e) { + LOGGER.error("获取文件信息失败: {}", fullKey, e); + return null; } } + + /** + * 列出文件夹内容(包括子目录) + */ + private void listFolderContents( + String bucketName, + String prefix, + String currentPath, + List results + ) { + ListObjectsRequest request = new ListObjectsRequest() + .withBucketName(bucketName) + .withPrefix(prefix) + .withDelimiter("/") // 使用分隔符获取子目录 + .withMaxKeys(1000); + + ObjectListing listing; + do { + listing = s3Client.listObjects(request); + + // 处理文件 + listing.getObjectSummaries().stream() + .filter(s -> !s.getKey().endsWith("/")) // 排除目录标记 + .forEach(s -> { + String fileName = s.getKey().substring(prefix.length()); + results.add(createFileItem(fileName, s, currentPath)); + }); + + // 处理子文件夹 + listing.getCommonPrefixes().forEach(commonPrefix -> { + String folderName = commonPrefix.substring(prefix.length(), commonPrefix.length()-1); + String folderPath = currentPath; + results.add(createFolderItem(folderName, folderPath)); + String folderPath1 = currentPath + folderName + "/"; + // 递归列出子目录内容 + listFolderContents(bucketName, commonPrefix, folderPath1, results); + }); + + request.setMarker(listing.getNextMarker()); + } while (listing.isTruncated()); + } + + // 辅助方法 + private FileItemResult createFileItem(String name, S3ObjectSummary summary, String path) { + FileItemResult item = new FileItemResult(); + item.setName(name); + item.setSize(summary.getSize()); + item.setTime(summary.getLastModified()); + item.setType(FileTypeEnum.FILE); + item.setPath(path); + item.setUrl(getDownloadUrl(path + name)); + return item; + } + + private FileItemResult createFolderItem(String name, String path) { + FileItemResult item = new FileItemResult(); + item.setName(name); + item.setType(FileTypeEnum.FOLDER); + item.setPath(path); + return item; + } + + private String extractFileName(String path) { + return path.contains("/") ? + path.substring(path.lastIndexOf('/') + 1) : path; + } + + private String extractParentPath(String path) { + return path.contains("/") ? + path.substring(0, path.lastIndexOf('/') + 1) : "/"; + } + + private String ensurePathWithSlash(String path) { + return path.startsWith("/") ? path : "/" + path; + } //这个方法有用 获取指定路径下的所有的文件以及文件夹 // public List s3FileLists(String path) { // String bucketName = param.getBucketName(); 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 c134c23..78d6bf0 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 @@ -94,7 +94,8 @@ public class LocalServiceImpl extends AbstractProxyTransferService { File file = new File(fullPath); if (!file.exists()) { - throw ExceptionUtil.wrapRuntime(new FileNotFoundException("文件不存在")); + return new FileItemResult(); + //throw ExceptionUtil.wrapRuntime(new FileNotFoundException("文件不存在")); } String folderPath = StringUtils.getParentPath(pathAndName); @@ -191,20 +192,22 @@ public class LocalServiceImpl extends AbstractProxyTransferService { List resultList = new ArrayList<>(); String basePath = param.getFilePath(); - // 1. 构建目标路径(确保前后有 /) - String targetPath = formatCombinedPath(folderPath, name); // 关键修改点 - - // 2. 拼接完整物理路径 + // 1. 构建目标路径(动态判断是否为目录) + String targetPath = formatCombinedPath(folderPath, name); String fullPath = Paths.get(basePath, targetPath).toString(); - File targetDir = new File(fullPath); - if (!targetDir.exists()) { + File target = new File(fullPath); + if (!target.exists()) { throw new FileNotFoundException("路径不存在: " + fullPath); } - // 3. 列出目录内容 - if (targetDir.isDirectory()) { - listFilesInDirectory(targetDir, targetPath, resultList); + // 2. 直接处理文件或目录 + if (target.isDirectory()) { + listFilesInDirectory(target, targetPath, resultList); + } else { + // 如果是文件,直接添加到结果列表 + FileItemResult item = convertToFileItem(target, folderPath); + resultList.add(item); } return resultList; @@ -214,26 +217,58 @@ public class LocalServiceImpl extends AbstractProxyTransferService { * 构建规范化路径(根路径为 /,其他路径为 /path/) */ private String formatCombinedPath(String folderPath, String name) { - StringBuilder pathBuilder = new StringBuilder(); - - // 处理 folderPath - if (StringUtils.isBlank(folderPath) || folderPath.equals("/")) { - pathBuilder.append("/"); + // 确保 folderPath 以 "/" 开头,如果不是,则添加 "/" + if (folderPath == null || folderPath.trim().isEmpty()) { + folderPath = "/"; } else { - pathBuilder.append(folderPath.trim()); - if (!folderPath.endsWith("/")) { - pathBuilder.append("/"); + folderPath = folderPath.trim(); + if (!folderPath.startsWith("/")) { + folderPath = "/" + folderPath; + } + // 去掉 folderPath 末尾的斜杠(除非是根路径) + if (!folderPath.equals("/") && folderPath.endsWith("/")) { + folderPath = folderPath.substring(0, folderPath.length() - 1); } } - // 处理 name - if (StringUtils.isNotBlank(name)) { - String sanitizedName = name.trim().replaceAll("/+", ""); // 防止注入额外路径 - pathBuilder.append(sanitizedName).append("/"); + // 如果 name 非空,拼接到 folderPath 后 + if (name != null && !name.trim().isEmpty()) { + folderPath += "/" + name; } - // 最终格式化(替换多斜杠为单斜杠) - return pathBuilder.toString().replaceAll("/+", "/"); + return folderPath; + +// StringBuilder pathBuilder = new StringBuilder(); +// +// // 处理 folderPath +// if (StringUtils.isBlank(folderPath)) { +// pathBuilder.append("/"); +// } else { +// // 确保 folderPath 以 / 开头且不以 / 结尾(除非是根路径) +// String trimmedPath = folderPath.trim(); +// if (!trimmedPath.startsWith("/")) { +// pathBuilder.append("/"); +// } +// pathBuilder.append(trimmedPath.replaceAll("/+$", "")); +// if (!trimmedPath.endsWith("/") && !trimmedPath.equals("/")) { +// pathBuilder.append("/"); +// } +// } +// +// // 处理 name(如果是文件,不添加 /) +// if (StringUtils.isNotBlank(name)) { +// String sanitizedName = name.trim().replaceAll("/+", ""); // 防止路径注入 +// pathBuilder.append(sanitizedName); +// +// // 如果是目录,添加 / +// File tempFile = new File(Paths.get(param.getFilePath(), folderPath, sanitizedName).toString()); +// if (tempFile.isDirectory()) { +// pathBuilder.append("/"); +// } +// } +// +// // 最终格式化(合并多斜杠) +// return pathBuilder.toString().replaceAll("/+", "/"); } /** @@ -250,7 +285,7 @@ public class LocalServiceImpl extends AbstractProxyTransferService { // 递归处理子目录 if (file.isDirectory()) { - String newParentPath = parentPath + file.getName() + "/"; + String newParentPath = parentPath + "/" + file.getName() + "/"; listFilesInDirectory(file, newParentPath, resultList); } } @@ -384,11 +419,30 @@ public class LocalServiceImpl extends AbstractProxyTransferService { FileUtil.mkdir(parentPath); } - File uploadToFileObj = new File(uploadPath); - BufferedOutputStream outputStream = FileUtil.getOutputStream(uploadToFileObj); - IoUtil.copy(inputStream, outputStream); - IoUtil.close(outputStream); - IoUtil.close(inputStream); +// File uploadToFileObj = new File(uploadPath); +// BufferedOutputStream outputStream = FileUtil.getOutputStream(uploadToFileObj); +// IoUtil.copy(inputStream, outputStream); +// IoUtil.close(outputStream); +// IoUtil.close(inputStream); +// try (BufferedOutputStream outputStream = FileUtil.getOutputStream(new File(uploadPath))) { +// IoUtil.copy(inputStream, outputStream); +// } catch (IOException e) { +// throw new RuntimeException("文件保存失败", e); +// } finally { +// IoUtil.close(inputStream); +// } + + try (BufferedOutputStream outputStream = FileUtil.getOutputStream(new File(uploadPath))) { + byte[] buffer = new byte[8192]; // 8KB 缓冲区 + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + } catch (IOException e) { + throw new RuntimeException("文件保存失败", e); + } finally { + IoUtil.close(inputStream); + } } diff --git a/java/src/main/resources/application-dev.yml b/java/src/main/resources/application-dev.yml index 7be4ef2..59f23d3 100644 --- a/java/src/main/resources/application-dev.yml +++ b/java/src/main/resources/application-dev.yml @@ -24,6 +24,10 @@ spring: multipart: max-file-size: 50GB max-request-size: 50GB + tomcat: + max-swallow-size: -1 + connection-timeout: 86400000 + max-http-form-post-size: -1 logging: file: name: logs/projectname.log