From 9c0486770e5f81393c050a046b23721843bd78ee Mon Sep 17 00:00:00 2001 From: lilin Date: Sun, 23 Mar 2025 12:56:43 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=810323?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/CompressionFormat.java | 68 ++ .../service/impl/TsFilesServiceImpl.java | 1008 ++++++++++++++--- .../service/impl/TsNodesServiceImpl.java | 17 + .../controller/FilesController.java | 8 +- .../service/IFilesService.java | 3 +- .../service/impl/FilesServiceImpl.java | 36 +- .../service/impl/ProjectServiceImpl.java | 15 +- .../base/AbstractS3BaseFileService.java | 57 +- .../storage/service/base/BaseFileService.java | 10 + .../service/impl/LocalServiceImpl.java | 16 + .../service/impl/MinIOServiceImpl.java | 5 + 11 files changed, 1079 insertions(+), 164 deletions(-) create mode 100644 java/src/main/java/com/yfd/platform/modules/experimentalData/enums/CompressionFormat.java diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/enums/CompressionFormat.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/enums/CompressionFormat.java new file mode 100644 index 0000000..0ac8215 --- /dev/null +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/enums/CompressionFormat.java @@ -0,0 +1,68 @@ +package com.yfd.platform.modules.experimentalData.enums; + +import ch.qos.logback.core.status.Status; +import io.netty.handler.codec.compression.CompressionException; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +/** + * 支持的压缩格式枚举 + * - ZIP: 标准ZIP格式 + * - TAR: 未压缩的TAR归档 + * - TAR_GZ: GZIP压缩的TAR归档 + * - TAR_BZ2: BZIP2压缩的TAR归档 + * - TAR_XZ: XZ压缩的TAR归档 + */ +@Getter +@AllArgsConstructor +public enum CompressionFormat { + ZIP("zip"), + TAR_GZ("tar.gz", "tgz"), + SEVEN_ZIP("7z"); + + private final Set aliases = new HashSet<>(); + + CompressionFormat(String... aliases) { + Collections.addAll(this.aliases, aliases); + } + + /** + * 从字符串转换压缩格式 + * @param formatStr 格式字符串(不区分大小写) + * @return 对应的枚举值 + * @throws IllegalArgumentException 格式不支持时抛出 + */ + public static CompressionFormat fromString(String formatStr) { + String normalized = formatStr.toLowerCase().trim(); + for (CompressionFormat format : values()) { + if (format.aliases.contains(normalized)) { + return format; + } + } + throw new IllegalArgumentException("不支持的压缩格式: " + formatStr + + ",支持格式: " + getSupportedFormats()); + } + + /** + * 获取支持格式列表 + * @return 格式列表字符串(逗号分隔) + */ + public static String getSupportedFormats() { + return Arrays.stream(values()) + .map(f -> String.join("|", f.aliases)) + .collect(Collectors.joining(", ")); + } + + /** + * 获取主扩展名 + */ + public String getPrimaryExtension() { + return aliases.iterator().next(); + } +} + 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 669f635..1752f65 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,22 +3,19 @@ package com.yfd.platform.modules.experimentalData.service.impl; import cn.hutool.core.collection.CollUtil; +import java.nio.charset.StandardCharsets; 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.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; 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; @@ -27,6 +24,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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; @@ -42,11 +40,27 @@ import com.yfd.platform.modules.storage.service.base.AbstractBaseFileService; import com.yfd.platform.system.domain.LoginUser; import com.yfd.platform.system.domain.SysDictionaryItems; import com.yfd.platform.system.mapper.SysDictionaryItemsMapper; -import com.yfd.platform.system.service.ISysDictionaryItemsService; import com.yfd.platform.utils.StringUtils; import io.netty.channel.ChannelInboundHandlerAdapter; +import com.github.junrar.Archive; +import com.github.junrar.rarfile.FileHeader; +import com.github.junrar.exception.RarException; +import io.netty.handler.codec.compression.CompressionException; +import org.apache.commons.compress.archivers.ArchiveEntry; +import org.apache.commons.compress.archivers.ArchiveOutputStream; +import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; +import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream; +import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; +import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; +import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -59,6 +73,7 @@ import javax.xml.crypto.Data; import java.io.*; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; @@ -97,6 +112,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + // 从数据库获取的压缩类型列表 + private List compressSuffixes; + /********************************** * 用途说明: 分页查询试验数据管理-文档内容 * 参数说明 @@ -182,14 +200,14 @@ public class TsFilesServiceImpl extends ServiceImpl impl } List records = tsFilesPage.getRecords(); for (TsFiles tsFiles : records) { - //如果是文件夹 跳过循环 - if (tsFiles.getIsFile().equals("FOLDER")) { - if (tsFiles.getUpdateTime() == null) { - tsFiles.setUpdateTime(tsFiles.getUploadTime()); - } - continue; - } + + // 获取原始路径和 nodeId String workPath = tsFiles.getWorkPath(); + + // 拼接需要去掉的部分 + String toRemove = File.separator + nodeId; + + String fileNameData = tsFiles.getFileName(); //主要是用于文件路径加名称 String path = workPath + fileNameData; @@ -210,6 +228,22 @@ public class TsFilesServiceImpl extends ServiceImpl impl if (tsFiles.getUpdateTime() == null) { tsFiles.setUpdateTime(tsFiles.getUploadTime()); } + + // 将路径分隔符统一为 "/" + String standardizedWorkPath = workPath.replace("\\", "/"); + String standardizedToRemove = toRemove.replace("\\", "/"); + + // 检查 standardizedWorkPath 是否包含 standardizedToRemove + if (standardizedWorkPath.contains(standardizedToRemove)) { + // 去掉目标部分 + String newWorkPath = standardizedWorkPath.replace(standardizedToRemove, ""); + // 确保路径最后保留一个 '/' + if (!newWorkPath.endsWith("/")) { + newWorkPath += "/"; + } + // 更新路径 + tsFiles.setWorkPath(newWorkPath); // 更新路径 + } } tsFilesPage.setRecords(records); // 同步到 tsFilesPage System.out.println("Updated records: " + records); @@ -273,9 +307,9 @@ public class TsFilesServiceImpl extends ServiceImpl impl if (StringUtils.isNotBlank(id)) { queryWrapper.eq("parent_id", id); } - if (StringUtils.isNotBlank(path)) { - queryWrapper.eq("work_path", path); - } +// if (StringUtils.isNotBlank(path)) { +// queryWrapper.eq("work_path", path); +// } if (StringUtils.isNotBlank(nodeId)) { queryWrapper.eq("node_id", nodeId); } @@ -284,6 +318,28 @@ public class TsFilesServiceImpl extends ServiceImpl impl } queryWrapper.isNotNull("work_path"); List tsFiles = tsFilesMapper.selectList(queryWrapper); + + for (TsFiles tsFiles1 : tsFiles) { + // 获取原始路径和 nodeId + String workPath = tsFiles1.getWorkPath(); + // 拼接需要去掉的部分 + String toRemove = File.separator + nodeId; + // 将路径分隔符统一为 "/" + String standardizedWorkPath = workPath.replace("\\", "/"); + String standardizedToRemove = toRemove.replace("\\", "/"); + + // 检查 standardizedWorkPath 是否包含 standardizedToRemove + if (standardizedWorkPath.contains(standardizedToRemove)) { + // 去掉目标部分 + String newWorkPath = standardizedWorkPath.replace(standardizedToRemove, ""); + // 确保路径最后保留一个 '/' + if (!newWorkPath.endsWith("/")) { + newWorkPath += "/"; + } + // 更新路径 + tsFiles1.setWorkPath(newWorkPath); // 更新路径 + } + } return tsFiles; } @@ -312,6 +368,27 @@ public class TsFilesServiceImpl extends ServiceImpl impl return ResponseResult.error("文件名称和文件大小的列表长度不一致!"); } +// +// //判断文件夹是否创建 +// AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); +// boolean flag = fileService.isFolderCreated(File.separator + tsFiles.getNodeId()); +// //如果是false 说明没有创建 那就新建一个文件夹 +// if(!flag){ +// //本地创建文件夹 +// NewFolderRequest newFolderRequest = new NewFolderRequest(); +// newFolderRequest.setStorageKey("local"); +// newFolderRequest.setPath(tsFiles.getWorkPath()); +// newFolderRequest.setPassword(null); +// newFolderRequest.setName(tsFiles.getFileName()); +// +// AbstractBaseFileService fileServiceData = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); +// boolean flagData = fileServiceData.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); +// if(!flagData){ +// LOGGER.error("创建节点文件夹失败!"); +// return ResponseResult.error("新增文件失败!"); +// } +// } + List filesToSave = new ArrayList<>(); // 设置当前时间 LocalDateTime now = LocalDateTime.now(); @@ -325,8 +402,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl String pathAndName = tsFiles.getWorkPath() + name; //准备获取文件的信息 - AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); - FileItemResult fileItemResult = fileService.getFileItem(pathAndName); + AbstractBaseFileService fileServicefile = storageSourceContext.getByStorageKey("local"); + FileItemResult fileItemResult = fileServicefile.getFileItem(pathAndName); if (fileItemResult == null || fileItemResult.getName() == null) { return ResponseResult.error(name + "文件没有上传到工作空间,请重新选择上传!"); } @@ -422,6 +499,28 @@ public class TsFilesServiceImpl extends ServiceImpl impl if (count > 0) { return ResponseResult.error("文件夹名称已存在!"); } + +// //判断文件夹是否创建 +// AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); +// boolean flag = fileService.isFolderCreated(File.separator + tsFiles.getNodeId()); +// //如果是false 说明没有创建 那就新建一个文件夹 +// if(!flag){ +// //本地创建文件夹 +// NewFolderRequest newFolderRequest = new NewFolderRequest(); +// newFolderRequest.setStorageKey("local"); +// newFolderRequest.setPath(tsFiles.getWorkPath()); +// newFolderRequest.setPassword(null); +// newFolderRequest.setName(tsFiles.getFileName()); +// +// AbstractBaseFileService fileServiceData = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); +// boolean flagData = fileServiceData.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); +// if(!flagData){ +// LOGGER.error("创建节点文件夹失败!"); +// return ResponseResult.error("新增文件夹失败!"); +// } +// } + + //本地创建文件夹 NewFolderRequest newFolderRequest = new NewFolderRequest(); newFolderRequest.setStorageKey("local"); @@ -429,9 +528,10 @@ public class TsFilesServiceImpl extends ServiceImpl impl newFolderRequest.setPassword(null); newFolderRequest.setName(tsFiles.getFileName()); - AbstractBaseFileService fileService = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); - boolean flag = fileService.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); - if (flag) { + AbstractBaseFileService fileServicefolder = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); + boolean flagfolder = fileServicefolder.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); + if (flagfolder) { +// tsFiles.setWorkPath(File.separator + tsFiles.getNodeId() + tsFiles.getWorkPath()); int valueAdded = tsFilesMapper.insert(tsFiles); if (valueAdded == 1) { LOGGER.info("表结构和本地都创建文件夹成功"); @@ -920,7 +1020,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 设置压缩级别,可以选择调整 zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别 // 调用压缩方法 - Boolean value = compressDirectoriesToZip(sourceDirs, zipOut); + Boolean value = compressToSameDirectory(sourceDirs, compressedFormat); if (value) { zipOut.finish(); // 确保所有数据都已写入 //表结构增加 nodeId, String TaskId @@ -971,7 +1071,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl } else { //如果为空就新增 int valueAdded = tsFilesMapper.insert(tsFiles); - returnResult = "压缩成功"; + if (valueAdded == 1) { + returnResult = "压缩成功"; + } else { + returnResult = "压缩失败"; + } } } else { @@ -981,7 +1085,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl } else { //如果为空就新增 int valueAdded = tsFilesMapper.insert(tsFiles); - returnResult = "压缩成功"; + if (valueAdded == 1) { + returnResult = "压缩成功"; + } else { + returnResult = "压缩失败"; + } } } returnResult = "压缩成功"; @@ -994,117 +1102,317 @@ public class TsFilesServiceImpl extends ServiceImpl impl return returnResult; } - private boolean compressDirectoriesToZip(List sourceDirs, ZipArchiveOutputStream zipOut) { - // 用于记录已处理的规范路径,防止循环引用 - Set processedPaths = new HashSet<>(); - for (Path sourceDir : sourceDirs) { - // 遍历时传入已处理路径集合 - if (!compressDirectoryToZip(sourceDir, zipOut, "", processedPaths)) { - return false; - } - } - return true; - } + // ================== 核心方法改造 ================== /** - * 递归压缩目录到ZIP文件 - * - * @param sourcePath 当前要压缩的目录/文件路径 - * @param zipOut ZIP输出流 - * @param parentEntryName 当前路径在ZIP中的父目录(用于构建层级) - * @param processedPaths 已处理的规范路径集合(用于检测循环引用) - * @return 压缩成功返回true,否则false + * 多路径压缩到各自同级目录 + * @param sourcePaths 要压缩的路径集合 + * @param compressionFormat 前端传入的压缩格式字符串 + * @return 压缩成功返回true,失败返回false */ - private boolean compressDirectoryToZip(Path sourcePath, - ZipArchiveOutputStream zipOut, - String parentEntryName, - Set processedPaths) { + public boolean compressToSameDirectory(List sourcePaths, String compressionFormat) { try { - // === 路径规范化处理 === - Path canonicalPath = sourcePath.toRealPath(); + // 转换压缩格式 + CompressionFormat format = CompressionFormat.fromString(compressionFormat); - // === 防循环检测 === - if (Files.isDirectory(sourcePath)) { - if (processedPaths.contains(canonicalPath)) { - return true; + // 遍历所有路径进行压缩 + for (Path sourcePath : sourcePaths) { + // 验证路径有效性 + validatePath(sourcePath); + + // 生成目标路径 + Path targetPath = generateTargetPath(sourcePath, format); + + // 执行压缩 + if (!compressSinglePath(sourcePath, targetPath, format)) { + return false; // 任一压缩失败则终止 } - processedPaths.add(canonicalPath); } - - // === 统一路径处理 === - String entryName = buildEntryName(sourcePath, parentEntryName); - - if (Files.isDirectory(sourcePath)) { - // === 处理目录 === - // 确保目录条目以/结尾 - if (!entryName.endsWith("/")) { - entryName += "/"; - } - - zipOut.putArchiveEntry(new ZipArchiveEntry(entryName)); - zipOut.closeArchiveEntry(); - - // 递归处理子项 - try (DirectoryStream dirStream = Files.newDirectoryStream(sourcePath)) { - for (Path child : dirStream) { - if (!compressDirectoryToZip(child, zipOut, entryName, processedPaths)) { - return false; - } - } - } - } else { - // === 处理文件 === - addFileToZip(sourcePath.toFile(), zipOut, entryName); - } - return true; + return true; // 全部压缩成功 + } catch (IllegalArgumentException e) { + System.err.println("格式错误: " + e.getMessage()); + return false; } catch (IOException e) { + System.err.println("压缩失败: " + e.getMessage()); e.printStackTrace(); return false; } } - /** - * 构建ZIP条目名称(关键修改点) - */ - private String buildEntryName(Path sourcePath, String parentEntryName) { - // 如果是根文件(无父目录),直接返回文件名 - if (parentEntryName.isEmpty()) { - return sourcePath.getFileName().toString(); - } + // ================== 核心私有方法 ================== - // 合并父路径和当前文件名 - return parentEntryName + sourcePath.getFileName().toString(); + /** + * 验证路径有效性 + * @param path 待验证路径 + * @throws IOException 路径无效时抛出 + */ + private void validatePath(Path path) throws IOException { + if (!Files.exists(path)) { + throw new FileNotFoundException("路径不存在: " + path); + } + if (!Files.isReadable(path)) { + throw new AccessDeniedException("无读取权限: " + path); + } } /** - * 增强版文件添加方法 + * 生成目标压缩文件路径 + * @param sourcePath 源路径 + * @param format 压缩格式 + * @return 目标路径(同级目录+格式后缀) */ - private void addFileToZip(File file, ZipArchiveOutputStream zipOut, String entryName) - throws IOException { - // === 重要修改:移除路径前缀 === - // 确保条目名称是纯文件名(当处理单个文件时) - if (!entryName.contains("/") && !entryName.isEmpty()) { - entryName = file.getName(); + private Path generateTargetPath(Path sourcePath, CompressionFormat format) { + // 获取无扩展名的文件名 + String baseName = FilenameUtils.removeExtension(sourcePath.getFileName().toString()); + // 添加压缩后缀 + String fileName = baseName + "." + format.getPrimaryExtension(); + return sourcePath.resolveSibling(fileName); + } + + /** + * 单路径压缩核心方法 + * @return 压缩成功返回true,失败返回false + */ + private boolean compressSinglePath(Path sourcePath, Path targetPath, CompressionFormat format) { + try { + switch (format) { + case ZIP: + compressToZip(sourcePath, targetPath); + break; + case TAR_GZ: + compressToTarGz(sourcePath, targetPath); + break; + case SEVEN_ZIP: + compressTo7z(sourcePath, targetPath); + break; + } + return true; + } catch (IOException e) { + System.err.println("压缩失败: " + sourcePath); + e.printStackTrace(); + return false; + } + } + + // ================== ZIP格式压缩实现 ================== + + private void compressToZip(Path source, Path target) throws IOException { + try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(Files.newOutputStream(target))) { + Set processedPaths = new HashSet<>(); + processZipEntry(source, zipOut, "", processedPaths); + } + } + + private void processZipEntry(Path currentPath, + ZipArchiveOutputStream zipOut, + String parentEntry, + Set processedPaths) throws IOException { + // 获取相对于压缩根的路径 + Path relativePath = getRelativePath(currentPath); + + // 构建条目名称(关键修改) + String entryName = buildZipEntryName(relativePath, parentEntry); + + Path realPath = currentPath.toRealPath(); + + if (Files.isDirectory(currentPath)) { + if (processedPaths.contains(realPath)) return; + processedPaths.add(realPath); } + if (Files.isDirectory(currentPath)) { + handleZipDirectory(zipOut, entryName); + processZipChildren(currentPath, zipOut, entryName, processedPaths); + } else { + addFileToZip(currentPath, zipOut, entryName); + } + } + + // ================== TAR.GZ格式压缩实现 ================== + + private void compressToTarGz(Path source, Path target) throws IOException { + try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream( + new GzipCompressorOutputStream(Files.newOutputStream(target)))) { + Set processedPaths = new HashSet<>(); + processTarEntry(source, tarOut, "", processedPaths); + } + } + + /** + * 获取相对于压缩根的路径 + */ + private Path getRelativePath(Path absolutePath) { + // 示例:如果压缩根是 /data/1.txt,则返回空路径 + // 如果是压缩目录 /data/333,则返回相对路径 + // 需要根据实际压缩根路径调整实现 + return absolutePath.getFileName(); + } + + private void processTarEntry(Path currentPath, + TarArchiveOutputStream tarOut, + String parentEntry, + Set processedPaths) throws IOException { + Path realPath = currentPath.toRealPath(); + + if (Files.isDirectory(currentPath)) { + if (processedPaths.contains(realPath)) return; + processedPaths.add(realPath); + } + + String entryName = buildTarEntryName(currentPath, parentEntry); + TarArchiveEntry entry = new TarArchiveEntry(currentPath.toFile(), entryName); + tarOut.putArchiveEntry(entry); + tarOut.closeArchiveEntry(); + + if (Files.isDirectory(currentPath)) { + try (DirectoryStream children = Files.newDirectoryStream(currentPath)) { + for (Path child : children) { + processTarEntry(child, tarOut, entryName, processedPaths); + } + } + } + } + + // ================== 7Z格式压缩实现 ================== + + private void compressTo7z(Path source, Path target) throws IOException { + try (SevenZOutputFile sevenZOut = new SevenZOutputFile(target.toFile())) { + Set processedPaths = new HashSet<>(); + process7zEntry(source, sevenZOut, "", processedPaths); + } + } + + private void process7zEntry(Path currentPath, + SevenZOutputFile sevenZOut, + String parentEntry, + Set processedPaths) throws IOException { + Path realPath = currentPath.toRealPath(); + + if (Files.isDirectory(currentPath)) { + if (processedPaths.contains(realPath)) return; + processedPaths.add(realPath); + } + + String entryName = build7zEntryName(currentPath, parentEntry); + SevenZArchiveEntry entry = sevenZOut.createArchiveEntry(currentPath.toFile(), entryName); + sevenZOut.putArchiveEntry(entry); + sevenZOut.closeArchiveEntry(); + + if (Files.isDirectory(currentPath)) { + try (DirectoryStream children = Files.newDirectoryStream(currentPath)) { + for (Path child : children) { + process7zEntry(child, sevenZOut, entryName, processedPaths); + } + } + } + } + + // ================== 通用工具方法 ================== + + private String buildZipEntryName(Path path, String parentEntry) { + // 仅保留文件名部分 + String fileName = path.getFileName().toString(); + + // 根条目处理 + if (parentEntry.isEmpty()) { + return Files.isDirectory(path) ? fileName + "/" : fileName; + } + + // 子条目处理 + return parentEntry + (parentEntry.endsWith("/") ? "" : "/") + fileName + + (Files.isDirectory(path) ? "/" : ""); + } + + private String buildTarEntryName(Path path, String parentEntry) { + return parentEntry.isEmpty() ? + path.getFileName().toString() : + parentEntry + "/" + path.getFileName(); + } + + private String build7zEntryName(Path path, String parentEntry) { + return buildTarEntryName(path, parentEntry); + } + + private void handleZipDirectory(ZipArchiveOutputStream zipOut, String entryName) + throws IOException { ZipArchiveEntry entry = new ZipArchiveEntry(entryName); - entry.setSize(file.length()); - entry.setTime(file.lastModified()); + entry.setMethod(ZipEntry.STORED); + entry.setSize(0); + entry.setCrc(0); + zipOut.putArchiveEntry(entry); + zipOut.closeArchiveEntry(); + } + + private void processZipChildren(Path dir, + ZipArchiveOutputStream zipOut, + String parentEntry, + Set processedPaths) throws IOException { + try (DirectoryStream children = Files.newDirectoryStream(dir)) { + for (Path child : children) { + processZipEntry(child, zipOut, parentEntry, processedPaths); + } + } + } + + private void addFileToZip(Path file, ZipArchiveOutputStream zipOut, String entryName) + throws IOException { + ZipArchiveEntry entry = new ZipArchiveEntry(entryName); + entry.setMethod(ZipEntry.DEFLATED); + entry.setSize(Files.size(file)); + entry.setTime(Files.getLastModifiedTime(file).toMillis()); zipOut.putArchiveEntry(entry); - try (FileInputStream fis = new FileInputStream(file); - BufferedInputStream bis = new BufferedInputStream(fis)) { - byte[] buffer = new byte[8192]; - int len; - while ((len = bis.read(buffer)) > 0) { - zipOut.write(buffer, 0, len); - } + try (InputStream input = Files.newInputStream(file)) { + IOUtils.copy(input, zipOut); } zipOut.closeArchiveEntry(); } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + /*************************************解压缩*******************************************/ /********************************** @@ -1123,12 +1431,12 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 1. 获取压缩包记录 TsFiles zipFileRecord = tsFilesMapper.selectById(id); - String zipName = getFileNameWithoutExtension(zipFileRecord.getFileName()); // 示例:222 + String zipName = zipFileRecord.getFileName(); // 示例:222 try { // 2. 判断压缩包类型(单文件还是文件夹) Path zipFilePath = Paths.get(storageSourceConfig.getValue(), zipFileRecord.getWorkPath(), zipFileRecord.getFileName()); // 3. 判断压缩包内容类型 - boolean hasFolder = hasFolderInZip(zipFilePath); + boolean hasFolder = hasFolder(zipFilePath); // // 4. 构建解压目标路径 Path destRoot; if (hasFolder) { @@ -1203,19 +1511,241 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } + /** - * 判断压缩包中是否包含文件夹 + * 检查压缩文件是否包含文件夹(主入口方法) + * + * @param filePath 压缩文件路径 + * @return 是否包含文件夹 */ - private boolean hasFolderInZip(Path zipFilePath) throws IOException { + public boolean hasFolder(Path filePath) throws IOException { + String fileName = filePath.getFileName().toString().toLowerCase(); + QueryWrapper queryWrapperSysDictionary = new QueryWrapper<>(); + queryWrapperSysDictionary.eq("parentcode", "compressType"); + queryWrapperSysDictionary.orderByAsc("orderno"); + List sysDictionaryItems = sysDictionaryItemsMapper.selectList(queryWrapperSysDictionary); + // 遍历所有支持的压缩后缀进行匹配 + for (SysDictionaryItems sys : sysDictionaryItems) { + if (fileName.endsWith("." + sys.getDictName())) { + return checkBySuffix(filePath, sys.getDictName()); + } + } + throw new IOException("不支持的压缩格式: " + fileName); + } + + /** + * 初始化压缩后缀配置 + * + * @param sysDictionaryItems 数据库查询结果集 + */ + public void initCompressTypes(List sysDictionaryItems) { + // 按后缀长度降序排序(确保优先匹配长后缀如.tar.gz) + this.compressSuffixes = sysDictionaryItems.stream() + .map(item -> item.getDictName().toLowerCase()) + .sorted((s1, s2) -> Integer.compare(s2.length(), s1.length())) + .collect(Collectors.toList()); + } + + /** + * 根据后缀分派检查方法 + * + * @param path 文件路径 + * @param suffix 压缩后缀(不带点) + */ + private boolean checkBySuffix(Path path, String suffix) throws IOException { + switch (suffix) { + case "zip": + return checkZip(path); + case "tar": + return checkTar(path); + case "gz": + return checkGzip(path); + case "bz2": + return checkBzip2(path); + case "rar": + return checkRar(path); + case "tar.gz": + return checkTarGz(path); + case "tar.bz2": + return checkTarBz2(path); + default: + throw new IOException("未实现的格式检查: " + suffix); + } + } + + + /******************** ZIP格式检查 ********************/ + /** + * 检查ZIP文件是否包含目录 + * + * @param zipFilePath ZIP文件路径 + * @return 是否包含目录 + */ + private boolean checkZip(Path zipFilePath) throws IOException { try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) { Enumeration entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); if (entry.isDirectory()) { - return true; // 如果发现文件夹,返回 true + return true; } } - return false; // 如果没有文件夹,返回 false + return false; + } + } + + /******************** TAR格式检查 ********************/ + /** + * 检查TAR文件是否包含目录 + * + * @param tarFilePath TAR文件路径 + * @return 是否包含目录 + */ + private boolean checkTar(Path tarFilePath) throws IOException { + try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(tarFilePath))) { + TarArchiveEntry entry; + while ((entry = tis.getNextTarEntry()) != null) { + if (entry.isDirectory()) { + return true; + } + } + return false; + } + } + + /******************** GZIP格式检查 ********************/ + /** + * 检查GZ文件是否包含目录(可能包含嵌套TAR) + * + * @param gzFilePath GZ文件路径 + * @return 是否包含目录 + */ + private boolean checkGzip(Path gzFilePath) throws IOException { + // 检查是否为TAR.GZ格式 + try (InputStream is = Files.newInputStream(gzFilePath); + GzipCompressorInputStream gzis = new GzipCompressorInputStream(is)) { + + // 读取头部判断是否嵌套TAR + byte[] header = new byte[512]; + if (gzis.read(header) >= 512 && isTarHeader(header)) { + return checkTar(new ByteArrayInputStream(header, 0, 512), gzis); + } + return false; // 纯GZ文件不含目录 + } + } + + /******************** TAR.GZ复合格式检查 ********************/ + /** + * 检查TAR.GZ文件是否包含目录 + * + * @param tgzFilePath TAR.GZ文件路径 + * @return 是否包含目录 + */ + private boolean checkTarGz(Path tgzFilePath) throws IOException { + try (InputStream is = Files.newInputStream(tgzFilePath); + GzipCompressorInputStream gzis = new GzipCompressorInputStream(is); + TarArchiveInputStream tis = new TarArchiveInputStream(gzis)) { + + return checkTarEntries(tis); + } + } + + /******************** BZIP2格式检查 ********************/ + /** + * 检查BZ2文件是否包含目录(可能包含嵌套TAR) + * + * @param bz2FilePath BZ2文件路径 + * @return 是否包含目录 + */ + private boolean checkBzip2(Path bz2FilePath) throws IOException { + try (InputStream is = Files.newInputStream(bz2FilePath); + BZip2CompressorInputStream bz2is = new BZip2CompressorInputStream(is)) { + + // 读取头部判断是否嵌套TAR + byte[] header = new byte[512]; + if (bz2is.read(header) >= 512 && isTarHeader(header)) { + return checkTar(new ByteArrayInputStream(header, 0, 512), bz2is); + } + return false; + } + } + + /******************** TAR.BZ2复合格式检查 ********************/ + /** + * 检查TAR.BZ2文件是否包含目录 + * + * @param tbz2FilePath TAR.BZ2文件路径 + * @return 是否包含目录 + */ + private boolean checkTarBz2(Path tbz2FilePath) throws IOException { + try (InputStream is = Files.newInputStream(tbz2FilePath); + BZip2CompressorInputStream bz2is = new BZip2CompressorInputStream(is); + TarArchiveInputStream tis = new TarArchiveInputStream(bz2is)) { + + return checkTarEntries(tis); + } + } + + /******************** RAR格式检查 ********************/ + /** + * 检查RAR文件是否包含目录 + * + * @param rarFilePath RAR文件路径 + * @return 是否包含目录 + */ + private boolean checkRar(Path rarFilePath) throws IOException { + try (Archive archive = new Archive(rarFilePath.toFile())) { + FileHeader fileHeader; + while ((fileHeader = archive.nextFileHeader()) != null) { + if (fileHeader.isDirectory()) { + return true; + } + } + return false; + } catch (Exception e) { + throw new IOException("解析RAR文件失败", e); + } + } + + /******************** 通用工具方法 ********************/ + /** + * 检查TAR流是否包含目录 + * + * @param tis TAR输入流 + * @return 是否包含目录 + */ + private boolean checkTarEntries(TarArchiveInputStream tis) throws IOException { + TarArchiveEntry entry; + while ((entry = tis.getNextTarEntry()) != null) { + if (entry.isDirectory()) { + return true; + } + } + return false; + } + + /** + * 判断字节流是否为TAR头部 + * + * @param header 512字节头部数据 + * @return 是否TAR格式 + */ + private boolean isTarHeader(byte[] header) { + if (header.length < 263) return false; + String magic = new String(header, 257, 6, StandardCharsets.US_ASCII); + return magic.startsWith("ustar"); + } + + /** + * 合并头部和剩余流检查TAR + * + * @param headerStream 头部字节流 + * @param remainingStream 剩余数据流 + */ + private boolean checkTar(InputStream headerStream, InputStream remainingStream) throws IOException { + try (TarArchiveInputStream tis = new TarArchiveInputStream( + new SequenceInputStream(headerStream, remainingStream))) { + return checkTarEntries(tis); } } @@ -1360,6 +1890,20 @@ public class TsFilesServiceImpl extends ServiceImpl impl return path.endsWith("/") ? path : path + "/"; } + + // 从字典表获取支持的压缩文件后缀列表 + private List getSupportedCompressionSuffixes() { + QueryWrapper queryWrapperSysDictionary = new QueryWrapper<>(); + queryWrapperSysDictionary.eq("parentcode", "compressType"); + queryWrapperSysDictionary.orderByAsc("orderno"); + List sysDictionaryItems = sysDictionaryItemsMapper.selectList(queryWrapperSysDictionary); + + // 提取后缀列表(例如 ["gz", "xz", "rar", "tar", "zip", "bz2", "tar.gz"]) + return sysDictionaryItems.stream() + .map(SysDictionaryItems::getDictName) + .collect(Collectors.toList()); + } + /** * 解压 ZIP 文件到临时目录 * @@ -1373,30 +1917,64 @@ public class TsFilesServiceImpl extends ServiceImpl impl Path destRoot = Paths.get(baseDir); Files.createDirectories(destRoot); - // 获取压缩包名称(无扩展名) + // 获取支持的压缩后缀列表 + List supportedSuffixes = getSupportedCompressionSuffixes(); + + // 根据文件扩展名匹配支持的压缩格式(优先匹配长后缀,如 .tar.gz) + String fileName = zipFilePath.getFileName().toString().toLowerCase(); + String matchedSuffix = supportedSuffixes.stream() + .filter(suffix -> fileName.endsWith("." + suffix)) + .max(Comparator.comparingInt(String::length)) // 优先匹配最长后缀(如 .tar.gz) + .orElseThrow(() -> new IOException("Unsupported compression format: " + fileName)); + + // 根据后缀选择解压方式 + switch (matchedSuffix) { + case "zip": + return unzipToTemp(zipFilePath, destRoot); + case "gz": + return decompressGzip(zipFilePath, destRoot); +// case "bz2": +// return decompressBzip2(zipFilePath, destRoot); +// case "xz": +// return decompressXz(zipFilePath, destRoot); + case "tar": + return decompressTar(zipFilePath, destRoot); + case "tar.gz": + return decompressTarGz(zipFilePath, destRoot); +// case "tar.bz2": +// return decompressTarBz2(zipFilePath, destRoot); + case "rar": + return decompressRar(zipFilePath, destRoot); + default: + throw new IOException("Unsupported compression format: " + matchedSuffix); + } + } + + /** + * 解压 ZIP 文件 + */ + private File unzipToTemp(Path zipFilePath, Path destRoot) throws IOException { String zipName = getFileNameWithoutExtension(zipFilePath.getFileName().toString()); - try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath.toFile()))) { + try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(zipFilePath))) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { + // 过滤 MacOS 元数据目录 if (entry.getName().startsWith("__MACOSX/")) { zis.closeEntry(); continue; } - - // 关键修改:剥离压缩包根目录 + // 剥离 ZIP 根目录(例如 ZIP 内有一个根目录 "example/") String entryName = entry.getName(); if (entryName.startsWith(zipName + "/")) { - entryName = entryName.substring(zipName.length() + 1); // 跳过 "2/" + entryName = entryName.substring(zipName.length() + 1); } - - // 跳过空路径(根目录本身) + // 跳过空路径 if (entryName.isEmpty()) { zis.closeEntry(); continue; } - - // 构建目标路径 + // 构建安全路径并写入文件 Path destPath = destRoot.resolve(entryName).normalize(); validatePathSafety(destPath, destRoot); @@ -1412,6 +1990,167 @@ public class TsFilesServiceImpl extends ServiceImpl impl return destRoot.toFile(); } + /** + * 解压 GZIP 文件(假设是单文件压缩) + */ + private File decompressGzip(Path gzipFilePath, Path destRoot) throws IOException { + String fileName = getFileNameWithoutExtension(gzipFilePath.getFileName().toString()); + Path outputFile = destRoot.resolve(fileName); + + try (InputStream is = Files.newInputStream(gzipFilePath); + GZIPInputStream gzis = new GZIPInputStream(is); + OutputStream os = Files.newOutputStream(outputFile)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = gzis.read(buffer)) > 0) { + os.write(buffer, 0, len); + } + } + return outputFile.toFile(); + } + + /** + * 解压 TAR 文件 + */ + private File decompressTar(Path tarFilePath, Path destRoot) throws IOException { + try (TarArchiveInputStream tais = new TarArchiveInputStream(Files.newInputStream(tarFilePath))) { + TarArchiveEntry entry; + while ((entry = tais.getNextTarEntry()) != null) { + // 过滤无效文件 + if (entry.getName().startsWith("__MACOSX/")) continue; + + Path destPath = destRoot.resolve(entry.getName()).normalize(); + validatePathSafety(destPath, destRoot); + + if (entry.isDirectory()) { + Files.createDirectories(destPath); + } else { + Files.createDirectories(destPath.getParent()); + writeFile(tais, destPath); + } + } + } + return destRoot.toFile(); + } + + /** + * 解压 TAR.GZ 文件 + */ + private File decompressTarGz(Path tarGzFilePath, Path destRoot) throws IOException { + try (InputStream is = Files.newInputStream(tarGzFilePath); + GZIPInputStream gzis = new GZIPInputStream(is); + TarArchiveInputStream tais = new TarArchiveInputStream(gzis)) { + + TarArchiveEntry entry; + while ((entry = tais.getNextTarEntry()) != null) { + // 过滤无效文件 + if (entry.getName().startsWith("__MACOSX/")) continue; + + Path destPath = destRoot.resolve(entry.getName()).normalize(); + validatePathSafety(destPath, destRoot); + + if (entry.isDirectory()) { + Files.createDirectories(destPath); + } else { + Files.createDirectories(destPath.getParent()); + writeFile(tais, destPath); + } + } + } + return destRoot.toFile(); + } + + /** + * 解压 RAR 文件(需要 junrar 依赖) + */ + private File decompressRar(Path rarFilePath, Path destRoot) throws IOException { + try (Archive archive = new Archive(rarFilePath.toFile())) { + FileHeader fileHeader; + while ((fileHeader = archive.nextFileHeader()) != null) { + // 过滤无效文件 + if (fileHeader.getFileName().startsWith("__MACOSX/")) continue; + + Path destPath = destRoot.resolve(fileHeader.getFileName()).normalize(); + validatePathSafety(destPath, destRoot); + + if (fileHeader.isDirectory()) { + Files.createDirectories(destPath); + } else { + Files.createDirectories(destPath.getParent()); + try (OutputStream os = Files.newOutputStream(destPath)) { + archive.extractFile(fileHeader, os); + } + } + } + } catch (RarException e) { + throw new IOException("Failed to decompress RAR file", e); + } + return destRoot.toFile(); + } + + /** + * 将输入流写入目标文件 + */ + private void writeFile(InputStream is, Path destPath) throws IOException { + try (OutputStream os = Files.newOutputStream(destPath)) { + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) > 0) { + os.write(buffer, 0, len); + } + } + } + + +// /** +// * 解析压缩包条目(支持多种格式) +// */ +// private List parseArchiveEntries(Path compressedFilePath) throws IOException { +// List entries = new ArrayList<>(); +// String suffix = getMatchedSuffix(compressedFilePath); +// +// switch (suffix) { +// case "zip": +// try (ZipInputStream zis = new ZipInputStream(Files.newInputStream(compressedFilePath))) { +// ZipEntry entry; +// while ((entry = zis.getNextEntry()) != null) { +// if (!entry.getName().startsWith("__MACOSX/")) { +// entries.add(new ZipEntryInfo(entry.getName(), entry.isDirectory())); +// } +// zis.closeEntry(); +// } +// } +// break; +// case "tar": +// case "tar.gz": +// case "tar.bz2": +// try (ArchiveInputStream ais = createArchiveInputStream(compressedFilePath, suffix)) { +// ArchiveEntry entry; +// while ((entry = ais.getNextEntry()) != null) { +// if (!entry.getName().startsWith("__MACOSX/")) { +// entries.add(new ZipEntryInfo(entry.getName(), entry.isDirectory())); +// } +// } +// } +// break; +// case "rar": +// try (Archive archive = new Archive(compressedFilePath.toFile())) { +// FileHeader fileHeader; +// while ((fileHeader = archive.nextFileHeader()) != null) { +// if (!fileHeader.getFileName().startsWith("__MACOSX/")) { +// entries.add(new ZipEntryInfo(fileHeader.getFileName(), fileHeader.isDirectory())); +// } +// } +// } catch (RarException e) { +// throw new IOException("Failed to parse RAR entries", e); +// } +// break; +// default: +// throw new IOException("Unsupported format for entry parsing: " + suffix); +// } +// return entries; +// } + /** * 解析压缩包条目,过滤无效文件(如 __MACOSX) */ @@ -1587,7 +2326,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 对比文件(并行计算 MD5) tsFiles = compareFiles(fileItemListMinio, fileItemListLocal, - filePathConfig.getValue(), bucketConfig.getValue()); + filePathConfig.getValue(), bucketConfig.getValue(), nodeId, taskId); LOGGER.info("MinIO文件列表: {}", fileItemListMinio); LOGGER.info("本地文件列表: {}", fileItemListLocal); @@ -1609,8 +2348,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl private QueryWrapper buildTaskQuery(String nodeId, String taskId) { return new QueryWrapper() .eq("task_id", taskId) - .eq("node_id", nodeId) - .and(wrapper -> wrapper.eq("work_path", "/").or().eq("backup_path", "/")); + .eq("node_id", nodeId); + //.and(wrapper -> wrapper.eq("work_path", "/").or().eq("backup_path", "/")); } // 辅助方法:处理文件列表(并行安全) @@ -1687,20 +2426,20 @@ public class TsFilesServiceImpl extends ServiceImpl impl public TsFiles compareFiles(List minioFiles, List localFiles, String filePath, - String bucketName) { + String bucketName, String nodeId, String taskId) { TsFiles result = new TsFiles(); // 使用并行流构建 Map(路径 + 文件名 -> 文件) ConcurrentMap minioMap = minioFiles.parallelStream() .collect(Collectors.toConcurrentMap( file -> generateMapKey(file), - file -> enrichFileMetadata(file, "backup_path") + file -> enrichFileMetadata(file, "backup_path", nodeId, taskId) )); ConcurrentMap localMap = localFiles.parallelStream() .collect(Collectors.toConcurrentMap( file -> generateMapKey(file), - file -> enrichFileMetadata(file, "work_path") + file -> enrichFileMetadata(file, "work_path", nodeId, taskId) )); // 检查独有文件(并行过滤) @@ -1757,17 +2496,22 @@ public class TsFilesServiceImpl extends ServiceImpl impl } // 辅助方法:丰富文件元数据 - private FileItemResult enrichFileMetadata(FileItemResult file, String pathType) { - String aaaa = normalizePath(file.getPath()); - String bbbb = pathType; - System.out.print("aaaa"+ file.getName()); + private FileItemResult enrichFileMetadata(FileItemResult file, String pathType, String nodeId, String taskId) { + // 记录查询条件的值 + String fileName = file.getName(); + String filePath = normalizePath(file.getPath()); + LOGGER.info("查询条件 - fileName: {}, pathType: {}, filePath: {}", fileName, filePath, pathType); TsFiles dbRecord = tsFilesMapper.selectOne( new QueryWrapper() .eq("file_name", file.getName()) + .eq("node_id", nodeId) + .eq("task_id", taskId) .eq(pathType, normalizePath(file.getPath())) ); if (dbRecord != null) { file.setId(dbRecord.getId()); + } else { + LOGGER.error("未找到匹配的记录,fileName: {}, pathType: {}, filePath: {}", fileName, filePath, pathType); } return file; } @@ -3261,6 +4005,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl String path = workPath + fileNameData; //准备获取文件的信息 if ("FILE".equals(node.getIsFile())) { + LOGGER.info("查询本地树的时候" + path); AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); FileItemResult fileItemResult = fileService.getFileItem(path); if (fileItemResult != null || fileItemResult.getName() != null) { @@ -3286,6 +4031,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl String path = backupPath + fileNameData; //准备获取文件的信息 if ("FILE".equals(node.getIsFile())) { + LOGGER.info("查询minio树的时候" + path); AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("minio"); FileItemResult fileItemResult = fileService.getFileItem(path); if (fileItemResult != null || fileItemResult.getName() != null) { @@ -3372,7 +4118,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 发送完成信号(如果需要) if (!Thread.currentThread().isInterrupted()) { - // ServerSendEventServer.sendMessage(token, "COMPLETED"); + // ServerSendEventServer.sendMessage(token, "COMPLETED"); } } catch (Exception e) { diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java index bf24812..ff396b3 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/impl/TsNodesServiceImpl.java @@ -32,6 +32,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; +import java.io.File; import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.*; @@ -280,9 +281,25 @@ public class TsNodesServiceImpl extends ServiceImpl impl } //序号 tsnodes.setNodeOrder(orderno); + + + int valueAdded = tsNodesMapper.insert(tsnodes); if (valueAdded == 1) { LOGGER.info("tsnodes表结构增加成功"); + //判断文件夹是否创建 + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); + boolean flag = fileService.isFolderCreated(File.separator + tsnodes.getNodeId()); + //如果是false 说明没有创建 那就新建一个文件夹 + if(!flag){ + //本地创建文件夹 + AbstractBaseFileService fileServiceData = storageSourceContext.getByStorageKey("local"); + boolean flagData = fileServiceData.newFolder(File.separator, tsnodes.getNodeId()); + if(!flagData){ + LOGGER.error("创建节点文件夹失败!"); + return ResponseResult.error("新增节点失败!"); + } + } return ResponseResult.success(); } else { LOGGER.error("tsnodes表结构增加失败"); diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java index 68f97dd..aff232f 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/controller/FilesController.java @@ -74,12 +74,8 @@ public class FilesController { if (ObjUtil.isEmpty(files)) { return ResponseResult.error("参数为空"); } - Boolean isOk = filesService.addFiles(files); - if (isOk) { - return ResponseResult.success(); - } else { - return ResponseResult.error(); - } + return filesService.addFiles(files); + } /********************************** diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java index 6c9b85f..94476e8 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/IFilesService.java @@ -1,6 +1,7 @@ package com.yfd.platform.modules.specialDocument.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.yfd.platform.config.ResponseResult; import com.yfd.platform.modules.specialDocument.domain.Files; import com.baomidou.mybatisplus.extension.service.IService; import org.springframework.web.multipart.MultipartFile; @@ -38,7 +39,7 @@ public interface IFilesService extends IService { * Files 文档内容 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 ***********************************/ - Boolean addFiles(Files files); + ResponseResult addFiles(Files files); /********************************** * 用途说明: 修改专项文档管理-文档内容 diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java index aee4839..cf4fc4c 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/FilesServiceImpl.java @@ -188,16 +188,9 @@ public class FilesServiceImpl extends ServiceImpl implements * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 ***********************************/ @Override - public Boolean addFiles(Files files) { + public ResponseResult addFiles(Files files) { Boolean value = false; - // 校验文件名是否包含非法字符 - String fileName = files.getFileName(); - if (containsInvalidCharacters(fileName)) { - return value; - } - - // String[] splitIds = ids.split(","); BigDecimal List names = Arrays.asList(files.getFileName().split(",")); List sizes = Arrays.asList(files.getFileSize().split(",")); @@ -210,8 +203,7 @@ public class FilesServiceImpl extends ServiceImpl implements // 数据校验 if (names.size() != sizes.size()) { - LOGGER.error("文件名称和文件大小的列表长度不一致"); - return false; + return ResponseResult.error("文件名称和文件大小的列表长度不一致!"); } List filesToSave = new ArrayList<>(); // 设置当前时间 @@ -220,9 +212,25 @@ public class FilesServiceImpl extends ServiceImpl implements Timestamp currentTime = Timestamp.valueOf(now); files.setUploadTime(currentTime); + for (int i = 0; i < names.size(); i++) { String name = names.get(i).trim(); String sizeStr = sizes.get(i).trim(); + + // 校验文件名是否包含非法字符 + if (containsInvalidCharacters(name)) { + return ResponseResult.error("文件名包含非法字符!"); + } + + //校验是否真正上传 + String pathAndName = files.getFilePath()+"/" + name; + //准备获取文件的信息 + AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("minio"); + FileItemResult fileItemResult = fileService.getFileItem(pathAndName); + if (fileItemResult == null || fileItemResult.getName() == null) { + return ResponseResult.error(name + "文件没有上传到空间,请重新选择上传!"); + } + // 校验文件大小是否可以转换成数值 try { Files files1 = new Files(); @@ -237,7 +245,7 @@ public class FilesServiceImpl extends ServiceImpl implements files1.setFileSize(sizeStr); filesToSave.add(files1); } catch (NumberFormatException e) { - LOGGER.error("文件大小必须是有效的数字"); + return ResponseResult.error("文件大小必须是有效的数字!"); } } if(filesToSave.size()>0){ @@ -251,7 +259,11 @@ public class FilesServiceImpl extends ServiceImpl implements } } } - return value; + if (value) { + return ResponseResult.success("新增文件成功!"); + } else { + return ResponseResult.error("新增文件失败!"); + } } /********************************** diff --git a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java index 2a4d57a..9e09e1f 100644 --- a/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/specialDocument/service/impl/ProjectServiceImpl.java @@ -89,14 +89,13 @@ public class ProjectServiceImpl extends ServiceImpl impl @Override public Boolean addSdproject(Project project) { //查询字典表获取项目类型对应数据字典 -// QueryWrapper queryWrapperSysDictionary = new QueryWrapper<>(); -// queryWrapperSysDictionary.eq("parentcode", "zxxmlx"); -// queryWrapperSysDictionary.eq("itemcode", project.getProjectType()); -// queryWrapperSysDictionary.orderByAsc("orderno"); -// SysDictionaryItems sysDictionaryItems = sysDictionaryItemsMapper.selectOne(queryWrapperSysDictionary); -// if(sysDictionaryItems != null){ -// project.setProjectType(sysDictionaryItems.getDictName()); -// } + QueryWrapper queryWrapperSysDictionary = new QueryWrapper<>(); + queryWrapperSysDictionary.eq("parentcode", "compressType"); + queryWrapperSysDictionary.orderByAsc("orderno"); + SysDictionaryItems sysDictionaryItems = sysDictionaryItemsMapper.selectOne(queryWrapperSysDictionary); + if(sysDictionaryItems != null){ + project.setProjectType(sysDictionaryItems.getDictName()); + } //TODO 01.21沟通以后说是先不用管重复校验问题 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 3ca674c..9835e7d 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 @@ -3,9 +3,13 @@ package com.yfd.platform.modules.storage.service.base; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.StrUtil; +import com.amazonaws.AmazonServiceException; import com.amazonaws.HttpMethod; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.*; +import com.amazonaws.services.s3.transfer.Copy; +import com.amazonaws.services.s3.transfer.TransferManager; +import com.amazonaws.services.s3.transfer.TransferManagerBuilder; import com.yfd.platform.constant.ZFileConstant; import com.yfd.platform.exception.StorageSourceAutoConfigCorsException; import com.yfd.platform.modules.config.service.SystemConfigService; @@ -592,15 +596,56 @@ public abstract class AbstractS3BaseFileService

extends A @Override public boolean renameFile(String path, String name, String newName) { - String srcPath = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, name); - String distPath = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, newName); + String srcKey = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, name); + String destKey = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, newName); - String bucketName = param.getBucketName(); - s3Client.copyObject(bucketName, srcPath, bucketName, distPath); - deleteFile(path, name); - return true; + TransferManager transferManager = TransferManagerBuilder.standard() + .withS3Client(s3Client) + .build(); + + try { + // 异步复制(适合大文件) + Copy copy = transferManager.copy( + param.getBucketName(), srcKey, + param.getBucketName(), destKey + ); + copy.waitForCompletion(); // 等待复制完成 + s3Client.deleteObject(param.getBucketName(), srcKey); + return true; + } catch (InterruptedException | AmazonServiceException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Rename failed", e); + } finally { + transferManager.shutdownNow(); + } } + public boolean renameFileWithTransferManager(String path, String name, String newName) { + String srcKey = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, name); + String destKey = StringUtils.concatTrimStartSlashes(param.getBasePath(), path, newName); + + TransferManager transferManager = TransferManagerBuilder.standard() + .withS3Client(s3Client) + .build(); + + try { + // 异步复制(适合大文件) + Copy copy = transferManager.copy( + param.getBucketName(), srcKey, + param.getBucketName(), destKey + ); + copy.waitForCompletion(); // 等待复制完成 + s3Client.deleteObject(param.getBucketName(), srcKey); + return true; + } catch (InterruptedException | AmazonServiceException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Rename failed", e); + } finally { + transferManager.shutdownNow(); + } + } + + //todo 改造之前的重命名 // @Override // public boolean renameFolder(String path, String name, String newName) { 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 eb5a887..8c8dbf0 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 @@ -288,4 +288,14 @@ public interface BaseFileService { */ List fileListData(String path, String name) throws Exception; + + /** + * 创建新文件夹 + * + * @param folderPath + * 文件夹路径 + * + * @return 是否创建成功 + */ + boolean isFolderCreated(String folderPath); } 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 78d6bf0..ac4affe 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 @@ -29,6 +29,8 @@ import org.springframework.stereotype.Service; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -112,6 +114,20 @@ public class LocalServiceImpl extends AbstractProxyTransferService { return FileUtil.mkdir(fullPath) != null; } + /** + * 判断给定路径的文件夹是否已经创建 + * + * @param folderPath 文件夹路径(例如 "/folderName") + * @return true - 文件夹已存在;false - 文件夹不存在 + */ + public boolean isFolderCreated(String folderPath) { + // 将路径转换为绝对路径(如果需要) + Path path = Paths.get(folderPath).toAbsolutePath(); + + // 检查路径是否存在且是一个目录 + return Files.exists(path) && Files.isDirectory(path); + } + @Override public boolean deleteFile(String path, String name) { diff --git a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java index 324c8e7..4d96f46 100644 --- a/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java +++ b/java/src/main/java/com/yfd/platform/modules/storage/service/impl/MinIOServiceImpl.java @@ -35,4 +35,9 @@ public class MinIOServiceImpl extends AbstractS3BaseFileService { return StorageTypeEnum.MINIO; } + @Override + public boolean isFolderCreated(String folderPath) { + return false; + } + }