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 8cf2ffc..60a6b84 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 @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import cn.hutool.json.JSONUtil; +import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectInputStream; import com.amazonaws.util.IOUtils; @@ -412,7 +413,13 @@ public class TsFilesServiceImpl extends ServiceImpl impl files1.setUploadTime(tsFiles.getUploadTime()); files1.setUploader(loginuser.getUsername()); files1.setFileName(name); - files1.setFileSize(sizeStr); + if ("0.000".equals(sizeStr)) { + files1.setFileSize("0.001"); + + } else { + files1.setFileSize(sizeStr); + } + filesToSave.add(files1); } catch (NumberFormatException e) { LOGGER.error("文件大小必须是有效的数字"); @@ -2235,6 +2242,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl } return destRoot.toFile(); } + // 转换Unix权限位 private String getPosixMode(int mode) { return String.format("%s%s%s %s%s%s %s%s%s", @@ -2242,6 +2250,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl (mode & 0040) != 0 ? "r" : "-", (mode & 0020) != 0 ? "w" : "-", (mode & 0010) != 0 ? "x" : "-", (mode & 0004) != 0 ? "r" : "-", (mode & 0002) != 0 ? "w" : "-", (mode & 0001) != 0 ? "x" : "-"); } + /** * 验证目标路径安全性(防止路径穿越攻击) */ @@ -2286,7 +2295,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl /** * 智能规范化压缩包内路径 - * @param entryPath 压缩包内原始路径 + * + * @param entryPath 压缩包内原始路径 * @param archiveFilename 压缩包文件名(如"111.tar.gz") * @return 处理后的路径 */ @@ -2308,7 +2318,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl /** * 安全规范化压缩包内路径 - * @param entryPath 原始路径(如 "111/222/3.txt") + * + * @param entryPath 原始路径(如 "111/222/3.txt") * @param archiveName 压缩包文件名(如 "111.7z") * @return 规范化后的路径(如 "222/3.txt") */ @@ -2382,7 +2393,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl /** * 智能路径规范化(修复7z/tar.gz多层级问题) - * @param entryPath 压缩包内原始路径(如 "111/222/3.txt") + * + * @param entryPath 压缩包内原始路径(如 "111/222/3.txt") * @param archiveName 压缩包文件名(如 "111.7z") * @return 规范化后的路径(如 "222/3.txt") */ @@ -2413,7 +2425,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - /** * 设置文件权限(兼容Unix系统) * @@ -2561,7 +2572,41 @@ public class TsFilesServiceImpl extends ServiceImpl impl List files = service.fileListData(path, fileName + "/"); if (files != null) { // 对每个文件的路径进行规范化 - for (FileItemResult file : files) { +// 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); @@ -2575,7 +2620,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 // 判断是否为 "0.00",如果是,则直接设置为 0 if ("0.00".equals(fileSizeFormatted)) { - file.setSize((long) 0); // 如果文件大小为 0.00,直接设置为 0 + file.setSize((long) 0.001); // 如果文件大小为 0.00,直接设置为 0 } else { // 否则,将文件大小转换为 long(去掉小数部分) file.setSize((long) Double.parseDouble(fileSizeFormatted)); @@ -2592,22 +2637,17 @@ public class TsFilesServiceImpl extends ServiceImpl impl file.setParentId(tsFiles1.getParentId()); file.setId(tsFiles1.getId()); } - } - - -// files.forEach(file -> { -// -// }); + }); // 同步添加(线程安全) - synchronized (targetList) { - targetList.addAll(files); - } + + targetList.addAll(files); + } } else { List files = service.fileListData(path, fileName); if (files != null) { // 对每个文件的路径进行规范化 - for (FileItemResult file : files) { + files.forEach(file -> { String normalizedPath = ensurePathFormat(file.getPath()); String ProcessingPath = processingPath(normalizedPath, nodeId); @@ -2622,7 +2662,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 // 判断是否为 "0.00",如果是,则直接设置为 0 if ("0.00".equals(fileSizeFormatted)) { - file.setSize((long) 0); // 如果文件大小为 0.00,直接设置为 0 + file.setSize((long) 0.001); // 如果文件大小为 0.00,直接设置为 0 } else { // 否则,将文件大小转换为 long(去掉小数部分) file.setSize((long) Double.parseDouble(fileSizeFormatted)); @@ -2639,11 +2679,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl file.setParentId(tsFiles1.getParentId()); file.setId(tsFiles1.getId()); } - } + }); // 同步添加(线程安全) - synchronized (targetList) { - targetList.addAll(files); - } + + targetList.addAll(files); + } } } @@ -2673,11 +2713,19 @@ 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; - }); +// 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) + .collect(Collectors.toList()); + + fileList.clear(); + fileList.addAll(uniqueFiles); } public TsFiles compareFiles(List minioFiles, @@ -2820,19 +2868,39 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 辅助方法:获取 MinIO 文件 MD5 private String getMinioMD5(FileItemResult file, String bucketName) { +// AbstractBaseFileService minioService = storageSourceContext.getByStorageKey("minio"); +// String key = StringUtils.concat(file.getPath(), file.getName()); +// +// try { +// if (file.getSize() <= 5L * 1024 * 1024 * 1024) { +// return minioService.getObjectMetadata(bucketName, key).getETag().replace("\"", ""); +// } else { +// try (S3Object s3Object = minioService.getObject(bucketName, key)) { +// return calculateMD5(s3Object.getObjectContent()); +// } +// } +// } catch (Exception e) { +// LOGGER.error("MinIO MD5获取失败: {}", key, e); +// return null; +// } AbstractBaseFileService minioService = storageSourceContext.getByStorageKey("minio"); - String key = StringUtils.concat(file.getPath(), file.getName()); + String key = normalizePath(file.getPath()) + file.getName(); try { - if (file.getSize() <= 5L * 1024 * 1024 * 1024) { - return minioService.getObjectMetadata(bucketName, key).getETag().replace("\"", ""); + ObjectMetadata metadata = minioService.getObjectMetadata(bucketName, key); + String eTag = metadata.getETag(); + + // 单部分上传的 ETag 是完整 MD5,可直接使用 + if (eTag != null && !eTag.contains("-")) { + return eTag.replace("\"", ""); } else { - try (S3Object s3Object = minioService.getObject(bucketName, key)) { - return calculateMD5(s3Object.getObjectContent()); + // 多部分上传需重新计算 + try (InputStream stream = minioService.getObject(bucketName, key).getObjectContent()) { + return calculateMD5(stream); } } } catch (Exception e) { - LOGGER.error("MinIO MD5获取失败: {}", key, e); + LOGGER.error("Failed to get MinIO MD5: {}", key, e); return null; } } @@ -4002,15 +4070,14 @@ public class TsFilesServiceImpl extends ServiceImpl impl FileItemResult fileItemResult = fileService.getFileItem(path); if (fileItemResult != null || fileItemResult.getName() != null) { dto.setUrl(fileItemResult.getUrl()); - String ProcessingPath = processingPath(fileItemResult.getPath(), node.getNodeId()); - dto.setPath(ProcessingPath); - //如果是压缩文件 类型就给zip - boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems); - if (isValid) { - dto.setType("ZIP"); - } else { - dto.setType(fileItemResult.getType().getValue()); - } + dto.setType(fileItemResult.getType().getValue()); +// //如果是压缩文件 类型就给zip +// boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems); +// if (isValid) { +// dto.setType("ZIP"); +// } else { +// dto.setType(fileItemResult.getType().getValue()); +// } } else { dto.setUrl(null); dto.setType(null); @@ -4037,13 +4104,14 @@ public class TsFilesServiceImpl extends ServiceImpl impl } else { dto.setUrl(fileItemResult.getUrl()); - //如果是压缩文件 类型就给zip - boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems); - if (isValid) { - dto.setType("ZIP"); - } else { - dto.setType(fileItemResult.getType().getValue()); - } + dto.setType(fileItemResult.getType().getValue()); +// //如果是压缩文件 类型就给zip +// boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems); +// if (isValid) { +// dto.setType("ZIP"); +// } else { +// dto.setType(fileItemResult.getType().getValue()); +// } } } } @@ -4206,11 +4274,13 @@ public class TsFilesServiceImpl extends ServiceImpl impl */ @Override public String readFileContent(String id) throws IOException { + StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); TsFiles tsFiles = tsFilesMapper.selectById(id); // 1. 路径标准化与安全校验 Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName()); + StringBuilder content = new StringBuilder(); // 使用缓冲流读取大文件(减少内存占用) @@ -4276,7 +4346,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl * 返回值说明: com.yfd.platform.config.ResponseResult操作结果 ***********************************/ @Override - public void batchUpdateFile(String id, List modifications)throws IOException { + public void batchUpdateFile(String id, List modifications) throws IOException { StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); TsFiles tsFile = tsFilesMapper.selectById(id); if (tsFile == null) { @@ -4334,7 +4404,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl /** * 应用所有修改到文件内容 - * @param lines 文件每一行的内容 + * + * @param lines 文件每一行的内容 * @param modifications 修改指令集合 */ private void applyModifications(List lines, List modifications) {