From 6835511f5ad52ad0ef9bb3afcc2361a64901ab40 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 26 Jan 2026 11:15:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(data-management):=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=9F=A5=E8=AF=86=E9=A1=B9=E5=AF=BC=E5=87=BA=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E4=B8=BAZIP=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将导出文件格式从JSON改为ZIP压缩包 - 使用ZipArchiveOutputStream实现ZIP文件创建 - 为每个知识项创建独立的文件条目 - 添加文件名规范化和长度限制逻辑 - 实现重复文件名的索引编号处理 - 移除Jackson ObjectMapper依赖引入 - 更新响应头内容类型为application/zip --- .../KnowledgeItemApplicationService.java | 71 +++++++++++++++++-- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java index 9d53c89..23dfebe 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/application/KnowledgeItemApplicationService.java @@ -27,12 +27,13 @@ import com.datamate.datamanagement.interfaces.dto.ImportKnowledgeItemsRequest; import com.datamate.datamanagement.interfaces.dto.KnowledgeItemPagingQuery; import com.datamate.datamanagement.interfaces.dto.KnowledgeItemResponse; import com.datamate.datamanagement.interfaces.dto.UpdateKnowledgeItemRequest; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; +import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -46,8 +47,10 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -63,8 +66,9 @@ public class KnowledgeItemApplicationService { private static final Set SUPPORTED_TEXT_EXTENSIONS = Set.of("txt", "md", "markdown"); private static final DateTimeFormatter EXPORT_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); private static final String EXPORT_FILE_PREFIX = "knowledge_set_"; - private static final String EXPORT_FILE_SUFFIX = ".json"; - private static final String EXPORT_CONTENT_TYPE = "application/json"; + private static final String EXPORT_FILE_SUFFIX = ".zip"; + private static final String EXPORT_CONTENT_TYPE = "application/zip"; + private static final int MAX_FILE_BASE_LENGTH = 120; private final KnowledgeItemRepository knowledgeItemRepository; private final KnowledgeSetRepository knowledgeSetRepository; @@ -228,16 +232,25 @@ public class KnowledgeItemApplicationService { BusinessAssert.notNull(response, CommonErrorCode.PARAM_ERROR); KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); List items = knowledgeItemRepository.findAllBySetId(setId); - List responses = KnowledgeConverter.INSTANCE.convertItemResponses(items); response.setContentType(EXPORT_CONTENT_TYPE); response.setCharacterEncoding(StandardCharsets.UTF_8.name()); response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + buildExportFileName(knowledgeSet.getId()) + "\""); - ObjectMapper objectMapper = new ObjectMapper(); - try { - objectMapper.writeValue(response.getOutputStream(), responses); + try (ZipArchiveOutputStream zos = new ZipArchiveOutputStream(response.getOutputStream())) { + zos.setEncoding(StandardCharsets.UTF_8.name()); + Map nameCounter = new HashMap<>(); + for (KnowledgeItem item : items) { + String entryName = buildItemEntryName(item, nameCounter); + ZipArchiveEntry entry = new ZipArchiveEntry(entryName); + zos.putArchiveEntry(entry); + byte[] contentBytes = (item.getContent() == null ? "" : item.getContent()) + .getBytes(StandardCharsets.UTF_8); + zos.write(contentBytes); + zos.closeArchiveEntry(); + } + zos.finish(); } catch (IOException e) { log.error("export knowledge items error, setId: {}", setId, e); throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); @@ -254,6 +267,50 @@ public class KnowledgeItemApplicationService { return EXPORT_FILE_PREFIX + setId + "_" + LocalDateTime.now().format(EXPORT_TIME_FORMATTER) + EXPORT_FILE_SUFFIX; } + private String buildItemEntryName(KnowledgeItem item, Map nameCounter) { + String rawTitle = StringUtils.isNotBlank(item.getTitle()) ? item.getTitle() : "item-" + item.getId(); + String baseName = sanitizeFileName(rawTitle); + if (StringUtils.isBlank(baseName)) { + baseName = "item-" + item.getId(); + } + baseName = trimToLength(baseName, MAX_FILE_BASE_LENGTH); + + String extension = item.getContentType() == KnowledgeContentType.MARKDOWN ? "md" : "txt"; + String baseKey = baseName + "." + extension; + int count = nameCounter.getOrDefault(baseKey, 0); + nameCounter.put(baseKey, count + 1); + if (count == 0) { + return baseKey; + } + return buildIndexedFileName(baseName, extension, count); + } + + private String buildIndexedFileName(String baseName, String extension, int index) { + String suffix = "_" + index; + int maxBaseLength = Math.max(1, MAX_FILE_BASE_LENGTH - suffix.length()); + String safeBase = trimToLength(baseName, maxBaseLength); + return safeBase + suffix + "." + extension; + } + + private String sanitizeFileName(String name) { + if (name == null) { + return ""; + } + String normalized = name.replaceAll("[\\\\/:*?\"<>|]", "_"); + normalized = normalized.replaceAll("\\s+", " ").trim(); + return normalized; + } + + private String trimToLength(String value, int maxLength) { + if (value == null) { + return ""; + } + if (value.length() <= maxLength) { + return value; + } + return value.substring(0, maxLength); + } + private KnowledgeContentType resolveContentType(DatasetFile datasetFile) { String extension = getFileExtension(datasetFile); if (StringUtils.isBlank(extension)) {