package xiaozhi.modules.knowledge.controller; import java.util.List; import java.util.Map; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springdoc.core.annotations.ParameterObject; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.Result; import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO; import xiaozhi.modules.knowledge.dto.KnowledgeFilesDTO; import xiaozhi.modules.knowledge.dto.document.ChunkDTO; import xiaozhi.modules.knowledge.dto.document.DocumentDTO; import xiaozhi.modules.knowledge.dto.document.RetrievalDTO; import xiaozhi.modules.knowledge.service.KnowledgeBaseService; import xiaozhi.modules.knowledge.service.KnowledgeFilesService; import xiaozhi.modules.security.user.SecurityUser; @AllArgsConstructor @RestController @RequestMapping("/datasets/{dataset_id}") @Tag(name = "知识库文档管理") public class KnowledgeFilesController { private final KnowledgeFilesService knowledgeFilesService; private final KnowledgeBaseService knowledgeBaseService; /** * 验证当前用户是否有权限操作指定知识库 * * @param datasetId 知识库ID */ private void validateKnowledgeBasePermission(String datasetId) { // 获取当前登录用户ID Long currentUserId = SecurityUser.getUserId(); // 获取知识库信息 KnowledgeBaseDTO knowledgeBase = knowledgeBaseService.getByDatasetId(datasetId); // 检查权限:用户只能操作自己创建的知识库 if (knowledgeBase.getCreator() == null || !knowledgeBase.getCreator().equals(currentUserId)) { throw new RenException(ErrorCode.NO_PERMISSION); } } @GetMapping("/documents") @Operation(summary = "分页查询文档列表") @RequiresPermissions("sys:role:normal") public Result> getPageList( @PathVariable("dataset_id") String datasetId, @RequestParam(required = false) String name, @RequestParam(required = false) String status, @RequestParam(required = false, defaultValue = "1") Integer page, @RequestParam(required = false, defaultValue = "10") Integer page_size) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); // 组装参数 KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO(); knowledgeFilesDTO.setDatasetId(datasetId); knowledgeFilesDTO.setName(name); knowledgeFilesDTO.setStatus(status); PageData pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size); return new Result>().ok(pageData); } @GetMapping("/documents/status/{status}") @Operation(summary = "根据状态分页查询文档列表") @RequiresPermissions("sys:role:normal") public Result> getPageListByStatus( @PathVariable("dataset_id") String datasetId, @PathVariable("status") String status, @RequestParam(required = false, defaultValue = "1") Integer page, @RequestParam(required = false, defaultValue = "10") Integer page_size) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); // 组装参数 KnowledgeFilesDTO knowledgeFilesDTO = new KnowledgeFilesDTO(); knowledgeFilesDTO.setDatasetId(datasetId); knowledgeFilesDTO.setStatus(status); PageData pageData = knowledgeFilesService.getPageList(knowledgeFilesDTO, page, page_size); return new Result>().ok(pageData); } @PostMapping("/documents") @Operation(summary = "上传文档到知识库") @RequiresPermissions("sys:role:normal") public Result uploadDocument( @PathVariable("dataset_id") String datasetId, @RequestParam("file") MultipartFile file, @RequestParam(required = false) String name, @RequestParam(required = false) String chunkMethod, @RequestParam(required = false) String metaFields, @RequestParam(required = false) String parserConfig) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); KnowledgeFilesDTO resp = knowledgeFilesService.uploadDocument(datasetId, file, name, metaFields != null ? parseJsonMap(metaFields) : null, chunkMethod, parserConfig != null ? parseJsonMap(parserConfig) : null); return new Result().ok(resp); } @DeleteMapping("/documents") @Operation(summary = "批量删除文档") @RequiresPermissions("sys:role:normal") public Result delete(@PathVariable("dataset_id") String datasetId, @RequestBody DocumentDTO.BatchIdReq req) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); knowledgeFilesService.deleteDocuments(datasetId, req); return new Result<>(); } @DeleteMapping("/documents/{document_id}") @Operation(summary = "删除单个文档") @RequiresPermissions("sys:role:normal") public Result deleteSingle(@PathVariable("dataset_id") String datasetId, @PathVariable("document_id") String documentId) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); DocumentDTO.BatchIdReq req = new DocumentDTO.BatchIdReq(); req.setIds(java.util.Collections.singletonList(documentId)); knowledgeFilesService.deleteDocuments(datasetId, req); return new Result<>(); } @PostMapping("/chunks") @Operation(summary = "解析文档(切块)") @RequiresPermissions("sys:role:normal") public Result parseDocuments(@PathVariable("dataset_id") String datasetId, @RequestBody Map> requestBody) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); List documentIds = requestBody.get("document_ids"); if (documentIds == null || documentIds.isEmpty()) { return new Result().error("document_ids参数不能为空"); } boolean success = knowledgeFilesService.parseDocuments(datasetId, documentIds); if (success) { return new Result(); } else { return new Result().error("文档解析失败,文档可能正在处理中"); } } @GetMapping("/documents/{document_id}/chunks") @Operation(summary = "列出指定文档的切片") @RequiresPermissions("sys:role:normal") public Result listChunks( @PathVariable("dataset_id") String datasetId, @PathVariable("document_id") String documentId, @RequestParam(name = "page", required = false, defaultValue = "1") Integer page, @RequestParam(name = "page_size", required = false, defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keywords, @RequestParam(required = false) String id) { // 验证权限 (内部已包含知识库存在性校验与归属权校验) validateKnowledgeBasePermission(datasetId); // 构建请求对象 ChunkDTO.ListReq req = ChunkDTO.ListReq.builder() .page(page) .pageSize(pageSize) .keywords(keywords) .id(id) .build(); // 调用服务层获取强类型切片列表 ChunkDTO.ListVO result = knowledgeFilesService.listChunks(datasetId, documentId, req); return new Result().ok(result); } @PostMapping("/retrieval-test") @Operation(summary = "召回测试") @RequiresPermissions("sys:role:normal") public Result retrievalTest( @PathVariable("dataset_id") String datasetId, @RequestBody RetrievalDTO.TestReq req) { // 验证知识库权限 validateKnowledgeBasePermission(datasetId); // 业务下沉逻辑:如果未指定知识库ID,则设为当前路径中的 datasetId if (req.getDatasetIds() == null || req.getDatasetIds().isEmpty()) { req.setDatasetIds(java.util.Arrays.asList(datasetId)); } // [Reinforce] 强管控分页参数,防止 RAGFlow 端出现 Negative Slicing 报错 if (req.getPage() == null || req.getPage() < 1) { req.setPage(1); } if (req.getPageSize() == null || req.getPageSize() < 1) { req.setPageSize(100); } // 调用检索服务,返回强类型聚合对象 RetrievalDTO.ResultVO result = knowledgeFilesService.retrievalTest(req); return new Result().ok(result); } /** * 解析JSON字符串为Map对象 */ private Map parseJsonMap(String jsonString) { try { ObjectMapper objectMapper = new ObjectMapper(); return objectMapper.readValue(jsonString, new TypeReference>() { }); } catch (Exception e) { throw new RuntimeException("解析JSON字符串失败: " + jsonString, e); } } }