From 9eaa8c300da7a4d7b43e41e2ddd2fd43b8f03eb7 Mon Sep 17 00:00:00 2001 From: lilin Date: Wed, 26 Mar 2025 09:54:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=E4=BB=A3=E7=A0=81tar=20tar.g?= =?UTF-8?q?z=207z=E5=8E=8B=E7=BC=A9=E8=A7=A3=E5=8E=8B=E7=BC=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/TsFilesServiceImpl.java | 563 +++++++++++++++--- 1 file changed, 475 insertions(+), 88 deletions(-) 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 bf11860..9f1e8e7 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,8 @@ import cn.hutool.core.collection.CollUtil; import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.SimpleDateFormat; @@ -46,6 +48,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import net.sf.jsqlparser.expression.LongValue; import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry; import org.apache.commons.compress.archivers.sevenz.SevenZFile; +import org.apache.commons.compress.archivers.sevenz.SevenZOutputFile; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; @@ -244,6 +247,22 @@ public class TsFilesServiceImpl extends ServiceImpl impl // 获取文件扩展名的方法 private String getFileExtension(String filename) { + + if (filename.endsWith(".tar.gz")) { + return ".tar.gz"; + } + // 检查是否是 .tar 格式 + if (filename.endsWith(".tar")) { + return ".tar"; // 去掉 ".tar" + } + // 检查是否是 .zip 格式 + if (filename.endsWith(".zip")) { + return ".zip"; // 去掉 ".zip" + } + // 检查是否是 .7z 格式 + if (filename.endsWith(".7z")) { + return ".7z"; // 去掉 ".7z" + } if (filename != null && filename.contains(".")) { return filename.substring(filename.lastIndexOf(".")); } @@ -1215,13 +1234,23 @@ public class TsFilesServiceImpl extends ServiceImpl impl if (pathSegments.isEmpty() || !pathSegments.get(0).equals(nodeId)) { throw new RuntimeException("路径必须包含当前节点ID"); } + String nodePath = "/" + nodeId + "/"; + if (compressedPath.equals(nodePath)) { + return "00"; + } + // 提取实际要处理的目录层级(去掉开头的nodeId) - List dirSegments = pathSegments.subList(1, pathSegments.size()); + List dirSegments = null; + if (pathSegments.size() > 1) { + // 如果 pathSegments 大于 1,去掉 nodeId + dirSegments = pathSegments.subList(1, pathSegments.size()); + } if (dirSegments.isEmpty()) { throw new RuntimeException("路径缺少有效目录层级"); } + // 2. 初始化根目录信息(基于nodeId) String parentId = "00"; // 根目录的父ID为0 String baseFsPath = "/" + nodeId + "/"; // 本地存储基础路径 @@ -1296,10 +1325,12 @@ public class TsFilesServiceImpl extends ServiceImpl impl switch (compressedFormat.toUpperCase()) { case "ZIP": return compressToZip(sourcePaths, outputPath); + case "TAR": + return compressToTar(sourcePaths, outputPath); case "TAR.GZ": - return compressToTarGz(sourcePaths); -// case "7Z": -// return compressTo7z(sourcePaths); + return compressToTarGz(sourcePaths, outputPath); + case "7Z": + return compressTo7z(sourcePaths, outputPath); default: throw new IllegalArgumentException("不支持的压缩格式: " + compressedFormat); } @@ -1436,10 +1467,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl addedEntries.add(entryName); } - private Path generateZipPath(Path sourcePath) { - String baseName = sourcePath.getFileName().toString().replaceFirst("[.][^.]+$", ""); - return sourcePath.resolveSibling(baseName + ".zip"); - } private boolean validateZip(Path zipPath) { try (ZipFile zipFile = new ZipFile(zipPath.toFile())) { @@ -1485,77 +1512,186 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - // ================== TAR.GZ格式压缩实现 ================== - private boolean compressToTarGz(List sourcePaths) throws IOException { - boolean success = true; - for (Path sourcePath : sourcePaths) { - Path tarGzPath = getCompressedFilePath(sourcePath, "tar.gz"); - try (OutputStream fos = Files.newOutputStream(tarGzPath); - GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos); - TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzos)) { +// ================== TAR格式压缩实现 ================== - tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); + /** + * 压缩为纯TAR格式(不进行GZ压缩) + * + * @param sourcePaths 要压缩的源路径列表(文件或目录) + * @param outputPath 输出文件路径(必须以.tar结尾) + * @return 是否压缩成功 + * @throws IOException 文件操作异常 + */ + private boolean compressToTar(List sourcePaths, Path outputPath) throws IOException { + try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(Files.newOutputStream(outputPath))) { + tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); - processEntries(sourcePath, (entryName, isDir) -> { - Path filePath = sourcePath.resolve(entryName); - TarArchiveEntry entry = new TarArchiveEntry(filePath.toFile(), entryName); - entry.setSize(isDir ? 0 : Files.size(filePath)); - tarOut.putArchiveEntry(entry); + for (Path sourcePath : sourcePaths) { + // 关键修改:始终使用源路径的父目录作为basePath + Path basePath = sourcePath.getParent(); - if (!isDir) { - try (InputStream input = Files.newInputStream(filePath)) { - IOUtils.copy(input, tarOut); - } + Files.walkFileTree(sourcePath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String entryName = basePath.relativize(file).toString().replace(File.separator, "/"); + addFileToTar(file, entryName, tarOut); + return FileVisitResult.CONTINUE; } - tarOut.closeArchiveEntry(); + @Override + public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + if (!dir.equals(sourcePath)) { // 不跳过源目录本身 + String dirName = basePath.relativize(dir).toString().replace(File.separator, "/") + "/"; + addDirToTar(dir, dirName, tarOut); + } + return FileVisitResult.CONTINUE; + } }); - - tarOut.finish(); - } catch (Exception e) { - success = false; - e.printStackTrace(); } + tarOut.finish(); + return true; } - return success; } - // ================== 7Z格式压缩实现 ================== -// private boolean compressTo7z(List sourcePaths) throws IOException { -// boolean success = true; -// for (Path sourcePath : sourcePaths) { -// Path sevenZPath = getCompressedFilePath(sourcePath, "7z"); -// try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(sevenZPath.toFile())) { -// -// processEntries(sourcePath, (entryName, isDir) -> { -// SevenZArchiveEntry entry = sevenZOutput.createEntry(); -// entry.setName(entryName); -// entry.setDirectory(isDir); -// -// if (!isDir) { -// Path filePath = sourcePath.resolve(entryName); -// entry.setSize(Files.size(filePath)); -// sevenZOutput.putArchiveEntry(entry); -// -// try (InputStream input = Files.newInputStream(filePath)) { -// byte[] buffer = new byte[8192]; -// int len; -// while ((len = input.read(buffer)) != -1) { -// sevenZOutput.write(buffer, 0, len); -// } -// } -// -// sevenZOutput.closeArchiveEntry(); -// } -// }); -// -// } catch (Exception e) { -// success = false; -// e.printStackTrace(); -// } -// } -// return success; -// } + // 辅助方法:添加文件条目 + private void addFileToTar(Path file, String entryName, TarArchiveOutputStream tarOut) throws IOException { + TarArchiveEntry entry = new TarArchiveEntry(file.toFile(), entryName); + entry.setSize(Files.size(file)); + tarOut.putArchiveEntry(entry); + try (InputStream is = Files.newInputStream(file)) { + IOUtils.copy(is, tarOut); + } + tarOut.closeArchiveEntry(); + } + + // 辅助方法:添加目录条目 + private void addDirToTar(Path dir, String dirName, TarArchiveOutputStream tarOut) throws IOException { + TarArchiveEntry entry = new TarArchiveEntry(dir.toFile(), dirName); + tarOut.putArchiveEntry(entry); + tarOut.closeArchiveEntry(); + } + + + // ================== TAR.GZ格式压缩实现 ================== + private boolean compressToTarGz(List sourcePaths, Path outputPath) throws IOException { + try (OutputStream fos = Files.newOutputStream(outputPath); + GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos); + TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzos)) { + + // 设置支持长文件名(超过100字符) + tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX); + + // 遍历所有源路径并添加到压缩文件 + for (Path path : sourcePaths) { + addToTarArchive(path, tarOut, ""); + } + + tarOut.finish(); + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 递归添加文件到TAR压缩流 + * + * @param source 当前要添加的路径 + * @param tarOut TAR输出流 + * @param parentDir 在压缩文件中的父目录路径(用于保持目录结构) + */ + private void addToTarArchive(Path source, TarArchiveOutputStream tarOut, String parentDir) throws IOException { + // 获取文件在压缩包中的入口名称(替换路径分隔符为/) + String entryName = parentDir + source.getFileName().toString().replace(File.separator, "/"); + + // 处理目录 + if (Files.isDirectory(source)) { + entryName += "/"; // 目录需要以/结尾 + TarArchiveEntry dirEntry = new TarArchiveEntry(source.toFile(), entryName); + tarOut.putArchiveEntry(dirEntry); + tarOut.closeArchiveEntry(); + + // 递归处理子目录 + try (DirectoryStream children = Files.newDirectoryStream(source)) { + for (Path child : children) { + addToTarArchive(child, tarOut, entryName); + } + } + } + // 处理文件 + else { + TarArchiveEntry fileEntry = new TarArchiveEntry(source.toFile(), entryName); + fileEntry.setSize(Files.size(source)); + tarOut.putArchiveEntry(fileEntry); + try (InputStream input = Files.newInputStream(source)) { + IOUtils.copy(input, tarOut); + } + tarOut.closeArchiveEntry(); + } + } + + /** + * 压缩为7z格式 + * + * @param sourcePaths 要压缩的源路径列表 + * @param outputPath 输出文件路径(必须包含文件名) + */ + private boolean compressTo7z(List sourcePaths, Path outputPath) throws IOException { + try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(outputPath.toFile())) { + + // 遍历所有源路径并添加到压缩文件 + for (Path path : sourcePaths) { + addTo7zArchive(path, sevenZOutput, ""); + } + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + /** + * 递归添加文件到7z压缩流 + * + * @param source 当前要添加的路径 + * @param sevenZOutput 7z输出流 + * @param parentDir 在压缩文件中的父目录路径(用于保持目录结构) + */ + private void addTo7zArchive(Path source, SevenZOutputFile sevenZOutput, String parentDir) throws IOException { + // 生成压缩包内条目名称 + String entryName = parentDir.isEmpty() + ? source.getFileName().toString().replace(File.separator, "/") + : parentDir + "/" + source.getFileName().toString().replace(File.separator, "/"); + + // 创建压缩条目并设置属性 + SevenZArchiveEntry entry = new SevenZArchiveEntry(); + entry.setName(entryName); + entry.setDirectory(Files.isDirectory(source)); + sevenZOutput.putArchiveEntry(entry); + + // 如果是文件则写入内容 + if (!entry.isDirectory()) { + try (InputStream input = Files.newInputStream(source)) { + byte[] buffer = new byte[8192]; + int len; + while ((len = input.read(buffer)) > 0) { + sevenZOutput.write(buffer, 0, len); + } + } + } + sevenZOutput.closeArchiveEntry(); + + // 递归处理子目录 + if (entry.isDirectory()) { + try (DirectoryStream children = Files.newDirectoryStream(source)) { + for (Path child : children) { + addTo7zArchive(child, sevenZOutput, entryName); + } + } + } + } // ================== 通用工具方法 ================== private interface EntryProcessor { @@ -1862,6 +1998,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl return unzipFile(zipFilePath, baseDir); } else if (fileName.endsWith(".tar.gz")) { return unTarGzFile(zipFilePath, baseDir); + } else if (ext.equals(".tar")) { + return unTarFile(zipFilePath, baseDir); } else if (ext.equals(".7z")) { return un7zFile(zipFilePath, baseDir); } else { @@ -1878,6 +2016,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl return hasFolderInZip(zipFilePath); } else if (fileName.endsWith(".tar.gz")) { return hasFolderInTarGz(zipFilePath); + } else if (ext.equals(".tar")) { + return hasFolderInTar(zipFilePath); } else if (ext.equals(".7z")) { return hasFolderIn7z(zipFilePath); } else { @@ -1910,6 +2050,38 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } + // 新增:TAR格式文件夹判断 + private boolean hasFolderInTar(Path filePath) throws IOException { + // 参数校验 + if (!filePath.toString().toLowerCase().endsWith(".tar")) { + throw new IllegalArgumentException("输入文件必须以.tar结尾"); + } + if (!Files.exists(filePath)) { + throw new FileNotFoundException("TAR文件不存在: " + filePath); + } + + try (InputStream fis = Files.newInputStream(filePath); + TarArchiveInputStream tis = new TarArchiveInputStream(fis)) { + + TarArchiveEntry entry; + while ((entry = tis.getNextTarEntry()) != null) { + // 忽略MAC系统元数据目录 + if (entry.getName().startsWith("__MACOSX/")) { + continue; + } + + // 发现目录立即返回true + if (entry.isDirectory()) { + // 额外验证:确保不是空名称或根目录标记 + if (!entry.getName().trim().isEmpty() && !entry.getName().equals("./")) { + return true; + } + } + } + return false; + } + } + // 新增:7Z格式文件夹判断 private boolean hasFolderIn7z(Path filePath) throws IOException { try (SevenZFile szFile = new SevenZFile(filePath.toFile())) { @@ -2003,23 +2175,93 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - // 新增:TAR.GZ解压实现(保持相同路径逻辑) + /** + * 解压TAR.GZ格式文件(含GZIP压缩的TAR包) + * + * @param sourcePath 压缩文件路径(必须是以.tar.gz结尾的文件) + * @param baseDir 解压目标目录路径 + * @return 解压后的根目录File对象 + * @throws IOException 文件操作异常或安全校验失败 + */ private File unTarGzFile(Path sourcePath, String baseDir) throws IOException { - Path destRoot = Paths.get(baseDir); + // 参数校验 + if (!sourcePath.toString().toLowerCase().endsWith(".tar.gz")) { + throw new IllegalArgumentException("文件必须是 .tar.gz 格式"); + } + + Path destRoot = Paths.get(baseDir).toAbsolutePath(); Files.createDirectories(destRoot); - try (InputStream fi = Files.newInputStream(sourcePath); - InputStream gi = new GzipCompressorInputStream(fi); - TarArchiveInputStream ti = new TarArchiveInputStream(gi)) { + try (TarArchiveInputStream tis = new TarArchiveInputStream( + new GzipCompressorInputStream(Files.newInputStream(sourcePath)))) { TarArchiveEntry entry; - while ((entry = ti.getNextTarEntry()) != null) { - // 保持相同过滤逻辑 - if (entry.getName().startsWith("__MACOSX/")) { + while ((entry = tis.getNextTarEntry()) != null) { + // 跳过无效条目 + if (entry.getName() == null || entry.getName().contains("__MACOSX")) { continue; } - Path targetPath = destRoot.resolve(entry.getName()).normalize(); + // 路径规范化 + String normalizedPath = normalizePathtargz(entry.getName(), sourcePath.getFileName().toString()); + if (normalizedPath.isEmpty()) continue; + + Path targetPath = destRoot.resolve(normalizedPath).normalize(); + validatePath(targetPath, destRoot); + + // 处理目录/文件 + if (entry.isDirectory()) { + Files.createDirectories(targetPath); + } else { + Files.createDirectories(targetPath.getParent()); + try (OutputStream os = Files.newOutputStream(targetPath)) { + IOUtils.copy(tis, os); + } + } + + // 保留Unix权限 + if (!System.getProperty("os.name").toLowerCase().contains("win")) { + Files.setPosixFilePermissions(targetPath, + PosixFilePermissions.fromString(getPosixMode(entry.getMode()))); + } + } + } + return destRoot.toFile(); + } + // 转换Unix权限位 + private String getPosixMode(int mode) { + return String.format("%s%s%s %s%s%s %s%s%s", + (mode & 0400) != 0 ? "r" : "-", (mode & 0200) != 0 ? "w" : "-", (mode & 0100) != 0 ? "x" : "-", + (mode & 0040) != 0 ? "r" : "-", (mode & 0020) != 0 ? "w" : "-", (mode & 0010) != 0 ? "x" : "-", + (mode & 0004) != 0 ? "r" : "-", (mode & 0002) != 0 ? "w" : "-", (mode & 0001) != 0 ? "x" : "-"); + } + /** + * 验证目标路径安全性(防止路径穿越攻击) + */ + private void validatePath(Path targetPath, Path destRoot) throws IOException { + if (!targetPath.normalize().startsWith(destRoot.normalize())) { + throw new IOException("危险路径: " + targetPath); + } + } + + /** + * 解压TAR文件(自动处理嵌套目录问题) + * + * @param sourcePath 源TAR文件路径 + * @param baseDir 目标目录(解压到此目录下) + * @return 解压后的根目录 + */ + private File unTarFile(Path sourcePath, String baseDir) throws IOException { + Path destRoot = Paths.get(baseDir).toAbsolutePath(); + Files.createDirectories(destRoot); + + try (TarArchiveInputStream tis = new TarArchiveInputStream(Files.newInputStream(sourcePath))) { + TarArchiveEntry entry; + while ((entry = tis.getNextTarEntry()) != null) { + // 处理路径:移除可能重复的顶层目录名 + String normalizedName = normalizeEntryPath(entry.getName(), sourcePath.getFileName().toString()); + + Path targetPath = destRoot.resolve(normalizedName).normalize(); validatePathSafety(targetPath, destRoot); if (entry.isDirectory()) { @@ -2027,7 +2269,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl } else { Files.createDirectories(targetPath.getParent()); try (OutputStream os = Files.newOutputStream(targetPath)) { - IOUtils.copy(ti, os); + IOUtils.copy(tis, os); } } } @@ -2035,22 +2277,85 @@ public class TsFilesServiceImpl extends ServiceImpl impl return destRoot.toFile(); } - // 新增:7Z解压实现(保持相同路径逻辑) + /** + * 智能规范化压缩包内路径 + * @param entryPath 压缩包内原始路径 + * @param archiveFilename 压缩包文件名(如"111.tar.gz") + * @return 处理后的路径 + */ + private String normalizeEntryPath(String entryPath, String archiveFilename) { + // 基础处理:移除开头的./和多余的/ + String path = entryPath.replaceAll("^\\./", "").replaceAll("/+", "/"); + + // 获取预期的根目录名(去掉压缩扩展名) + String baseName = archiveFilename.replaceFirst("\\.(tar|gz|7z|tar\\.gz)$", ""); + + // 情况1:路径直接以baseName开头(如"111/22/33.txt") + if (path.startsWith(baseName + "/")) { + return path.substring(baseName.length() + 1); + } + + // 情况2:路径已经是扁平结构(如"22/33.txt") + return path; + } + + /** + * 安全规范化压缩包内路径 + * @param entryPath 原始路径(如 "111/222/3.txt") + * @param archiveName 压缩包文件名(如 "111.7z") + * @return 规范化后的路径(如 "222/3.txt") + */ + private String normalizePathtargz(String entryPath, String archiveName) { + if (entryPath == null || entryPath.trim().isEmpty()) return ""; + + // 统一路径分隔符并清理特殊字符 + String path = entryPath.replace("\\", "/") + .replaceAll("^\\./", "") + .replaceAll("/+", "/") + .trim(); + + // 移除压缩包文件名对应的顶层目录(如果存在) + String baseName = archiveName.replaceAll("\\.(tar\\.gz|gz|tar|7z)$", ""); + if (!baseName.isEmpty() && path.startsWith(baseName + "/")) { + return path.substring(baseName.length() + 1); + } + return path; + } + + /** + * 解压7Z格式文件 + * + * @param sourcePath 压缩文件路径(必须是以.7z结尾的文件) + * @param baseDir 解压目标目录路径 + * @return 解压后的根目录File对象 + */ private File un7zFile(Path sourcePath, String baseDir) throws IOException { - Path destRoot = Paths.get(baseDir); + // 参数校验 + if (!sourcePath.getFileName().toString().toLowerCase().endsWith(".7z")) { + throw new IllegalArgumentException("必须是.7z文件"); + } + + Path destRoot = Paths.get(baseDir).toAbsolutePath(); Files.createDirectories(destRoot); try (SevenZFile szFile = new SevenZFile(sourcePath.toFile())) { SevenZArchiveEntry entry; while ((entry = szFile.getNextEntry()) != null) { - // 保持相同过滤逻辑 - if (entry.getName().startsWith("__MACOSX/")) { + // 跳过无效条目 + if (entry.getName() == null || entry.getName().contains("__MACOSX")) { continue; } - Path targetPath = destRoot.resolve(entry.getName()).normalize(); + // 关键修复:使用智能路径规范化 + String normalizedPath = smartNormalizePath7z(entry.getName(), sourcePath.getFileName().toString()); + if (normalizedPath.isEmpty()) { + continue; // 跳过根目录条目 + } + + Path targetPath = destRoot.resolve(normalizedPath).normalize(); validatePathSafety(targetPath, destRoot); + // 处理文件/目录 if (entry.isDirectory()) { Files.createDirectories(targetPath); } else { @@ -2068,6 +2373,67 @@ public class TsFilesServiceImpl extends ServiceImpl impl return destRoot.toFile(); } + /** + * 智能路径规范化(修复7z/tar.gz多层级问题) + * @param entryPath 压缩包内原始路径(如 "111/222/3.txt") + * @param archiveName 压缩包文件名(如 "111.7z") + * @return 规范化后的路径(如 "222/3.txt") + */ + private String smartNormalizePath7z(String entryPath, String archiveName) { + if (entryPath == null || entryPath.trim().isEmpty()) { + return ""; + } + + // 统一路径分隔符 + String path = entryPath.replace("\\", "/") + .replaceAll("/+", "/") + .trim(); + + // 移除压缩包名称对应的冗余顶层目录 + String baseName = archiveName.replaceAll("\\.(7z|tar\\.gz|tar|zip)$", ""); + if (!baseName.isEmpty()) { + // 情况1:路径直接以baseName开头(如 "111/222/3.txt") + if (path.startsWith(baseName + "/")) { + return path.substring(baseName.length() + 1); + } + // 情况2:路径是baseName自身(如 "111/") + if (path.equals(baseName) || path.equals(baseName + "/")) { + return ""; + } + } + + return path; + } + + + + /** + * 设置文件权限(兼容Unix系统) + * + * @param path 目标路径 + * @param entry 压缩条目(含权限信息) + */ + private void setFilePermissions(Path path, TarArchiveEntry entry) throws IOException { + if (!System.getProperty("os.name").toLowerCase().contains("win")) { + Set perms = PosixFilePermissions.fromString( + getPosixPermissions(entry.getMode()) + ); + Files.setPosixFilePermissions(path, perms); + } + } + + /** + * 转换Unix权限位为rwx格式 + * + * @param mode 权限位(如0755) + */ + private String getPosixPermissions(int mode) { + return String.format("%s%s%s", + (mode & 0400) != 0 ? "r" : "-", + (mode & 0200) != 0 ? "w" : "-", + (mode & 0100) != 0 ? "x" : "-" + ); + } /** @@ -2083,6 +2449,23 @@ public class TsFilesServiceImpl extends ServiceImpl impl * 获取无扩展名的文件名 */ private String getFileNameWithoutExtension(String fileName) { + // 检查是否是 .tar.gz 格式 + if (fileName.endsWith(".tar.gz")) { + return fileName.substring(0, fileName.length() - 7); // 去掉 ".tar.gz" + } + // 检查是否是 .tar 格式 + if (fileName.endsWith(".tar")) { + return fileName.substring(0, fileName.length() - 4); // 去掉 ".tar" + } + // 检查是否是 .zip 格式 + if (fileName.endsWith(".zip")) { + return fileName.substring(0, fileName.length() - 4); // 去掉 ".zip" + } + // 检查是否是 .7z 格式 + if (fileName.endsWith(".7z")) { + return fileName.substring(0, fileName.length() - 3); // 去掉 ".7z" + } + // 对其他普通文件名,直接返回去除扩展名的部分 int dotIndex = fileName.lastIndexOf('.'); return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex); } @@ -2328,7 +2711,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl String key = generateMapKey(minioFile); if (localMap.containsKey(key)) { FileItemResult localFile = localMap.get(key); - checkFileConsistency(minioFile, localFile, filePath, bucketName, sizeMismatches, md5Mismatches); + checkFileConsistency(minioFile, localFile, filePath, bucketName, sizeMismatches, md5Mismatches, nodeId); } }); @@ -2389,7 +2772,11 @@ public class TsFilesServiceImpl extends ServiceImpl impl String basePath, String bucketName, List sizeMismatches, - List md5Mismatches) { + List md5Mismatches, String nodeId) { + + + minioFile.setPath("/" + nodeId + minioFile.getPath()); + localFile.setPath("/" + nodeId + localFile.getPath()); File localFileObj = new File(basePath + minioFile.getPath(), localFile.getName()); if (localFileObj.isDirectory()) { LOGGER.warn("跳过文件夹: {}", localFileObj.getAbsolutePath()); @@ -3667,8 +4054,8 @@ public class TsFilesServiceImpl extends ServiceImpl impl public String processingPath(String Path, String nodeId) { String newWorkPath = ""; - if(Path == null || nodeId == null){ - return newWorkPath; + if (Path == null || nodeId == null) { + return newWorkPath; } // 获取原始路径和 nodeId String workPath1 = Path;