diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/model/dto/BrandCrawlResultFileDto.java b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/model/dto/BrandCrawlResultFileDto.java index 79804db..4aed94b 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/model/dto/BrandCrawlResultFileDto.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/model/dto/BrandCrawlResultFileDto.java @@ -19,7 +19,7 @@ public class BrandCrawlResultFileDto { @Schema(description = "原始文件名。为空时会回退使用任务创建时记录的 originalFilename。") private String originalFilename; - @Schema(description = "相对路径。文件夹上传场景下用于生成 zip 时恢复目录层级。") + @Schema(description = "相对路径。创建任务时可传;当前仅用于记录来源信息,最终 zip 不再恢复目录层级。") private String relativePath; @Schema(description = "主 sheet 名称。为空时默认输出为 Sheet1。") diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java index 5b5192c..db1f4ff 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/brand/service/BrandTaskService.java @@ -267,10 +267,11 @@ public class BrandTaskService { if (cachedFile == null) { throw new BusinessException("缺少原始缓存数据: " + resultFile.getFileUrl()); } - String originalFilename = blankToDefault(resultFile.getOriginalFilename(), sourceFile.getOriginalFilename()); + File sourceLocalFile = resolveSourceFile(sourceFile); + String originalFilename = resolveOriginalFilename(sourceFile, sourceLocalFile); File outputFile = buildNamedOutputFile(outputDir, buildResultFilename(originalFilename)); writeBrandWorkbook(outputFile, request.getStrategy(), cachedFile, resultFile); - outputEntries.add(new OutputEntry(blankToNull(resultFile.getRelativePath()), outputFile)); + outputEntries.add(new OutputEntry(sourceLocalFile, originalFilename, outputFile, outputFile.getName())); } brandTaskProgressCacheService.updatePhase(taskId, BrandTaskProgressCacheService.PHASE_UPLOADING, finishedCount, totalCount); @@ -830,43 +831,26 @@ public class BrandTaskService { } List urls = new ArrayList<>(); for (OutputEntry entry : entries) { - String objectKey = ossStorageService.uploadResultFile(entry.file(), "BRAND"); + String objectKey = ossStorageService.uploadResultFile(entry.resultFile(), "BRAND"); urls.add(ossStorageService.generateDownloadUrl(objectKey)); } Map result = new LinkedHashMap<>(); result.put("urls", urls); - if (entries.size() > 1) { - File zipFile = packageAsZip(taskId, entries); - String zipObjectKey = ossStorageService.uploadResultFile(zipFile, "BRAND"); - result.put("zip_url", ossStorageService.generateDownloadUrl(zipObjectKey)); - } + File zipFile = packageAsZip(taskId, entries); + String zipObjectKey = ossStorageService.uploadResultFile(zipFile, "BRAND"); + result.put("zip_url", ossStorageService.generateDownloadUrl(zipObjectKey)); return result; } private File packageAsZip(Long taskId, List entries) throws IOException { File zipDir = FileUtil.mkdir(FileUtil.file(storageProperties.getLocalTempDir(), "brand-result", String.valueOf(taskId))); File zipFile = buildNamedOutputFile(zipDir, "brand_task_" + taskId + ".zip"); + Set zipEntryNames = new LinkedHashSet<>(); try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) { byte[] buffer = new byte[8192]; for (OutputEntry entry : entries) { - String relativePath = blankToNull(entry.relativePath()); - String entryName; - if (relativePath != null) { - String relativeParent = relativePath.replace('\\', '/'); - int slashIndex = relativeParent.lastIndexOf('/'); - String prefix = slashIndex >= 0 ? relativeParent.substring(0, slashIndex + 1) : ""; - entryName = prefix + entry.file().getName(); - } else { - entryName = entry.file().getName(); - } - zos.putNextEntry(new ZipEntry(entryName)); - try (FileInputStream inputStream = new FileInputStream(entry.file())) { - int len; - while ((len = inputStream.read(buffer)) > 0) { - zos.write(buffer, 0, len); - } - } - zos.closeEntry(); + writeZipEntry(zos, buffer, entry.sourceFile(), buildSourceZipEntry(entry.sourceFilename()), zipEntryNames); + writeZipEntry(zos, buffer, entry.resultFile(), buildResultZipEntry(entry.resultFilename()), zipEntryNames); } } return zipFile; @@ -890,6 +874,41 @@ public class BrandTaskService { } } + private void writeZipEntry(ZipOutputStream zos, + byte[] buffer, + File file, + String entryName, + Set zipEntryNames) throws IOException { + if (!zipEntryNames.add(entryName)) { + throw new BusinessException("压缩包中存在重复文件名: " + entryName); + } + zos.putNextEntry(new ZipEntry(entryName)); + try (FileInputStream inputStream = new FileInputStream(file)) { + int len; + while ((len = inputStream.read(buffer)) > 0) { + zos.write(buffer, 0, len); + } + } + zos.closeEntry(); + } + + private String buildSourceZipEntry(String sourceFilename) { + return "源文件/" + normalizeZipFilename(sourceFilename); + } + + private String buildResultZipEntry(String resultFilename) { + return "结果文件/" + normalizeZipFilename(resultFilename); + } + + private String normalizeZipFilename(String filename) { + String normalized = blankToDefault(filename, "brand.xlsx").replace('\\', '/'); + int slashIndex = normalized.lastIndexOf('/'); + if (slashIndex >= 0) { + normalized = normalized.substring(slashIndex + 1); + } + return normalized; + } + private String buildResultFilename(String originalFilename) { String sourceName = blankToDefault(originalFilename, "brand.xlsx"); return FileUtil.mainName(sourceName) + "_result.xlsx"; @@ -1020,6 +1039,6 @@ public class BrandTaskService { List uniqueBrands) { } - private record OutputEntry(String relativePath, File file) { + private record OutputEntry(File sourceFile, String sourceFilename, File resultFile, String resultFilename) { } }