提交代码

This commit is contained in:
lilin 2025-03-24 09:17:09 +08:00
parent 62cf72359a
commit b08961f2d3
3 changed files with 420 additions and 525 deletions

View File

@ -116,7 +116,7 @@ public class TsFilesController {
@ApiOperation("新增试验数据管理文件夹") @ApiOperation("新增试验数据管理文件夹")
@ResponseBody @ResponseBody
@PreAuthorize("@el.check('add:tsFiles')") @PreAuthorize("@el.check('add:tsFiles')")
public ResponseResult addTsFile(@RequestBody TsFiles tsFiles) { public ResponseResult addTsFile(@RequestBody TsFiles tsFiles) throws IOException {
//对象不能为空 //对象不能为空
if (ObjUtil.isEmpty(tsFiles)) { if (ObjUtil.isEmpty(tsFiles)) {
return ResponseResult.error("参数为空"); return ResponseResult.error("参数为空");
@ -461,4 +461,6 @@ public class TsFilesController {
} }
} }
} }

View File

@ -109,7 +109,7 @@ public interface ITsFilesService extends IService<TsFiles> {
* TsFiles 文档内容 * TsFiles 文档内容
* 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败
***********************************/ ***********************************/
ResponseResult addTsFile(TsFiles tsFiles); ResponseResult addTsFile(TsFiles tsFiles) throws IOException;
/********************************** /**********************************
* 用途说明: 查询实验数据管理文件夹 * 用途说明: 查询实验数据管理文件夹

View File

@ -5,6 +5,7 @@ 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.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@ -62,6 +63,7 @@ import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -75,10 +77,7 @@ import javax.xml.crypto.Data;
import java.io.*; import java.io.*;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.zip.GZIPInputStream; import java.util.zip.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.Hex;
@ -472,8 +471,41 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
***********************************/ ***********************************/
@Override @Override
@Transactional(rollbackFor = Exception.class)// 添加事务注解遇到异常时回滚 @Transactional(rollbackFor = Exception.class)// 添加事务注解遇到异常时回滚
public ResponseResult addTsFile(TsFiles tsFiles) { public ResponseResult addTsFile(TsFiles tsFiles) throws IOException {
if (tsFiles.getIsFile().equals("FILE")) {
StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath"));
String basePath = config.getValue() + tsFiles.getWorkPath();
// 拼接完整文件路径
// 拼接完整的文件路径
Path filePath = Paths.get(basePath, tsFiles.getFileName() + ".txt");
// 确保目录存在如果不存在则创建目录
Files.createDirectories(filePath.getParent());
// 使用 Files.write() 写入字符串内容
Files.write(filePath, "默认内容".getBytes(StandardCharsets.UTF_8));
tsFiles.setFileName(tsFiles.getFileName() + ".txt");
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
LoginUser loginuser = (LoginUser) authentication.getPrincipal();
// 设置当前时间
LocalDateTime now = LocalDateTime.now();
// 转换为 Timestamp
Timestamp currentTime = Timestamp.valueOf(now);
tsFiles.setUploadTime(currentTime);
tsFiles.setUploader(loginuser.getUsername());
tsFiles.setFileSize("0");
int valueAdded = tsFilesMapper.insert(tsFiles);
if (valueAdded == 1) {
LOGGER.info("文件创建成功");
return ResponseResult.success();
} else {
LOGGER.error("文件创建失败");
return ResponseResult.error();
}
} else {
// 校验文件名是否包含非法字符 // 校验文件名是否包含非法字符
String fileName = tsFiles.getFileName(); String fileName = tsFiles.getFileName();
if (containsInvalidCharacters(fileName)) { if (containsInvalidCharacters(fileName)) {
@ -547,6 +579,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
return ResponseResult.error(); return ResponseResult.error();
} }
} }
}
// 校验文件名是否包含非法字符 // 校验文件名是否包含非法字符
private boolean containsInvalidCharacters(String fileName) { private boolean containsInvalidCharacters(String fileName) {
@ -1018,13 +1051,11 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
String zipFileName = compressedName + "." + compressedFormat; String zipFileName = compressedName + "." + compressedFormat;
// 使用 try-with-resources 确保 zipOut 在使用后关闭 // 使用 try-with-resources 确保 zipOut 在使用后关闭
try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(new FileOutputStream(zipFilePath.toFile()))) {
// 设置压缩级别可以选择调整 // 设置压缩级别可以选择调整
zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别 // zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别
// 调用压缩方法 // 调用压缩方法
Boolean value = compressToSameDirectory(sourceDirs, compressedFormat); Boolean value = compressToSameDirectory(sourceDirs, compressedFormat);
if (value) { if (value) {
zipOut.finish(); // 确保所有数据都已写入
//表结构增加 nodeId, String TaskId //表结构增加 nodeId, String TaskId
TsFiles tsFiles = new TsFiles(); TsFiles tsFiles = new TsFiles();
tsFiles.setTaskId(filesList.get(0).getTaskId()); tsFiles.setTaskId(filesList.get(0).getTaskId());
@ -1098,100 +1129,26 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} else { } else {
returnResult = "压缩失败"; returnResult = "压缩失败";
} }
} catch (IOException e) {
e.printStackTrace();
}
return returnResult; return returnResult;
} }
// ================== 核心方法改造 ================== // ================== 核心方法改造 ==================
/**
* 多路径压缩到各自同级目录 private Boolean compressToSameDirectory(List<Path> sourcePaths, String compressedFormat) {
* @param sourcePaths 要压缩的路径集合
* @param compressionFormat 前端传入的压缩格式字符串
* @return 压缩成功返回true失败返回false
*/
public boolean compressToSameDirectory(List<Path> sourcePaths, String compressionFormat) {
try { try {
// 转换压缩格式 switch (compressedFormat.toUpperCase()) {
CompressionFormat format = CompressionFormat.fromString(compressionFormat); case "ZIP":
return compressToZip(sourcePaths);
// 遍历所有路径进行压缩 case "TAR.GZ":
for (Path sourcePath : sourcePaths) { return compressToTarGz(sourcePaths);
// 验证路径有效性 // case "7Z":
validatePath(sourcePath); // return compressTo7z(sourcePaths);
default:
// 生成目标路径 throw new IllegalArgumentException("不支持的压缩格式: " + compressedFormat);
Path targetPath = generateTargetPath(sourcePath, format);
// 执行压缩
if (!compressSinglePath(sourcePath, targetPath, format)) {
return false; // 任一压缩失败则终止
} }
}
return true; // 全部压缩成功
} catch (IllegalArgumentException e) {
System.err.println("格式错误: " + e.getMessage());
return false;
} catch (IOException e) { } catch (IOException e) {
System.err.println("压缩失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
// ================== 核心私有方法 ==================
/**
* 验证路径有效性
* @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 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(); e.printStackTrace();
return false; return false;
} }
@ -1199,222 +1156,252 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
// ================== ZIP格式压缩实现 ================== // ================== ZIP格式压缩实现 ==================
private void compressToZip(Path source, Path target) throws IOException {
try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(Files.newOutputStream(target))) { /**
Set<Path> processedPaths = new HashSet<>(); * 压缩多个文件/目录到单个ZIP
processZipEntry(source, zipOut, "", processedPaths); */
} public boolean compressToZip(List<Path> sourcePaths) {
if (sourcePaths.isEmpty()) {
LOGGER.warn("源路径列表为空,无法压缩");
return false;
} }
private void processZipEntry(Path currentPath, Path zipPath = generateZipPath(sourcePaths.get(0));
ZipArchiveOutputStream zipOut, try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipPath))) {
String parentEntry, zipOut.setLevel(Deflater.DEFAULT_COMPRESSION);
Set<Path> processedPaths) throws IOException {
// 获取相对于压缩根的路径
Path relativePath = getRelativePath(currentPath);
// 构建条目名称关键修改 for (Path sourcePath : sourcePaths) {
String entryName = buildZipEntryName(relativePath, parentEntry); if (Files.isDirectory(sourcePath)) {
addDirectoryToZip(sourcePath, zipOut, sourcePath.getFileName().toString());
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 { } else {
addFileToZip(currentPath, zipOut, entryName); addFileToZip(sourcePath, zipOut, "");
} }
} }
// ================== TAR.GZ格式压缩实现 ================== zipOut.finish();
return validateZip(zipPath);
private void compressToTarGz(Path source, Path target) throws IOException { } catch (Exception e) {
try (TarArchiveOutputStream tarOut = new TarArchiveOutputStream( LOGGER.error("压缩失败", e);
new GzipCompressorOutputStream(Files.newOutputStream(target)))) { deleteQuietly(zipPath);
Set<Path> processedPaths = new HashSet<>(); return false;
processTarEntry(source, tarOut, "", processedPaths);
} }
} }
private void addDirectoryToZip(Path dir, ZipOutputStream zipOut, String baseDir) throws IOException {
Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
String entryName = baseDir + "/" + dir.relativize(dir).toString().replace("\\", "/") + "/";
ZipEntry entry = new ZipEntry(entryName);
zipOut.putNextEntry(entry);
zipOut.closeEntry();
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String entryName = baseDir + "/" + dir.relativize(file).toString().replace("\\", "/");
addFileEntry(file, entryName, zipOut);
return FileVisitResult.CONTINUE;
}
});
}
private void addFileToZip(Path file, ZipOutputStream zipOut, String baseDir) throws IOException {
String entryName = baseDir + file.getFileName().toString();
addFileEntry(file, entryName, zipOut);
}
private void addFileEntry(Path file, String entryName, ZipOutputStream zipOut) throws IOException {
LOGGER.debug("添加文件条目: {} (大小: {} 字节)", entryName, Files.size(file));
ZipEntry entry = new ZipEntry(entryName);
entry.setSize(Files.size(file));
entry.setTime(Files.getLastModifiedTime(file).toMillis());
zipOut.putNextEntry(entry);
try (InputStream input = Files.newInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
zipOut.write(buffer, 0, bytesRead);
}
}
zipOut.closeEntry();
}
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())) {
Enumeration<? extends ZipEntry> entries = zipFile.entries(); // 获取所有条目
if (!entries.hasMoreElements()) {
LOGGER.warn("ZIP 文件为空: {}", zipPath);
return false;
}
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
try (InputStream is = zipFile.getInputStream(entry)) {
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {} // 读取条目内容
}
}
LOGGER.info("ZIP 文件验证成功: {}", zipPath);
return true;
} catch (Exception e) {
LOGGER.error("ZIP 文件验证失败: {}", zipPath, e);
return false;
}
}
private void deleteQuietly(Path path) {
try {
Files.deleteIfExists(path);
} catch (IOException ignored) {}
}
/** /**
* 获取相对于压缩根的路径 * 生成压缩文件路径去除源文件扩展名
*/ */
private Path getRelativePath(Path absolutePath) { private Path getCompressedFilePath(Path sourcePath, String extension) {
// 示例如果压缩根是 /data/1.txt则返回空路径 String fileName = sourcePath.getFileName().toString();
// 如果是压缩目录 /data/333则返回相对路径 String baseName = fileName.contains(".")
// 需要根据实际压缩根路径调整实现 ? fileName.substring(0, fileName.lastIndexOf('.'))
return absolutePath.getFileName(); : fileName;
return sourcePath.resolveSibling(baseName + "." + extension);
} }
private void processTarEntry(Path currentPath,
TarArchiveOutputStream tarOut,
String parentEntry,
Set<Path> 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);
// ================== TAR.GZ格式压缩实现 ==================
private boolean compressToTarGz(List<Path> 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)) {
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); tarOut.putArchiveEntry(entry);
tarOut.closeArchiveEntry();
if (Files.isDirectory(currentPath)) { if (!isDir) {
try (DirectoryStream<Path> children = Files.newDirectoryStream(currentPath)) { try (InputStream input = Files.newInputStream(filePath)) {
for (Path child : children) { IOUtils.copy(input, tarOut);
processTarEntry(child, tarOut, entryName, processedPaths);
} }
} }
tarOut.closeArchiveEntry();
});
tarOut.finish();
} catch (Exception e) {
success = false;
e.printStackTrace();
} }
} }
return success;
}
// ================== 7Z格式压缩实现 ================== // ================== 7Z格式压缩实现 ==================
// private boolean compressTo7z(List<Path> sourcePaths) throws IOException {
private void compressTo7z(Path source, Path target) throws IOException { // boolean success = true;
try (SevenZOutputFile sevenZOut = new SevenZOutputFile(target.toFile())) { // for (Path sourcePath : sourcePaths) {
Set<Path> processedPaths = new HashSet<>(); // Path sevenZPath = getCompressedFilePath(sourcePath, "7z");
process7zEntry(source, sevenZOut, "", processedPaths); // try (SevenZOutputFile sevenZOutput = new SevenZOutputFile(sevenZPath.toFile())) {
} //
} // processEntries(sourcePath, (entryName, isDir) -> {
// SevenZArchiveEntry entry = sevenZOutput.createEntry();
private void process7zEntry(Path currentPath, // entry.setName(entryName);
SevenZOutputFile sevenZOut, // entry.setDirectory(isDir);
String parentEntry, //
Set<Path> processedPaths) throws IOException { // if (!isDir) {
Path realPath = currentPath.toRealPath(); // Path filePath = sourcePath.resolve(entryName);
// entry.setSize(Files.size(filePath));
if (Files.isDirectory(currentPath)) { // sevenZOutput.putArchiveEntry(entry);
if (processedPaths.contains(realPath)) return; //
processedPaths.add(realPath); // try (InputStream input = Files.newInputStream(filePath)) {
} // byte[] buffer = new byte[8192];
// int len;
String entryName = build7zEntryName(currentPath, parentEntry); // while ((len = input.read(buffer)) != -1) {
SevenZArchiveEntry entry = sevenZOut.createArchiveEntry(currentPath.toFile(), entryName); // sevenZOutput.write(buffer, 0, len);
sevenZOut.putArchiveEntry(entry); // }
sevenZOut.closeArchiveEntry(); // }
//
if (Files.isDirectory(currentPath)) { // sevenZOutput.closeArchiveEntry();
try (DirectoryStream<Path> children = Files.newDirectoryStream(currentPath)) { // }
for (Path child : children) { // });
process7zEntry(child, sevenZOut, entryName, processedPaths); //
} // } catch (Exception e) {
} // success = false;
} // e.printStackTrace();
} // }
// }
// return success;
// }
// ================== 通用工具方法 ================== // ================== 通用工具方法 ==================
private interface EntryProcessor {
private String buildZipEntryName(Path path, String parentEntry) { void process(String entryName, boolean isDir) throws IOException;
// 仅保留文件名部分
String fileName = path.getFileName().toString();
// 根条目处理
if (parentEntry.isEmpty()) {
return Files.isDirectory(path) ? fileName + "/" : fileName;
} }
// 子条目处理 private void processEntries(Path rootPath, EntryProcessor processor) throws IOException {
return parentEntry + (parentEntry.endsWith("/") ? "" : "/") + fileName Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() {
+ (Files.isDirectory(path) ? "/" : ""); @Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
String entryName = rootPath.relativize(dir).toString().replace("\\", "/");
if (!entryName.isEmpty()) {
processor.process(entryName + "/", true);
}
return FileVisitResult.CONTINUE;
} }
private String buildTarEntryName(Path path, String parentEntry) { @Override
return parentEntry.isEmpty() ? public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
path.getFileName().toString() : String entryName = rootPath.relativize(file).toString().replace("\\", "/");
parentEntry + "/" + path.getFileName(); processor.process(entryName, false);
return FileVisitResult.CONTINUE;
}
});
} }
private String build7zEntryName(Path path, String parentEntry) {
return buildTarEntryName(path, parentEntry);
}
private void handleZipDirectory(ZipArchiveOutputStream zipOut, String entryName) // ================== ZIP条目添加方法 ==================
throws IOException { private void addZipDirectoryEntry(ZipArchiveOutputStream zipOut, String entryName) throws IOException {
ZipArchiveEntry entry = new ZipArchiveEntry(entryName); ZipArchiveEntry entry = new ZipArchiveEntry(entryName);
entry.setMethod(ZipEntry.STORED);
entry.setSize(0);
entry.setCrc(0);
zipOut.putArchiveEntry(entry); zipOut.putArchiveEntry(entry);
zipOut.closeArchiveEntry(); zipOut.closeArchiveEntry();
} }
private void processZipChildren(Path dir, private void addZipFileEntry(ZipArchiveOutputStream zipOut, Path filePath, String entryName) throws IOException {
ZipArchiveOutputStream zipOut, ZipArchiveEntry entry = new ZipArchiveEntry(filePath.toFile(), entryName);
String parentEntry, entry.setSize(Files.size(filePath));
Set<Path> processedPaths) throws IOException { entry.setTime(Files.getLastModifiedTime(filePath).toMillis());
try (DirectoryStream<Path> 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); zipOut.putArchiveEntry(entry);
try (InputStream input = Files.newInputStream(file)) { try (InputStream input = Files.newInputStream(filePath)) {
IOUtils.copy(input, zipOut); IOUtils.copy(input, zipOut);
} }
zipOut.closeArchiveEntry(); zipOut.closeArchiveEntry();
} }
/*************************************解压缩*******************************************/ /*************************************解压缩*******************************************/
/********************************** /**********************************
@ -1442,8 +1429,12 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
// // 4. 构建解压目标路径 // // 4. 构建解压目标路径
Path destRoot; Path destRoot;
if (hasFolder) { if (hasFolder) {
// 动态去掉 .zip 后缀仅用于路径构建
String folderName = zipName.toLowerCase().endsWith(".zip")
? zipName.substring(0, zipName.length() - 4)
: zipName;
// 如果有文件夹创建子目录 E:/yun/333/222/ // 如果有文件夹创建子目录 E:/yun/333/222/
destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath, zipName); destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath, folderName);
} else { } else {
// 如果只有文件直接解压到目标目录 E:/yun/333/ // 如果只有文件直接解压到目标目录 E:/yun/333/
destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath); destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath);
@ -1655,17 +1646,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} }
// 新增统一解压入口方法保持原有逻辑结构 // 新增统一解压入口方法保持原有逻辑结构
private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException { private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException {
String fileName = zipFilePath.getFileName().toString(); String fileName = zipFilePath.getFileName().toString();
@ -1700,7 +1680,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} }
// 保持原有ZIP判断方法不变 // 保持原有ZIP判断方法不变
private boolean hasFolderInZip(Path zipFilePath) throws IOException { private boolean hasFolderInZip(Path zipFilePath) throws IOException {
try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) { try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) {
@ -1738,80 +1717,68 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} }
} }
// 保持原有解压逻辑结构的ZIP实现 // 保持原有解压逻辑结构的ZIP实现
// 修改后的ZIP解压方法修复解压问题 // 修改后的ZIP解压方法修复解压问题
private File unzipFile(Path sourcePath, String baseDir) throws IOException { private File unzipFile(Path sourcePath, String baseDir) throws IOException {
// 创建目标目录
Path destRoot = Paths.get(baseDir); Path destRoot = Paths.get(baseDir);
Files.createDirectories(destRoot); Files.createDirectories(destRoot);
// 关键修复1使用与压缩时相同的字符集
try (ZipInputStream zis = new ZipInputStream( try (ZipInputStream zis = new ZipInputStream(
Files.newInputStream(sourcePath), Files.newInputStream(sourcePath), StandardCharsets.UTF_8)) {
StandardCharsets.UTF_8)) {
// 关键修复2记录已处理路径防止重复
Set<String> processedEntries = new HashSet<>();
ZipEntry entry; ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) { while ((entry = zis.getNextEntry()) != null) {
String entryName = entry.getName(); try {
// 跳过 macOS 系统文件
// 过滤系统文件与压缩时逻辑对应 if (entry.getName().startsWith("__MACOSX")) {
if (entryName.startsWith("__MACOSX/")) {
zis.closeEntry();
continue; continue;
} }
// 关键修复3路径标准化处理 // 标准化路径并处理目录标识
entryName = entryName.replace("\\", "/"); // 统一分隔符 String entryName = entry.getName()
if (processedEntries.contains(entryName)) { .replace("\\", "/")
.replaceFirst("^/+", ""); // 去除开头的斜杠
// 检测是否为目录兼容以'/'结尾的条目
boolean isDirectory = entry.isDirectory() || entry.getName().endsWith("/");
// 调整路径去除顶层目录假设所有文件在单一顶层目录下
if (entryName.contains("/")) {
int firstSlash = entryName.indexOf('/');
entryName = entryName.substring(firstSlash + 1);
// 若处理后名称为空则跳过顶层目录条目
if (entryName.isEmpty() && isDirectory) {
continue; continue;
} }
processedEntries.add(entryName);
// 构建目标路径
Path destPath = destRoot.resolve(entryName).normalize();
validatePathSafetya(destPath, destRoot);
// 日志跟踪
LOGGER.debug("[解压] 处理条目:{} → {}", entryName, destPath);
if (entry.isDirectory()) {
Files.createDirectories(destPath);
LOGGER.debug("创建目录:{}", destPath);
} else {
// 关键修复4确保父目录存在
Path parentDir = destPath.getParent();
if (!Files.exists(parentDir)) {
Files.createDirectories(parentDir);
LOGGER.debug("创建父目录:{}", parentDir);
} }
// 关键修复5使用与压缩时相同的写入方式 Path targetPath = destRoot.resolve(entryName).normalize();
try (BufferedOutputStream bos = new BufferedOutputStream( validatePathSafetya(targetPath, destRoot);
Files.newOutputStream(destPath,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING))) {
byte[] buffer = new byte[8192]; // 处理目录
int len; if (isDirectory) {
while ((len = zis.read(buffer)) > 0) { Files.createDirectories(targetPath);
bos.write(buffer, 0, len);
} }
// 立即刷新到磁盘 // 处理文件
bos.flush(); else {
LOGGER.info("已解压文件:{} (大小:{} bytes)", if (Files.exists(targetPath)) {
destPath, Files.size(destPath)); LOGGER.warn("文件已存在,跳过覆盖: {}", targetPath);
continue;
}
// 确保父目录存在
Files.createDirectories(targetPath.getParent());
try (OutputStream os = Files.newOutputStream(targetPath)) {
IOUtils.copy(zis, os);
} }
} }
zis.closeEntry(); } finally {
zis.closeEntry(); // 确保每个条目只关闭一次
}
} }
} }
// 最终验证
LOGGER.info("解压完成,验证文件结构:");
Files.walk(destRoot)
.forEach(path -> LOGGER.info("-> {}", path));
return destRoot.toFile(); return destRoot.toFile();
} }
@ -1828,45 +1795,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
+ " 超出根目录 " + normalizedDest); + " 超出根目录 " + normalizedDest);
} }
} }
/**
* 安全写入文件带缓冲
*/
private void writeFile(ZipInputStream zis, Path destPath) throws IOException {
try (BufferedOutputStream bos = new BufferedOutputStream(
Files.newOutputStream(destPath,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
)
)) {
byte[] buffer = new byte[8192];
int len;
while ((len = zis.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
}
}
/**
* 内部类存储 ZIP 条目信息
*/
private static class ZipEntryInfo {
private final String name;
private final boolean isDirectory;
public ZipEntryInfo(String name, boolean isDirectory) {
this.name = name;
this.isDirectory = isDirectory;
}
public String getName() {
return name;
}
public boolean isDirectory() {
return isDirectory;
}
}
// 新增TAR.GZ解压实现保持相同路径逻辑 // 新增TAR.GZ解压实现保持相同路径逻辑
@ -1935,36 +1863,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} }
// /** // /**
// * 解析压缩包条目支持多种格式 // * 解析压缩包条目支持多种格式
// */ // */
@ -2015,8 +1913,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
// } // }
//上面这个方法会覆盖 TODO //上面这个方法会覆盖 TODO
// private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException { // private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException {
// // 1. 直接构建目标路径baseDir/zipName // // 1. 直接构建目标路径baseDir/zipName
@ -3835,7 +3731,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
dto.setUrl(null); dto.setUrl(null);
dto.setType(null); dto.setType(null);
}else{ } else {
dto.setUrl(fileItemResult.getUrl()); dto.setUrl(fileItemResult.getUrl());
//如果是压缩文件 类型就给zip //如果是压缩文件 类型就给zip
boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems); boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems);
@ -3931,8 +3827,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} }
// 封装发送数据的逻辑 // 封装发送数据的逻辑
private void sendData(String token, String[] values, int lineCount) { private void sendData(String token, String[] values, int lineCount) {
if (Thread.currentThread().isInterrupted()) { if (Thread.currentThread().isInterrupted()) {
@ -3980,7 +3874,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath")); StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath"));
TsFiles tsFiles = tsFilesMapper.selectById(id); TsFiles tsFiles = tsFilesMapper.selectById(id);
// 1. 路径标准化与安全校验 // 1. 路径标准化与安全校验
Path targetPath = validateAndNormalizePath(config.getValue()+tsFiles.getWorkPath()+tsFiles.getFileName()); Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName());
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();
@ -3995,7 +3889,6 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
} }
/** /**
* 验证路径是否合法并转换为标准化路径 * 验证路径是否合法并转换为标准化路径
*/ */
@ -4029,7 +3922,7 @@ public class TsFilesServiceImpl extends ServiceImpl<TsFilesMapper, TsFiles> impl
StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath")); StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper<StorageSourceConfig>().eq("name", "filePath"));
TsFiles tsFiles = tsFilesMapper.selectById(id); TsFiles tsFiles = tsFilesMapper.selectById(id);
// 1. 路径标准化与安全校验 // 1. 路径标准化与安全校验
Path targetPath = validateAndNormalizePath(config.getValue()+tsFiles.getWorkPath()+tsFiles.getFileName()); Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName());
// 2. 确保父目录存在 // 2. 确保父目录存在
Path parentDir = targetPath.getParent(); Path parentDir = targetPath.getParent();
if (parentDir != null && !Files.exists(parentDir)) { if (parentDir != null && !Files.exists(parentDir)) {