From d0b5473068ace8947475a1b6f4530cd1c14e9054 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 29 Jan 2026 11:17:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(knowledge):=20=E6=B7=BB=E5=8A=A0=E7=9F=A5?= =?UTF-8?q?=E8=AF=86=E6=9D=A1=E7=9B=AE=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E5=92=8C=E4=B8=8B=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增文件上传接口支持批量上传知识条目文件 - 实现文件存储路径管理和安全验证机制 - 添加文件下载功能支持知识条目文件导出 - 扩展知识内容类型枚举增加FILE类型 - 扩展知识来源类型枚举增加FILE_UPLOAD类型 - 新增上传请求DTO定义文件验证和元数据配置 - 实现文件上传目录管理和文件名安全处理 - 添加文件扩展名识别和内容类型转换逻辑 --- .../KnowledgeItemApplicationService.java | 213 +++++++++++++++++- .../common/enums/KnowledgeContentType.java | 6 +- .../common/enums/KnowledgeSourceType.java | 6 +- .../dto/UploadKnowledgeItemsRequest.java | 72 ++++++ .../rest/KnowledgeItemController.java | 17 ++ .../Detail/KnowledgeSetDetail.tsx | 62 ++++- .../components/KnowledgeItemEditor.tsx | 208 +++++++++++------ .../knowledge-management.api.ts | 10 + .../knowledge-management.const.tsx | 2 + .../knowledge-management.model.ts | 2 + 10 files changed, 511 insertions(+), 87 deletions(-) create mode 100644 backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UploadKnowledgeItemsRequest.java 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 23dfebe..f87747d 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 @@ -15,6 +15,7 @@ import com.datamate.datamanagement.domain.model.dataset.DatasetFile; import com.datamate.datamanagement.domain.model.dataset.Tag; import com.datamate.datamanagement.domain.model.knowledge.KnowledgeItem; import com.datamate.datamanagement.domain.model.knowledge.KnowledgeSet; +import com.datamate.datamanagement.infrastructure.config.DataManagementProperties; import com.datamate.datamanagement.infrastructure.exception.DataManagementErrorCode; import com.datamate.datamanagement.infrastructure.persistence.mapper.TagMapper; import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository; @@ -27,6 +28,7 @@ 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.datamate.datamanagement.interfaces.dto.UploadKnowledgeItemsRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -37,8 +39,12 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -69,12 +75,16 @@ public class KnowledgeItemApplicationService { 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 static final int MAX_TITLE_LENGTH = 200; + private static final String KNOWLEDGE_ITEM_UPLOAD_DIR = "knowledge-items"; + private static final String DEFAULT_FILE_EXTENSION = "bin"; private final KnowledgeItemRepository knowledgeItemRepository; private final KnowledgeSetRepository knowledgeSetRepository; private final DatasetRepository datasetRepository; private final DatasetFileRepository datasetFileRepository; private final TagMapper tagMapper; + private final DataManagementProperties dataManagementProperties; public KnowledgeItem createKnowledgeItem(String setId, CreateKnowledgeItemRequest request) { KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); @@ -106,6 +116,73 @@ public class KnowledgeItemApplicationService { return knowledgeItem; } + public List uploadKnowledgeItems(String setId, UploadKnowledgeItemsRequest request) { + KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); + BusinessAssert.isTrue(!isReadOnlyStatus(knowledgeSet.getStatus()), + DataManagementErrorCode.KNOWLEDGE_SET_STATUS_ERROR); + + List files = request.getFiles(); + BusinessAssert.isTrue(CollectionUtils.isNotEmpty(files), CommonErrorCode.PARAM_ERROR); + + Path uploadRoot = resolveUploadRootPath(); + Path setDir = uploadRoot.resolve(KNOWLEDGE_ITEM_UPLOAD_DIR).resolve(setId).normalize(); + BusinessAssert.isTrue(setDir.startsWith(uploadRoot), CommonErrorCode.PARAM_ERROR); + createDirectories(setDir); + + boolean singleFile = files.size() == 1; + String overrideTitle = StringUtils.trimToNull(request.getTitle()); + List items = new ArrayList<>(); + + for (MultipartFile file : files) { + BusinessAssert.notNull(file, CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(!file.isEmpty(), CommonErrorCode.PARAM_ERROR); + + String originalName = resolveOriginalFileName(file); + String safeOriginalName = sanitizeFileName(originalName); + if (StringUtils.isBlank(safeOriginalName)) { + safeOriginalName = "file"; + } + + String extension = getFileExtension(safeOriginalName); + String storedName = UUID.randomUUID().toString() + + (StringUtils.isBlank(extension) ? "" : "." + extension); + Path targetPath = setDir.resolve(storedName).normalize(); + BusinessAssert.isTrue(targetPath.startsWith(setDir), CommonErrorCode.PARAM_ERROR); + + saveMultipartFile(file, targetPath); + + KnowledgeItem knowledgeItem = new KnowledgeItem(); + knowledgeItem.setId(UUID.randomUUID().toString()); + knowledgeItem.setSetId(setId); + String title = singleFile && StringUtils.isNotBlank(overrideTitle) + ? overrideTitle + : stripExtension(safeOriginalName); + if (StringUtils.isBlank(title)) { + title = "未命名文件"; + } + knowledgeItem.setTitle(trimToLength(title, MAX_TITLE_LENGTH)); + knowledgeItem.setContent(buildRelativeFilePath(setId, storedName)); + knowledgeItem.setContentType(KnowledgeContentType.FILE); + knowledgeItem.setSourceType(KnowledgeSourceType.FILE_UPLOAD); + knowledgeItem.setSourceFileId(trimToLength(safeOriginalName, MAX_TITLE_LENGTH)); + knowledgeItem.setStatus(request.getStatus() == null ? KnowledgeStatusType.DRAFT : request.getStatus()); + + applyDefaultsFromSet(knowledgeItem, knowledgeSet, request.getTags(), request.getDomain(), + request.getBusinessLine(), request.getOwner(), request.getSensitivity(), + request.getValidFrom(), request.getValidTo(), request.getMetadata()); + + validateEffectivePeriod(knowledgeItem.getValidFrom(), knowledgeItem.getValidTo()); + validatePublishable(knowledgeItem); + + items.add(knowledgeItem); + } + + if (CollectionUtils.isNotEmpty(items)) { + knowledgeItemRepository.saveBatch(items, items.size()); + } + return items; + } + public KnowledgeItem updateKnowledgeItem(String setId, String itemId, UpdateKnowledgeItemRequest request) { KnowledgeSet knowledgeSet = requireKnowledgeSet(setId); KnowledgeItem knowledgeItem = knowledgeItemRepository.getById(itemId); @@ -245,8 +322,7 @@ public class KnowledgeItemApplicationService { String entryName = buildItemEntryName(item, nameCounter); ZipArchiveEntry entry = new ZipArchiveEntry(entryName); zos.putArchiveEntry(entry); - byte[] contentBytes = (item.getContent() == null ? "" : item.getContent()) - .getBytes(StandardCharsets.UTF_8); + byte[] contentBytes = resolveExportContent(item); zos.write(contentBytes); zos.closeArchiveEntry(); } @@ -257,6 +333,126 @@ public class KnowledgeItemApplicationService { } } + @Transactional(readOnly = true) + public void downloadKnowledgeItemFile(String setId, String itemId, HttpServletResponse response) { + BusinessAssert.notNull(response, CommonErrorCode.PARAM_ERROR); + KnowledgeItem knowledgeItem = knowledgeItemRepository.getById(itemId); + BusinessAssert.notNull(knowledgeItem, DataManagementErrorCode.KNOWLEDGE_ITEM_NOT_FOUND); + BusinessAssert.isTrue(Objects.equals(knowledgeItem.getSetId(), setId), CommonErrorCode.PARAM_ERROR); + BusinessAssert.isTrue(knowledgeItem.getContentType() == KnowledgeContentType.FILE + || knowledgeItem.getSourceType() == KnowledgeSourceType.FILE_UPLOAD, + CommonErrorCode.PARAM_ERROR); + + String relativePath = knowledgeItem.getContent(); + BusinessAssert.isTrue(StringUtils.isNotBlank(relativePath), CommonErrorCode.PARAM_ERROR); + Path filePath = resolveKnowledgeItemStoragePath(relativePath); + BusinessAssert.isTrue(Files.exists(filePath) && Files.isRegularFile(filePath), CommonErrorCode.PARAM_ERROR); + + String downloadName = StringUtils.isNotBlank(knowledgeItem.getSourceFileId()) + ? knowledgeItem.getSourceFileId() + : filePath.getFileName().toString(); + + response.setContentType("application/octet-stream"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setHeader(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + URLEncoder.encode(downloadName, StandardCharsets.UTF_8) + "\""); + + try (InputStream inputStream = Files.newInputStream(filePath)) { + inputStream.transferTo(response.getOutputStream()); + response.flushBuffer(); + } catch (IOException e) { + log.error("download knowledge item file error, itemId: {}", itemId, e); + throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); + } + } + + private byte[] resolveExportContent(KnowledgeItem item) { + if (item.getContentType() == KnowledgeContentType.FILE) { + String relativePath = item.getContent(); + BusinessAssert.isTrue(StringUtils.isNotBlank(relativePath), CommonErrorCode.PARAM_ERROR); + Path filePath = resolveKnowledgeItemStoragePath(relativePath); + BusinessAssert.isTrue(Files.exists(filePath) && Files.isRegularFile(filePath), CommonErrorCode.PARAM_ERROR); + try { + return Files.readAllBytes(filePath); + } catch (IOException e) { + log.error("export knowledge item file error, itemId: {}", item.getId(), e); + throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); + } + } + return (item.getContent() == null ? "" : item.getContent()).getBytes(StandardCharsets.UTF_8); + } + + private String resolveExportExtension(KnowledgeItem item) { + if (item.getContentType() == KnowledgeContentType.MARKDOWN) { + return "md"; + } + if (item.getContentType() == KnowledgeContentType.FILE) { + String extension = getFileExtension(item.getSourceFileId()); + if (StringUtils.isBlank(extension)) { + extension = getFileExtensionFromPath(item.getContent()); + } + return StringUtils.isBlank(extension) ? DEFAULT_FILE_EXTENSION : extension; + } + return "txt"; + } + + private String getFileExtensionFromPath(String path) { + if (StringUtils.isBlank(path)) { + return ""; + } + Path fileName = Paths.get(path).getFileName(); + return fileName == null ? "" : getFileExtension(fileName.toString()); + } + + private Path resolveUploadRootPath() { + String uploadDir = dataManagementProperties.getFileStorage().getUploadDir(); + BusinessAssert.isTrue(StringUtils.isNotBlank(uploadDir), CommonErrorCode.PARAM_ERROR); + return Paths.get(uploadDir).toAbsolutePath().normalize(); + } + + private Path resolveKnowledgeItemStoragePath(String relativePath) { + String normalizedRelativePath = relativePath.replace("/", File.separator); + Path root = resolveUploadRootPath(); + Path target = root.resolve(normalizedRelativePath).toAbsolutePath().normalize(); + BusinessAssert.isTrue(target.startsWith(root), CommonErrorCode.PARAM_ERROR); + return target; + } + + private String buildRelativeFilePath(String setId, String storedName) { + String relativePath = Paths.get(KNOWLEDGE_ITEM_UPLOAD_DIR, setId, storedName).toString(); + return relativePath.replace(File.separatorChar, '/'); + } + + private void createDirectories(Path path) { + try { + Files.createDirectories(path); + } catch (IOException e) { + log.error("create knowledge item upload dir error, path: {}", path, e); + throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); + } + } + + private void saveMultipartFile(MultipartFile file, Path targetPath) { + try (InputStream inputStream = file.getInputStream()) { + Files.copy(inputStream, targetPath); + } catch (IOException e) { + log.error("save knowledge item file error, target: {}", targetPath, e); + throw BusinessException.of(SystemErrorCode.FILE_SYSTEM_ERROR); + } + } + + private String resolveOriginalFileName(MultipartFile file) { + String originalName = file.getOriginalFilename(); + if (StringUtils.isBlank(originalName)) { + originalName = file.getName(); + } + if (originalName == null) { + return ""; + } + Path fileName = Paths.get(originalName).getFileName(); + return fileName == null ? originalName : fileName.toString(); + } + private KnowledgeSet requireKnowledgeSet(String setId) { KnowledgeSet knowledgeSet = knowledgeSetRepository.getById(setId); BusinessAssert.notNull(knowledgeSet, DataManagementErrorCode.KNOWLEDGE_SET_NOT_FOUND); @@ -275,7 +471,7 @@ public class KnowledgeItemApplicationService { } baseName = trimToLength(baseName, MAX_FILE_BASE_LENGTH); - String extension = item.getContentType() == KnowledgeContentType.MARKDOWN ? "md" : "txt"; + String extension = resolveExportExtension(item); String baseKey = baseName + "." + extension; int count = nameCounter.getOrDefault(baseKey, 0); nameCounter.put(baseKey, count + 1); @@ -325,6 +521,17 @@ public class KnowledgeItemApplicationService { : KnowledgeContentType.TEXT; } + private String getFileExtension(String fileName) { + if (StringUtils.isBlank(fileName)) { + return ""; + } + int dotIndex = fileName.lastIndexOf('.'); + if (dotIndex <= 0 || dotIndex >= fileName.length() - 1) { + return ""; + } + return fileName.substring(dotIndex + 1); + } + private String getFileExtension(DatasetFile datasetFile) { String fileName = datasetFile.getFileName(); if (StringUtils.isNotBlank(fileName) && fileName.contains(".")) { diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java index bf19b3b..c4ed98c 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeContentType.java @@ -11,5 +11,9 @@ public enum KnowledgeContentType { /** * Markdown */ - MARKDOWN + MARKDOWN, + /** + * 文件(不解析内容) + */ + FILE } diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java index 68408bf..930c85a 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/common/enums/KnowledgeSourceType.java @@ -11,5 +11,9 @@ public enum KnowledgeSourceType { /** * 数据集文件导入 */ - DATASET_FILE + DATASET_FILE, + /** + * 文件上传 + */ + FILE_UPLOAD } diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UploadKnowledgeItemsRequest.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UploadKnowledgeItemsRequest.java new file mode 100644 index 0000000..3dc5403 --- /dev/null +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/dto/UploadKnowledgeItemsRequest.java @@ -0,0 +1,72 @@ +package com.datamate.datamanagement.interfaces.dto; + +import com.datamate.datamanagement.common.enums.KnowledgeStatusType; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import lombok.Getter; +import lombok.Setter; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.web.multipart.MultipartFile; + +import java.time.LocalDate; +import java.util.List; + +/** + * 上传知识条目请求DTO(基于文件) + */ +@Getter +@Setter +public class UploadKnowledgeItemsRequest { + /** + * 上传文件列表 + */ + @NotEmpty(message = "文件列表不能为空") + private List files; + /** + * 标题(单文件时可指定) + */ + @Size(min = 1, max = 200) + private String title; + /** + * 状态 + */ + private KnowledgeStatusType status; + /** + * 标签列表 + */ + private List tags; + /** + * 领域 + */ + @Size(max = 100) + private String domain; + /** + * 业务线 + */ + @Size(max = 100) + private String businessLine; + /** + * 负责人 + */ + @Size(max = 100) + private String owner; + /** + * 有效期开始 + */ + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + private LocalDate validFrom; + /** + * 有效期结束 + */ + @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) + private LocalDate validTo; + /** + * 敏感级别 + */ + @Size(max = 50) + private String sensitivity; + /** + * 扩展元数据 + */ + private String metadata; +} diff --git a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java index 0d02eb2..3bee452 100644 --- a/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java +++ b/backend/services/data-management-service/src/main/java/com/datamate/datamanagement/interfaces/rest/KnowledgeItemController.java @@ -10,10 +10,12 @@ 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.datamate.datamanagement.interfaces.dto.UploadKnowledgeItemsRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -48,12 +50,27 @@ public class KnowledgeItemController { return KnowledgeConverter.INSTANCE.convertItemResponses(items); } + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public List uploadKnowledgeItems(@PathVariable("setId") String setId, + @Valid UploadKnowledgeItemsRequest request) { + List items = knowledgeItemApplicationService.uploadKnowledgeItems(setId, request); + return KnowledgeConverter.INSTANCE.convertItemResponses(items); + } + @IgnoreResponseWrap @GetMapping("/export") public void exportKnowledgeItems(@PathVariable("setId") String setId, HttpServletResponse response) { knowledgeItemApplicationService.exportKnowledgeItems(setId, response); } + @IgnoreResponseWrap + @GetMapping("/{itemId}/file") + public void downloadKnowledgeItemFile(@PathVariable("setId") String setId, + @PathVariable("itemId") String itemId, + HttpServletResponse response) { + knowledgeItemApplicationService.downloadKnowledgeItemFile(setId, itemId, response); + } + @GetMapping("/{itemId}") public KnowledgeItemResponse getKnowledgeItemById(@PathVariable("setId") String setId, @PathVariable("itemId") String itemId) { diff --git a/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx b/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx index 9309444..fe5222b 100644 --- a/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx +++ b/frontend/src/pages/KnowledgeManagement/Detail/KnowledgeSetDetail.tsx @@ -19,12 +19,14 @@ import useFetchData from "@/hooks/useFetchData"; import { deleteKnowledgeItemByIdUsingDelete, deleteKnowledgeSetByIdUsingDelete, + downloadKnowledgeItemFileUsingGet, exportKnowledgeItemsUsingGet, queryKnowledgeItemsUsingGet, queryKnowledgeSetByIdUsingGet, } from "../knowledge-management.api"; import { knowledgeContentTypeOptions, + knowledgeSourceTypeOptions, knowledgeStatusMap, mapKnowledgeItem, KnowledgeItemView, @@ -33,6 +35,7 @@ import { KnowledgeItem, KnowledgeSet, KnowledgeContentType, + KnowledgeSourceType, KnowledgeStatusType, } from "../knowledge-management.model"; import CreateKnowledgeSet from "../components/CreateKnowledgeSet"; @@ -108,6 +111,12 @@ const KnowledgeSetDetail = () => { }; const isReadableItem = (record: KnowledgeItemView) => { + if ( + record.contentType === KnowledgeContentType.FILE || + record.sourceType === KnowledgeSourceType.FILE_UPLOAD + ) { + return false; + } return ( record.contentType === KnowledgeContentType.TEXT || record.contentType === KnowledgeContentType.MARKDOWN @@ -115,6 +124,13 @@ const KnowledgeSetDetail = () => { }; const handleReadItem = async (record: KnowledgeItemView) => { + if ( + record.contentType === KnowledgeContentType.FILE || + record.sourceType === KnowledgeSourceType.FILE_UPLOAD + ) { + message.info("该条目为文件,请使用下载查看"); + return; + } setReadItemId(record.id); setReadTitle(record.title || "知识条目"); @@ -156,6 +172,16 @@ const KnowledgeSetDetail = () => { } }; + const handleDownloadItem = async (record: KnowledgeItemView) => { + if (!id) return; + try { + await downloadKnowledgeItemFileUsingGet(id, record.id, record.sourceFileId); + } catch (error) { + console.error("下载知识条目文件失败", error); + message.error("下载失败,请稍后重试"); + } + }; + const statusMeta = knowledgeSet?.status ? knowledgeStatusMap[knowledgeSet.status] : undefined; @@ -218,6 +244,10 @@ const KnowledgeSetDetail = () => { key: "sourceType", width: 140, ellipsis: true, + render: (sourceType: string) => + knowledgeSourceTypeOptions.find((opt) => opt.value === sourceType)?.label || + sourceType || + "-", }, { title: "更新时间", @@ -232,16 +262,28 @@ const KnowledgeSetDetail = () => { width: 200, render: (_: unknown, record: KnowledgeItemView) => (
- -
- - - {!data?.id && ( - + +
+ 支持多格式文件;多文件将自动拆分为多个条目 +
)} + {data?.id && isFileItem && ( + +
+ + {data.sourceFileId || data.title || "-"} + + +
+
+ )} +
+ + + + + + +
diff --git a/frontend/src/pages/KnowledgeManagement/knowledge-management.api.ts b/frontend/src/pages/KnowledgeManagement/knowledge-management.api.ts index 67765f9..9c68811 100644 --- a/frontend/src/pages/KnowledgeManagement/knowledge-management.api.ts +++ b/frontend/src/pages/KnowledgeManagement/knowledge-management.api.ts @@ -55,6 +55,16 @@ export function deleteKnowledgeItemByIdUsingDelete(setId: string, itemId: string return del(`/api/data-management/knowledge-sets/${setId}/items/${itemId}`); } +// 上传知识条目文件 +export function uploadKnowledgeItemsUsingPost(setId: string, data: FormData) { + return post(`/api/data-management/knowledge-sets/${setId}/items/upload`, data); +} + +// 下载知识条目文件 +export function downloadKnowledgeItemFileUsingGet(setId: string, itemId: string, fileName?: string) { + return download(`/api/data-management/knowledge-sets/${setId}/items/${itemId}/file`, null, fileName || ""); +} + // 导出知识条目 export function exportKnowledgeItemsUsingGet(setId: string) { return download(`/api/data-management/knowledge-sets/${setId}/items/export`); diff --git a/frontend/src/pages/KnowledgeManagement/knowledge-management.const.tsx b/frontend/src/pages/KnowledgeManagement/knowledge-management.const.tsx index e4a6f97..7e5e6c5 100644 --- a/frontend/src/pages/KnowledgeManagement/knowledge-management.const.tsx +++ b/frontend/src/pages/KnowledgeManagement/knowledge-management.const.tsx @@ -57,11 +57,13 @@ export const knowledgeStatusOptions = Object.values(knowledgeStatusMap).map((ite export const knowledgeContentTypeOptions = [ { label: "文本", value: KnowledgeContentType.TEXT }, { label: "Markdown", value: KnowledgeContentType.MARKDOWN }, + { label: "文件", value: KnowledgeContentType.FILE }, ]; export const knowledgeSourceTypeOptions = [ { label: "手工录入", value: KnowledgeSourceType.MANUAL }, { label: "数据集文件导入", value: KnowledgeSourceType.DATASET_FILE }, + { label: "文件上传", value: KnowledgeSourceType.FILE_UPLOAD }, ]; export type KnowledgeSetView = { diff --git a/frontend/src/pages/KnowledgeManagement/knowledge-management.model.ts b/frontend/src/pages/KnowledgeManagement/knowledge-management.model.ts index d9c0b70..8ce22e7 100644 --- a/frontend/src/pages/KnowledgeManagement/knowledge-management.model.ts +++ b/frontend/src/pages/KnowledgeManagement/knowledge-management.model.ts @@ -8,11 +8,13 @@ export enum KnowledgeStatusType { export enum KnowledgeContentType { TEXT = "TEXT", MARKDOWN = "MARKDOWN", + FILE = "FILE", } export enum KnowledgeSourceType { MANUAL = "MANUAL", DATASET_FILE = "DATASET_FILE", + FILE_UPLOAD = "FILE_UPLOAD", } export interface KnowledgeTag {