提交代码tar tar.gz 7z压缩解压缩
This commit is contained in:
parent
a477ae3952
commit
9eaa8c300d
@ -6,6 +6,8 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.*;
|
import java.nio.file.*;
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
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.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -46,6 +48,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
|
|||||||
import net.sf.jsqlparser.expression.LongValue;
|
import net.sf.jsqlparser.expression.LongValue;
|
||||||
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
|
import org.apache.commons.compress.archivers.sevenz.SevenZArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.sevenz.SevenZFile;
|
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.TarArchiveEntry;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
|
||||||
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
|
||||||
@ -244,6 +247,22 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
|
|
||||||
// 获取文件扩展名的方法
|
// 获取文件扩展名的方法
|
||||||
private String getFileExtension(String filename) {
|
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(".")) {
|
if (filename != null && filename.contains(".")) {
|
||||||
return filename.substring(filename.lastIndexOf("."));
|
return filename.substring(filename.lastIndexOf("."));
|
||||||
}
|
}
|
||||||
@ -1215,13 +1234,23 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
if (pathSegments.isEmpty() || !pathSegments.get(0).equals(nodeId)) {
|
if (pathSegments.isEmpty() || !pathSegments.get(0).equals(nodeId)) {
|
||||||
throw new RuntimeException("路径必须包含当前节点ID");
|
throw new RuntimeException("路径必须包含当前节点ID");
|
||||||
}
|
}
|
||||||
|
String nodePath = "/" + nodeId + "/";
|
||||||
|
if (compressedPath.equals(nodePath)) {
|
||||||
|
return "00";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 提取实际要处理的目录层级(去掉开头的nodeId)
|
// 提取实际要处理的目录层级(去掉开头的nodeId)
|
||||||
List<String> dirSegments = pathSegments.subList(1, pathSegments.size());
|
List<String> dirSegments = null;
|
||||||
|
if (pathSegments.size() > 1) {
|
||||||
|
// 如果 pathSegments 大于 1,去掉 nodeId
|
||||||
|
dirSegments = pathSegments.subList(1, pathSegments.size());
|
||||||
|
}
|
||||||
if (dirSegments.isEmpty()) {
|
if (dirSegments.isEmpty()) {
|
||||||
throw new RuntimeException("路径缺少有效目录层级");
|
throw new RuntimeException("路径缺少有效目录层级");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 2. 初始化根目录信息(基于nodeId)
|
// 2. 初始化根目录信息(基于nodeId)
|
||||||
String parentId = "00"; // 根目录的父ID为0
|
String parentId = "00"; // 根目录的父ID为0
|
||||||
String baseFsPath = "/" + nodeId + "/"; // 本地存储基础路径
|
String baseFsPath = "/" + nodeId + "/"; // 本地存储基础路径
|
||||||
@ -1296,10 +1325,12 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
switch (compressedFormat.toUpperCase()) {
|
switch (compressedFormat.toUpperCase()) {
|
||||||
case "ZIP":
|
case "ZIP":
|
||||||
return compressToZip(sourcePaths, outputPath);
|
return compressToZip(sourcePaths, outputPath);
|
||||||
|
case "TAR":
|
||||||
|
return compressToTar(sourcePaths, outputPath);
|
||||||
case "TAR.GZ":
|
case "TAR.GZ":
|
||||||
return compressToTarGz(sourcePaths);
|
return compressToTarGz(sourcePaths, outputPath);
|
||||||
// case "7Z":
|
case "7Z":
|
||||||
// return compressTo7z(sourcePaths);
|
return compressTo7z(sourcePaths, outputPath);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("不支持的压缩格式: " + compressedFormat);
|
throw new IllegalArgumentException("不支持的压缩格式: " + compressedFormat);
|
||||||
}
|
}
|
||||||
@ -1436,10 +1467,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
addedEntries.add(entryName);
|
addedEntries.add(entryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Path generateZipPath(Path sourcePath) {
|
|
||||||
String baseName = sourcePath.getFileName().toString().replaceFirst("[.][^.]+$", "");
|
|
||||||
return sourcePath.resolveSibling(baseName + ".zip");
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean validateZip(Path zipPath) {
|
private boolean validateZip(Path zipPath) {
|
||||||
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
|
try (ZipFile zipFile = new ZipFile(zipPath.toFile())) {
|
||||||
@ -1485,77 +1512,186 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ================== TAR.GZ格式压缩实现 ==================
|
// ================== TAR格式压缩实现 ==================
|
||||||
private boolean compressToTarGz(List<Path> sourcePaths) throws IOException {
|
|
||||||
boolean success = true;
|
/**
|
||||||
|
* 压缩为纯TAR格式(不进行GZ压缩)
|
||||||
|
*
|
||||||
|
* @param sourcePaths 要压缩的源路径列表(文件或目录)
|
||||||
|
* @param outputPath 输出文件路径(必须以.tar结尾)
|
||||||
|
* @return 是否压缩成功
|
||||||
|
* @throws IOException 文件操作异常
|
||||||
|
*/
|
||||||
|
private boolean compressToTar(List<Path> sourcePaths, Path outputPath) throws IOException {
|
||||||
|
try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream(Files.newOutputStream(outputPath))) {
|
||||||
|
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
||||||
|
|
||||||
for (Path sourcePath : sourcePaths) {
|
for (Path sourcePath : sourcePaths) {
|
||||||
Path tarGzPath = getCompressedFilePath(sourcePath, "tar.gz");
|
// 关键修改:始终使用源路径的父目录作为basePath
|
||||||
try (OutputStream fos = Files.newOutputStream(tarGzPath);
|
Path basePath = sourcePath.getParent();
|
||||||
|
|
||||||
|
Files.walkFileTree(sourcePath, new SimpleFileVisitor<Path>() {
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助方法:添加文件条目
|
||||||
|
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<Path> sourcePaths, Path outputPath) throws IOException {
|
||||||
|
try (OutputStream fos = Files.newOutputStream(outputPath);
|
||||||
GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos);
|
GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos);
|
||||||
TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzos)) {
|
TarArchiveOutputStream tarOut = new TarArchiveOutputStream(gzos)) {
|
||||||
|
|
||||||
|
// 设置支持长文件名(超过100字符)
|
||||||
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
tarOut.setLongFileMode(TarArchiveOutputStream.LONGFILE_POSIX);
|
||||||
|
|
||||||
processEntries(sourcePath, (entryName, isDir) -> {
|
// 遍历所有源路径并添加到压缩文件
|
||||||
Path filePath = sourcePath.resolve(entryName);
|
for (Path path : sourcePaths) {
|
||||||
TarArchiveEntry entry = new TarArchiveEntry(filePath.toFile(), entryName);
|
addToTarArchive(path, tarOut, "");
|
||||||
entry.setSize(isDir ? 0 : Files.size(filePath));
|
|
||||||
tarOut.putArchiveEntry(entry);
|
|
||||||
|
|
||||||
if (!isDir) {
|
|
||||||
try (InputStream input = Files.newInputStream(filePath)) {
|
|
||||||
IOUtils.copy(input, tarOut);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tarOut.closeArchiveEntry();
|
|
||||||
});
|
|
||||||
|
|
||||||
tarOut.finish();
|
tarOut.finish();
|
||||||
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
success = false;
|
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ================== 7Z格式压缩实现 ==================
|
/**
|
||||||
// private boolean compressTo7z(List<Path> sourcePaths) throws IOException {
|
* 递归添加文件到TAR压缩流
|
||||||
// boolean success = true;
|
*
|
||||||
// for (Path sourcePath : sourcePaths) {
|
* @param source 当前要添加的路径
|
||||||
// Path sevenZPath = getCompressedFilePath(sourcePath, "7z");
|
* @param tarOut TAR输出流
|
||||||
// try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(sevenZPath.toFile())) {
|
* @param parentDir 在压缩文件中的父目录路径(用于保持目录结构)
|
||||||
//
|
*/
|
||||||
// processEntries(sourcePath, (entryName, isDir) -> {
|
private void addToTarArchive(Path source, TarArchiveOutputStream tarOut, String parentDir) throws IOException {
|
||||||
// SevenZArchiveEntry entry = sevenZOutput.createEntry();
|
// 获取文件在压缩包中的入口名称(替换路径分隔符为/)
|
||||||
// entry.setName(entryName);
|
String entryName = parentDir + source.getFileName().toString().replace(File.separator, "/");
|
||||||
// entry.setDirectory(isDir);
|
|
||||||
//
|
// 处理目录
|
||||||
// if (!isDir) {
|
if (Files.isDirectory(source)) {
|
||||||
// Path filePath = sourcePath.resolve(entryName);
|
entryName += "/"; // 目录需要以/结尾
|
||||||
// entry.setSize(Files.size(filePath));
|
TarArchiveEntry dirEntry = new TarArchiveEntry(source.toFile(), entryName);
|
||||||
// sevenZOutput.putArchiveEntry(entry);
|
tarOut.putArchiveEntry(dirEntry);
|
||||||
//
|
tarOut.closeArchiveEntry();
|
||||||
// try (InputStream input = Files.newInputStream(filePath)) {
|
|
||||||
// byte[] buffer = new byte[8192];
|
// 递归处理子目录
|
||||||
// int len;
|
try (DirectoryStream<Path> children = Files.newDirectoryStream(source)) {
|
||||||
// while ((len = input.read(buffer)) != -1) {
|
for (Path child : children) {
|
||||||
// sevenZOutput.write(buffer, 0, len);
|
addToTarArchive(child, tarOut, entryName);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//
|
}
|
||||||
// sevenZOutput.closeArchiveEntry();
|
// 处理文件
|
||||||
// }
|
else {
|
||||||
// });
|
TarArchiveEntry fileEntry = new TarArchiveEntry(source.toFile(), entryName);
|
||||||
//
|
fileEntry.setSize(Files.size(source));
|
||||||
// } catch (Exception e) {
|
tarOut.putArchiveEntry(fileEntry);
|
||||||
// success = false;
|
try (InputStream input = Files.newInputStream(source)) {
|
||||||
// e.printStackTrace();
|
IOUtils.copy(input, tarOut);
|
||||||
// }
|
}
|
||||||
// }
|
tarOut.closeArchiveEntry();
|
||||||
// return success;
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 压缩为7z格式
|
||||||
|
*
|
||||||
|
* @param sourcePaths 要压缩的源路径列表
|
||||||
|
* @param outputPath 输出文件路径(必须包含文件名)
|
||||||
|
*/
|
||||||
|
private boolean compressTo7z(List<Path> 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<Path> children = Files.newDirectoryStream(source)) {
|
||||||
|
for (Path child : children) {
|
||||||
|
addTo7zArchive(child, sevenZOutput, entryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ================== 通用工具方法 ==================
|
// ================== 通用工具方法 ==================
|
||||||
private interface EntryProcessor {
|
private interface EntryProcessor {
|
||||||
@ -1862,6 +1998,8 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
return unzipFile(zipFilePath, baseDir);
|
return unzipFile(zipFilePath, baseDir);
|
||||||
} else if (fileName.endsWith(".tar.gz")) {
|
} else if (fileName.endsWith(".tar.gz")) {
|
||||||
return unTarGzFile(zipFilePath, baseDir);
|
return unTarGzFile(zipFilePath, baseDir);
|
||||||
|
} else if (ext.equals(".tar")) {
|
||||||
|
return unTarFile(zipFilePath, baseDir);
|
||||||
} else if (ext.equals(".7z")) {
|
} else if (ext.equals(".7z")) {
|
||||||
return un7zFile(zipFilePath, baseDir);
|
return un7zFile(zipFilePath, baseDir);
|
||||||
} else {
|
} else {
|
||||||
@ -1878,6 +2016,8 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
return hasFolderInZip(zipFilePath);
|
return hasFolderInZip(zipFilePath);
|
||||||
} else if (fileName.endsWith(".tar.gz")) {
|
} else if (fileName.endsWith(".tar.gz")) {
|
||||||
return hasFolderInTarGz(zipFilePath);
|
return hasFolderInTarGz(zipFilePath);
|
||||||
|
} else if (ext.equals(".tar")) {
|
||||||
|
return hasFolderInTar(zipFilePath);
|
||||||
} else if (ext.equals(".7z")) {
|
} else if (ext.equals(".7z")) {
|
||||||
return hasFolderIn7z(zipFilePath);
|
return hasFolderIn7z(zipFilePath);
|
||||||
} else {
|
} else {
|
||||||
@ -1910,6 +2050,38 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> 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格式文件夹判断
|
// 新增:7Z格式文件夹判断
|
||||||
private boolean hasFolderIn7z(Path filePath) throws IOException {
|
private boolean hasFolderIn7z(Path filePath) throws IOException {
|
||||||
try (SevenZFile szFile = new SevenZFile(filePath.toFile())) {
|
try (SevenZFile szFile = new SevenZFile(filePath.toFile())) {
|
||||||
@ -2003,23 +2175,93 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> 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 {
|
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);
|
Files.createDirectories(destRoot);
|
||||||
|
|
||||||
try (InputStream fi = Files.newInputStream(sourcePath);
|
try (TarArchiveInputStream tis = new TarArchiveInputStream(
|
||||||
InputStream gi = new GzipCompressorInputStream(fi);
|
new GzipCompressorInputStream(Files.newInputStream(sourcePath)))) {
|
||||||
TarArchiveInputStream ti = new TarArchiveInputStream(gi)) {
|
|
||||||
|
|
||||||
TarArchiveEntry entry;
|
TarArchiveEntry entry;
|
||||||
while ((entry = ti.getNextTarEntry()) != null) {
|
while ((entry = tis.getNextTarEntry()) != null) {
|
||||||
// 保持相同过滤逻辑
|
// 跳过无效条目
|
||||||
if (entry.getName().startsWith("__MACOSX/")) {
|
if (entry.getName() == null || entry.getName().contains("__MACOSX")) {
|
||||||
continue;
|
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);
|
validatePathSafety(targetPath, destRoot);
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
@ -2027,7 +2269,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
} else {
|
} else {
|
||||||
Files.createDirectories(targetPath.getParent());
|
Files.createDirectories(targetPath.getParent());
|
||||||
try (OutputStream os = Files.newOutputStream(targetPath)) {
|
try (OutputStream os = Files.newOutputStream(targetPath)) {
|
||||||
IOUtils.copy(ti, os);
|
IOUtils.copy(tis, os);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2035,22 +2277,85 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
return destRoot.toFile();
|
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 {
|
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);
|
Files.createDirectories(destRoot);
|
||||||
|
|
||||||
try (SevenZFile szFile = new SevenZFile(sourcePath.toFile())) {
|
try (SevenZFile szFile = new SevenZFile(sourcePath.toFile())) {
|
||||||
SevenZArchiveEntry entry;
|
SevenZArchiveEntry entry;
|
||||||
while ((entry = szFile.getNextEntry()) != null) {
|
while ((entry = szFile.getNextEntry()) != null) {
|
||||||
// 保持相同过滤逻辑
|
// 跳过无效条目
|
||||||
if (entry.getName().startsWith("__MACOSX/")) {
|
if (entry.getName() == null || entry.getName().contains("__MACOSX")) {
|
||||||
continue;
|
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);
|
validatePathSafety(targetPath, destRoot);
|
||||||
|
|
||||||
|
// 处理文件/目录
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
Files.createDirectories(targetPath);
|
Files.createDirectories(targetPath);
|
||||||
} else {
|
} else {
|
||||||
@ -2068,6 +2373,67 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
return destRoot.toFile();
|
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<PosixFilePermission> 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<TsFilesMapper, TsFiles> impl
|
|||||||
* 获取无扩展名的文件名
|
* 获取无扩展名的文件名
|
||||||
*/
|
*/
|
||||||
private String getFileNameWithoutExtension(String fileName) {
|
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('.');
|
int dotIndex = fileName.lastIndexOf('.');
|
||||||
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
|
return (dotIndex == -1) ? fileName : fileName.substring(0, dotIndex);
|
||||||
}
|
}
|
||||||
@ -2328,7 +2711,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
String key = generateMapKey(minioFile);
|
String key = generateMapKey(minioFile);
|
||||||
if (localMap.containsKey(key)) {
|
if (localMap.containsKey(key)) {
|
||||||
FileItemResult localFile = localMap.get(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<TsFilesMapper, TsFiles> impl
|
|||||||
String basePath,
|
String basePath,
|
||||||
String bucketName,
|
String bucketName,
|
||||||
List<String> sizeMismatches,
|
List<String> sizeMismatches,
|
||||||
List<FileItemResult> md5Mismatches) {
|
List<FileItemResult> md5Mismatches, String nodeId) {
|
||||||
|
|
||||||
|
|
||||||
|
minioFile.setPath("/" + nodeId + minioFile.getPath());
|
||||||
|
localFile.setPath("/" + nodeId + localFile.getPath());
|
||||||
File localFileObj = new File(basePath + minioFile.getPath(), localFile.getName());
|
File localFileObj = new File(basePath + minioFile.getPath(), localFile.getName());
|
||||||
if (localFileObj.isDirectory()) {
|
if (localFileObj.isDirectory()) {
|
||||||
LOGGER.warn("跳过文件夹: {}", localFileObj.getAbsolutePath());
|
LOGGER.warn("跳过文件夹: {}", localFileObj.getAbsolutePath());
|
||||||
@ -3667,7 +4054,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
|
|||||||
|
|
||||||
public String processingPath(String Path, String nodeId) {
|
public String processingPath(String Path, String nodeId) {
|
||||||
String newWorkPath = "";
|
String newWorkPath = "";
|
||||||
if(Path == null || nodeId == null){
|
if (Path == null || nodeId == null) {
|
||||||
return newWorkPath;
|
return newWorkPath;
|
||||||
}
|
}
|
||||||
// 获取原始路径和 nodeId
|
// 获取原始路径和 nodeId
|
||||||
|
Loading…
Reference in New Issue
Block a user