diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java index 8274339..977307b 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/controller/TsFilesController.java @@ -116,7 +116,7 @@ public class TsFilesController { @ApiOperation("新增试验数据管理文件夹") @ResponseBody @PreAuthorize("@el.check('add:tsFiles')") - public ResponseResult addTsFile(@RequestBody TsFiles tsFiles) { + public ResponseResult addTsFile(@RequestBody TsFiles tsFiles) throws IOException { //对象不能为空 if (ObjUtil.isEmpty(tsFiles)) { return ResponseResult.error("参数为空"); @@ -461,4 +461,6 @@ public class TsFilesController { } } + + } diff --git a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java index 7704b3b..ec51c28 100644 --- a/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java +++ b/java/src/main/java/com/yfd/platform/modules/experimentalData/service/ITsFilesService.java @@ -109,7 +109,7 @@ public interface ITsFilesService extends IService { * TsFiles 文档内容 * 返回值说明: com.yfd.platform.config.ResponseResult 返回新增成功或者失败 ***********************************/ - ResponseResult addTsFile(TsFiles tsFiles); + ResponseResult addTsFile(TsFiles tsFiles) throws IOException; /********************************** * 用途说明: 查询实验数据管理文件夹 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 dbb6896..6c779b8 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 @@ -5,6 +5,7 @@ import cn.hutool.core.collection.CollUtil; import java.nio.charset.StandardCharsets; import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; 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.GzipCompressorOutputStream; import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -75,10 +77,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; +import java.util.zip.*; import org.apache.commons.codec.binary.Hex; @@ -472,35 +471,68 @@ public class TsFilesServiceImpl extends ServiceImpl impl ***********************************/ @Override @Transactional(rollbackFor = Exception.class)// 添加事务注解,遇到异常时回滚 - public ResponseResult addTsFile(TsFiles tsFiles) { + public ResponseResult addTsFile(TsFiles tsFiles) throws IOException { - // 校验文件名是否包含非法字符 - String fileName = tsFiles.getFileName(); - if (containsInvalidCharacters(fileName)) { - return ResponseResult.error("文件名包含非法字符(<>:\"/\\|?*)!"); - } + if (tsFiles.getIsFile().equals("FILE")) { + StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); + String basePath = config.getValue() + tsFiles.getWorkPath(); + // 拼接完整文件路径 - //上传人是当前登录人 - 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"); - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("node_id", tsFiles.getNodeId()); - queryWrapper.eq("task_id", tsFiles.getTaskId()); - queryWrapper.eq("parent_id", tsFiles.getParentId()); - queryWrapper.eq("file_name", tsFiles.getFileName()); - int count = tsFilesMapper.selectCount(queryWrapper); - // 大于0说明 区域名称重复 - if (count > 0) { - return ResponseResult.error("文件夹名称已存在!"); - } + // 拼接完整的文件路径 + 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(); + if (containsInvalidCharacters(fileName)) { + return ResponseResult.error("文件名包含非法字符(<>:\"/\\|?*)!"); + } + + //上传人是当前登录人 + 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"); + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("node_id", tsFiles.getNodeId()); + queryWrapper.eq("task_id", tsFiles.getTaskId()); + queryWrapper.eq("parent_id", tsFiles.getParentId()); + queryWrapper.eq("file_name", tsFiles.getFileName()); + int count = tsFilesMapper.selectCount(queryWrapper); + // 大于0说明 区域名称重复 + if (count > 0) { + return ResponseResult.error("文件夹名称已存在!"); + } // //判断文件夹是否创建 // AbstractBaseFileService fileService = storageSourceContext.getByStorageKey("local"); @@ -523,28 +555,29 @@ public class TsFilesServiceImpl extends ServiceImpl impl // } - //本地创建文件夹 - NewFolderRequest newFolderRequest = new NewFolderRequest(); - newFolderRequest.setStorageKey("local"); - newFolderRequest.setPath(tsFiles.getWorkPath()); - newFolderRequest.setPassword(null); - newFolderRequest.setName(tsFiles.getFileName()); + //本地创建文件夹 + NewFolderRequest newFolderRequest = new NewFolderRequest(); + newFolderRequest.setStorageKey("local"); + newFolderRequest.setPath(tsFiles.getWorkPath()); + newFolderRequest.setPassword(null); + newFolderRequest.setName(tsFiles.getFileName()); - AbstractBaseFileService fileServicefolder = storageSourceContext.getByStorageKey(newFolderRequest.getStorageKey()); - boolean flagfolder = fileServicefolder.newFolder(newFolderRequest.getPath(), newFolderRequest.getName()); - if (flagfolder) { + 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("表结构和本地都创建文件夹成功"); - return ResponseResult.success(); + int valueAdded = tsFilesMapper.insert(tsFiles); + if (valueAdded == 1) { + LOGGER.info("表结构和本地都创建文件夹成功"); + return ResponseResult.success(); + } else { + LOGGER.error("本地创建文件夹成功,表结构新增失败"); + return ResponseResult.error(); + } } else { - LOGGER.error("本地创建文件夹成功,表结构新增失败"); + LOGGER.error("本地创建文件夹失败"); return ResponseResult.error(); } - } else { - LOGGER.error("本地创建文件夹失败"); - return ResponseResult.error(); } } @@ -1018,88 +1051,83 @@ public class TsFilesServiceImpl extends ServiceImpl impl String zipFileName = compressedName + "." + compressedFormat; // 使用 try-with-resources 确保 zipOut 在使用后关闭 - try (ZipArchiveOutputStream zipOut = new ZipArchiveOutputStream(new FileOutputStream(zipFilePath.toFile()))) { - // 设置压缩级别,可以选择调整 - zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别 - // 调用压缩方法 - Boolean value = compressToSameDirectory(sourceDirs, compressedFormat); - if (value) { - zipOut.finish(); // 确保所有数据都已写入 - //表结构增加 nodeId, String TaskId - TsFiles tsFiles = new TsFiles(); - tsFiles.setTaskId(filesList.get(0).getTaskId()); - tsFiles.setNodeId(filesList.get(0).getNodeId()); - tsFiles.setWorkPath(compressedPath); - tsFiles.setIsFile("FILE"); + // 设置压缩级别,可以选择调整 +// zipOut.setLevel(ZipArchiveOutputStream.STORED); // 或者使用其他的压缩级别 + // 调用压缩方法 + Boolean value = compressToSameDirectory(sourceDirs, compressedFormat); + if (value) { + //表结构增加 nodeId, String TaskId + TsFiles tsFiles = new TsFiles(); + tsFiles.setTaskId(filesList.get(0).getTaskId()); + tsFiles.setNodeId(filesList.get(0).getNodeId()); + tsFiles.setWorkPath(compressedPath); + tsFiles.setIsFile("FILE"); - // 获取文件大小(字节) - long fileSizeInBytes = zipFilePath.toFile().length(); - // 转换为 MB 并保留两位小数 - double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0); - String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 - // 设置文件大小 - tsFiles.setFileSize(fileSizeFormatted); - //tsFiles.setFileSize(String.valueOf(zipFilePath.toFile().length())); - tsFiles.setParentId(parentId); - tsFiles.setBackupPath(""); - tsFiles.setKeywords(""); - tsFiles.setDescription(""); - tsFiles.setFileName(zipFileName); + // 获取文件大小(字节) + long fileSizeInBytes = zipFilePath.toFile().length(); + // 转换为 MB 并保留两位小数 + double fileSizeInMB = fileSizeInBytes / (1024.0 * 1024.0); + String fileSizeFormatted = String.format("%.2f", fileSizeInMB); // 保留两位小数 + // 设置文件大小 + tsFiles.setFileSize(fileSizeFormatted); + //tsFiles.setFileSize(String.valueOf(zipFilePath.toFile().length())); + tsFiles.setParentId(parentId); + tsFiles.setBackupPath(""); + tsFiles.setKeywords(""); + tsFiles.setDescription(""); + tsFiles.setFileName(zipFileName); - //上传人是当前登录人 - 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()); - // 通过 nodeid taskid 名称查询 如果存在就不 新增 - - QueryWrapper queryWrapperTsFiles = new QueryWrapper<>(); - queryWrapperTsFiles.eq("node_id", filesList.get(0).getNodeId()); - queryWrapperTsFiles.eq("task_id", filesList.get(0).getTaskId()); - queryWrapperTsFiles.eq("work_path", compressedPath); - queryWrapperTsFiles.eq("file_name", zipFileName); - queryWrapperTsFiles.eq("parent_id", filesList.get(0).getParentId()); - TsFiles tsFilesdata = tsFilesMapper.selectOne(queryWrapperTsFiles); - //covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 - if ("0".equals(covered)) { - //如果不为空 更新updateTime时间 - if (tsFilesdata != null) { - tsFilesdata.setUpdateTime(currentTime); - tsFilesMapper.updateById(tsFilesdata); - } else { - //如果为空就新增 - int valueAdded = tsFilesMapper.insert(tsFiles); - if (valueAdded == 1) { - returnResult = "压缩成功"; - } else { - returnResult = "压缩失败"; - } - } + //上传人是当前登录人 + 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()); + // 通过 nodeid taskid 名称查询 如果存在就不 新增 + QueryWrapper queryWrapperTsFiles = new QueryWrapper<>(); + queryWrapperTsFiles.eq("node_id", filesList.get(0).getNodeId()); + queryWrapperTsFiles.eq("task_id", filesList.get(0).getTaskId()); + queryWrapperTsFiles.eq("work_path", compressedPath); + queryWrapperTsFiles.eq("file_name", zipFileName); + queryWrapperTsFiles.eq("parent_id", filesList.get(0).getParentId()); + TsFiles tsFilesdata = tsFilesMapper.selectOne(queryWrapperTsFiles); + //covered 是否覆盖 0 覆盖更新updateTime时间 1提示文件存在 + if ("0".equals(covered)) { + //如果不为空 更新updateTime时间 + if (tsFilesdata != null) { + tsFilesdata.setUpdateTime(currentTime); + tsFilesMapper.updateById(tsFilesdata); } else { - // 1提示文件存在 如果不为空返回提示 - if (tsFilesdata != null) { - returnResult = "文件或文件夹已存在!"; + //如果为空就新增 + int valueAdded = tsFilesMapper.insert(tsFiles); + if (valueAdded == 1) { + returnResult = "压缩成功"; } else { - //如果为空就新增 - int valueAdded = tsFilesMapper.insert(tsFiles); - if (valueAdded == 1) { - returnResult = "压缩成功"; - } else { - returnResult = "压缩失败"; - } + returnResult = "压缩失败"; } } - returnResult = "压缩成功"; + } else { - returnResult = "压缩失败"; + // 1提示文件存在 如果不为空返回提示 + if (tsFilesdata != null) { + returnResult = "文件或文件夹已存在!"; + } else { + //如果为空就新增 + int valueAdded = tsFilesMapper.insert(tsFiles); + if (valueAdded == 1) { + returnResult = "压缩成功"; + } else { + returnResult = "压缩失败"; + } + } } - } catch (IOException e) { - e.printStackTrace(); + returnResult = "压缩成功"; + } else { + returnResult = "压缩失败"; } return returnResult; } @@ -1107,91 +1135,20 @@ public class TsFilesServiceImpl extends ServiceImpl impl // ================== 核心方法改造 ================== - /** - * 多路径压缩到各自同级目录 - * @param sourcePaths 要压缩的路径集合 - * @param compressionFormat 前端传入的压缩格式字符串 - * @return 压缩成功返回true,失败返回false - */ - public boolean compressToSameDirectory(List sourcePaths, String compressionFormat) { + + private Boolean compressToSameDirectory(List sourcePaths, String compressedFormat) { try { - // 转换压缩格式 - CompressionFormat format = CompressionFormat.fromString(compressionFormat); - - // 遍历所有路径进行压缩 - for (Path sourcePath : sourcePaths) { - // 验证路径有效性 - validatePath(sourcePath); - - // 生成目标路径 - Path targetPath = generateTargetPath(sourcePath, format); - - // 执行压缩 - if (!compressSinglePath(sourcePath, targetPath, format)) { - return false; // 任一压缩失败则终止 - } + switch (compressedFormat.toUpperCase()) { + case "ZIP": + return compressToZip(sourcePaths); + case "TAR.GZ": + return compressToTarGz(sourcePaths); +// case "7Z": +// return compressTo7z(sourcePaths); + default: + throw new IllegalArgumentException("不支持的压缩格式: " + compressedFormat); } - return true; // 全部压缩成功 - } catch (IllegalArgumentException e) { - System.err.println("格式错误: " + e.getMessage()); - return false; } 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(); return false; } @@ -1199,222 +1156,252 @@ public class TsFilesServiceImpl extends ServiceImpl impl // ================== 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); + + /** + * 压缩多个文件/目录到单个ZIP + */ + public boolean compressToZip(List sourcePaths) { + if (sourcePaths.isEmpty()) { + LOGGER.warn("源路径列表为空,无法压缩"); + return false; + } + + Path zipPath = generateZipPath(sourcePaths.get(0)); + try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipPath))) { + zipOut.setLevel(Deflater.DEFAULT_COMPRESSION); + + for (Path sourcePath : sourcePaths) { + if (Files.isDirectory(sourcePath)) { + addDirectoryToZip(sourcePath, zipOut, sourcePath.getFileName().toString()); + } else { + addFileToZip(sourcePath, zipOut, ""); + } + } + + zipOut.finish(); + return validateZip(zipPath); + } catch (Exception e) { + LOGGER.error("压缩失败", e); + deleteQuietly(zipPath); + return false; } } - private void processZipEntry(Path currentPath, - ZipArchiveOutputStream zipOut, - String parentEntry, - Set processedPaths) throws IOException { - // 获取相对于压缩根的路径 - Path relativePath = getRelativePath(currentPath); + private void addDirectoryToZip(Path dir, ZipOutputStream zipOut, String baseDir) throws IOException { + Files.walkFileTree(dir, new SimpleFileVisitor() { + @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; + } - // 构建条目名称(关键修改) - String entryName = buildZipEntryName(relativePath, parentEntry); + @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; + } + }); + } - Path realPath = currentPath.toRealPath(); + private void addFileToZip(Path file, ZipOutputStream zipOut, String baseDir) throws IOException { + String entryName = baseDir + file.getFileName().toString(); + addFileEntry(file, entryName, zipOut); + } - if (Files.isDirectory(currentPath)) { - if (processedPaths.contains(realPath)) return; - processedPaths.add(realPath); + 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); + } } - if (Files.isDirectory(currentPath)) { - handleZipDirectory(zipOut, entryName); - processZipChildren(currentPath, zipOut, entryName, processedPaths); - } else { - addFileToZip(currentPath, zipOut, entryName); + 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 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; } } - // ================== 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 void deleteQuietly(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException ignored) {} } /** - * 获取相对于压缩根的路径 + * 生成压缩文件路径(去除源文件扩展名) */ - private Path getRelativePath(Path absolutePath) { - // 示例:如果压缩根是 /data/1.txt,则返回空路径 - // 如果是压缩目录 /data/333,则返回相对路径 - // 需要根据实际压缩根路径调整实现 - return absolutePath.getFileName(); + private Path getCompressedFilePath(Path sourcePath, String extension) { + String fileName = sourcePath.getFileName().toString(); + String baseName = fileName.contains(".") + ? fileName.substring(0, fileName.lastIndexOf('.')) + : fileName; + return sourcePath.resolveSibling(baseName + "." + extension); } - 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); - } + + + + + + // ================== 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)) { + + 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); + + if (!isDir) { + try (InputStream input = Files.newInputStream(filePath)) { + IOUtils.copy(input, tarOut); + } + } + + tarOut.closeArchiveEntry(); + }); + + tarOut.finish(); + } catch (Exception e) { + success = false; + e.printStackTrace(); } } + return success; } // ================== 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 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 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 interface EntryProcessor { + void process(String entryName, boolean isDir) throws IOException; } - private String buildTarEntryName(Path path, String parentEntry) { - return parentEntry.isEmpty() ? - path.getFileName().toString() : - parentEntry + "/" + path.getFileName(); + private void processEntries(Path rootPath, EntryProcessor processor) throws IOException { + Files.walkFileTree(rootPath, new SimpleFileVisitor() { + @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; + } + + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + String entryName = rootPath.relativize(file).toString().replace("\\", "/"); + 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) - throws IOException { + // ================== ZIP条目添加方法 ================== + private void addZipDirectoryEntry(ZipArchiveOutputStream zipOut, String entryName) throws IOException { ZipArchiveEntry entry = new ZipArchiveEntry(entryName); - 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()); + private void addZipFileEntry(ZipArchiveOutputStream zipOut, Path filePath, String entryName) throws IOException { + ZipArchiveEntry entry = new ZipArchiveEntry(filePath.toFile(), entryName); + entry.setSize(Files.size(filePath)); + entry.setTime(Files.getLastModifiedTime(filePath).toMillis()); zipOut.putArchiveEntry(entry); - try (InputStream input = Files.newInputStream(file)) { + try (InputStream input = Files.newInputStream(filePath)) { IOUtils.copy(input, zipOut); } zipOut.closeArchiveEntry(); } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /*************************************解压缩*******************************************/ /********************************** @@ -1442,8 +1429,12 @@ public class TsFilesServiceImpl extends ServiceImpl impl // // 4. 构建解压目标路径 Path destRoot; if (hasFolder) { + // 动态去掉 .zip 后缀(仅用于路径构建) + String folderName = zipName.toLowerCase().endsWith(".zip") + ? zipName.substring(0, zipName.length() - 4) + : zipName; // 如果有文件夹,创建子目录(如 E:/yun/333/222/) - destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath, zipName); + destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath, folderName); } else { // 如果只有文件,直接解压到目标目录(如 E:/yun/333/) destRoot = Paths.get(storageSourceConfig.getValue(), decompressionPath); @@ -1655,17 +1646,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - - - - - - - - - - - // 新增:统一解压入口方法(保持原有逻辑结构) private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException { String fileName = zipFilePath.getFileName().toString(); @@ -1700,7 +1680,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - // 保持原有ZIP判断方法不变 private boolean hasFolderInZip(Path zipFilePath) throws IOException { try (ZipFile zipFile = new ZipFile(zipFilePath.toFile())) { @@ -1738,81 +1717,69 @@ public class TsFilesServiceImpl extends ServiceImpl impl } } + // 保持原有解压逻辑结构的ZIP实现 // 修改后的ZIP解压方法(修复解压问题) private File unzipFile(Path sourcePath, String baseDir) throws IOException { + // 创建目标目录 Path destRoot = Paths.get(baseDir); Files.createDirectories(destRoot); - // 关键修复1:使用与压缩时相同的字符集 try (ZipInputStream zis = new ZipInputStream( - Files.newInputStream(sourcePath), - StandardCharsets.UTF_8)) { - - // 关键修复2:记录已处理路径防止重复 - Set processedEntries = new HashSet<>(); + Files.newInputStream(sourcePath), StandardCharsets.UTF_8)) { ZipEntry entry; while ((entry = zis.getNextEntry()) != null) { - String entryName = entry.getName(); - - // 过滤系统文件(与压缩时逻辑对应) - if (entryName.startsWith("__MACOSX/")) { - zis.closeEntry(); - continue; - } - - // 关键修复3:路径标准化处理 - entryName = entryName.replace("\\", "/"); // 统一分隔符 - if (processedEntries.contains(entryName)) { - 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); + try { + // 跳过 macOS 系统文件 + if (entry.getName().startsWith("__MACOSX")) { + continue; } - // 关键修复5:使用与压缩时相同的写入方式 - try (BufferedOutputStream bos = new BufferedOutputStream( - Files.newOutputStream(destPath, - StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING))) { + // 标准化路径并处理目录标识 + String entryName = entry.getName() + .replace("\\", "/") + .replaceFirst("^/+", ""); // 去除开头的斜杠 - byte[] buffer = new byte[8192]; - int len; - while ((len = zis.read(buffer)) > 0) { - bos.write(buffer, 0, len); + // 检测是否为目录(兼容以'/'结尾的条目) + boolean isDirectory = entry.isDirectory() || entry.getName().endsWith("/"); + + // 调整路径:去除顶层目录(假设所有文件在单一顶层目录下) + if (entryName.contains("/")) { + int firstSlash = entryName.indexOf('/'); + entryName = entryName.substring(firstSlash + 1); + + // 若处理后名称为空,则跳过顶层目录条目 + if (entryName.isEmpty() && isDirectory) { + continue; } - // 立即刷新到磁盘 - bos.flush(); - LOGGER.info("已解压文件:{} (大小:{} bytes)", - destPath, Files.size(destPath)); } + + Path targetPath = destRoot.resolve(entryName).normalize(); + validatePathSafetya(targetPath, destRoot); + + // 处理目录 + if (isDirectory) { + Files.createDirectories(targetPath); + } + // 处理文件 + else { + if (Files.exists(targetPath)) { + LOGGER.warn("文件已存在,跳过覆盖: {}", targetPath); + continue; + } + // 确保父目录存在 + Files.createDirectories(targetPath.getParent()); + try (OutputStream os = Files.newOutputStream(targetPath)) { + IOUtils.copy(zis, os); + } + } + } finally { + zis.closeEntry(); // 确保每个条目只关闭一次 } - zis.closeEntry(); } } - // 最终验证 - LOGGER.info("解压完成,验证文件结构:"); - Files.walk(destRoot) - .forEach(path -> LOGGER.info("-> {}", path)); - return destRoot.toFile(); } @@ -1828,45 +1795,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl + " 超出根目录 " + 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解压实现(保持相同路径逻辑) @@ -1935,36 +1863,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // /** // * 解析压缩包条目(支持多种格式) // */ @@ -2015,8 +1913,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl // } - - //上面这个方法会覆盖 TODO // private File unzipToTemp(Path zipFilePath, String baseDir) throws IOException { // // 1. 直接构建目标路径(baseDir/zipName) @@ -3835,7 +3731,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl dto.setUrl(null); dto.setType(null); - }else{ + } else { dto.setUrl(fileItemResult.getUrl()); //如果是压缩文件 类型就给zip boolean isValid = hasValidExtension(fileItemResult.getName(), sysDictionaryItems); @@ -3931,8 +3827,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - - // 封装发送数据的逻辑 private void sendData(String token, String[] values, int lineCount) { if (Thread.currentThread().isInterrupted()) { @@ -3980,7 +3874,7 @@ public class TsFilesServiceImpl extends ServiceImpl impl StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); TsFiles tsFiles = tsFilesMapper.selectById(id); // 1. 路径标准化与安全校验 - Path targetPath = validateAndNormalizePath(config.getValue()+tsFiles.getWorkPath()+tsFiles.getFileName()); + Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName()); StringBuilder content = new StringBuilder(); @@ -3995,7 +3889,6 @@ public class TsFilesServiceImpl extends ServiceImpl impl } - /** * 验证路径是否合法并转换为标准化路径 */ @@ -4020,16 +3913,16 @@ public class TsFilesServiceImpl extends ServiceImpl impl /** * 保存文件内容 * - * @param id 文件的id - * @param content 新的文件内容(HTML/文本) + * @param id 文件的id + * @param content 新的文件内容(HTML/文本) * @throws SecurityException 路径非法时抛出 - * @throws IOException 文件写入失败时抛出 + * @throws IOException 文件写入失败时抛出 */ public void saveFileContent(String id, String content) throws SecurityException, IOException { StorageSourceConfig config = storageSourceConfigMapper.selectOne(new QueryWrapper().eq("name", "filePath")); TsFiles tsFiles = tsFilesMapper.selectById(id); // 1. 路径标准化与安全校验 - Path targetPath = validateAndNormalizePath(config.getValue()+tsFiles.getWorkPath()+tsFiles.getFileName()); + Path targetPath = validateAndNormalizePath(config.getValue() + tsFiles.getWorkPath() + tsFiles.getFileName()); // 2. 确保父目录存在 Path parentDir = targetPath.getParent(); if (parentDir != null && !Files.exists(parentDir)) {