feat: 完善知识库批量导入与公共 API
- 新增批量异步导入、状态查询、失败重试与中断恢复链路 - 拆分知识库读取、导入、维护权限并完善 Public API 契约 - 补充数据库迁移、管理端交互、接口说明与相关测试
This commit is contained in:
@@ -0,0 +1,326 @@
|
||||
package tech.easyflow.publicapi.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.Part;
|
||||
import org.springframework.http.InvalidMediaTypeException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import tech.easyflow.ai.documentimport.ImportCallerContext;
|
||||
import tech.easyflow.ai.documentimport.ImportCallerType;
|
||||
import tech.easyflow.ai.documentimport.PublicDocumentImportDtos;
|
||||
import tech.easyflow.ai.documentimport.task.KnowledgeImportBatchFacade;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.enums.KnowledgeApiPermissionScope;
|
||||
import tech.easyflow.ai.service.DocumentCollectionService;
|
||||
import tech.easyflow.ai.service.KnowledgeShareAuditService;
|
||||
import tech.easyflow.ai.service.KnowledgeSharePermissionService;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.publicapi.interceptor.PublicApiInterceptor;
|
||||
import tech.easyflow.system.entity.SysApiKey;
|
||||
import tech.easyflow.system.service.SysApiKeyService;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 知识库文档 Public API 批量异步导入接口。
|
||||
*
|
||||
* @author Codex
|
||||
* @since 2026-08-02
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping(
|
||||
value = "/public-api/knowledge-share/document/import/batch",
|
||||
produces = MediaType.APPLICATION_JSON_VALUE
|
||||
)
|
||||
public class PublicKnowledgeDocumentImportController {
|
||||
|
||||
private static final long MAX_METADATA_BYTES = 1024L * 1024L;
|
||||
|
||||
private final SysApiKeyService sysApiKeyService;
|
||||
private final KnowledgeSharePermissionService permissionService;
|
||||
private final KnowledgeShareAuditService auditService;
|
||||
private final DocumentCollectionService documentCollectionService;
|
||||
private final KnowledgeImportBatchFacade importFacade;
|
||||
|
||||
/**
|
||||
* 创建 Public API 批量导入控制器。
|
||||
*
|
||||
* @param sysApiKeyService 访问令牌服务
|
||||
* @param permissionService 知识库权限服务
|
||||
* @param auditService 审计服务
|
||||
* @param documentCollectionService 知识库服务
|
||||
* @param importFacade 批量导入门面
|
||||
*/
|
||||
public PublicKnowledgeDocumentImportController(
|
||||
SysApiKeyService sysApiKeyService,
|
||||
KnowledgeSharePermissionService permissionService,
|
||||
KnowledgeShareAuditService auditService,
|
||||
DocumentCollectionService documentCollectionService,
|
||||
KnowledgeImportBatchFacade importFacade) {
|
||||
this.sysApiKeyService = sysApiKeyService;
|
||||
this.permissionService = permissionService;
|
||||
this.auditService = auditService;
|
||||
this.documentCollectionService = documentCollectionService;
|
||||
this.importFacade = importFacade;
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收多文件并创建异步导入任务。
|
||||
*
|
||||
* @param apiKey 访问令牌
|
||||
* @param metadataPart JSON 元数据 Part
|
||||
* @param files 多个文件 Part
|
||||
* @param servletRequest Servlet 请求
|
||||
* @return HTTP 202 异步任务响应
|
||||
*/
|
||||
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public ResponseEntity<Result<PublicDocumentImportDtos.SubmitResponse>> submit(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestPart("metadata") Part metadataPart,
|
||||
@RequestPart("files") List<MultipartFile> files,
|
||||
HttpServletRequest servletRequest) {
|
||||
PublicDocumentImportDtos.BatchMetadata metadata =
|
||||
parseMetadata(metadataPart);
|
||||
SysApiKey token = resolveAuthenticatedApiKey(servletRequest, apiKey);
|
||||
assertImportPermission(
|
||||
token,
|
||||
servletRequest.getRequestURI(),
|
||||
metadata.getKnowledgeId()
|
||||
);
|
||||
requireDocumentKnowledge(metadata.getKnowledgeId());
|
||||
ImportCallerContext caller =
|
||||
new ImportCallerContext(ImportCallerType.PUBLIC_API, token.getId());
|
||||
PublicDocumentImportDtos.SubmitResponse response =
|
||||
importFacade.submit(caller, metadata, files);
|
||||
audit(
|
||||
token,
|
||||
"API批量导入文档",
|
||||
servletRequest.getRequestURI(),
|
||||
Map.of(
|
||||
"knowledgeId", metadata.getKnowledgeId(),
|
||||
"taskId", response.getTaskId(),
|
||||
"totalCount", response.getTotalCount()
|
||||
)
|
||||
);
|
||||
return ResponseEntity.status(HttpStatus.ACCEPTED).body(Result.ok(response));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询异步导入任务状态。
|
||||
*
|
||||
* @param apiKey 访问令牌
|
||||
* @param taskId 批次任务 ID
|
||||
* @param itemStatus 可选文件状态
|
||||
* @param pageNumber 页码
|
||||
* @param pageSize 每页数量
|
||||
* @param servletRequest Servlet 请求
|
||||
* @return 任务状态
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
public Result<PublicDocumentImportDtos.StatusResponse> status(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger taskId,
|
||||
@RequestParam(required = false) String itemStatus,
|
||||
@RequestParam(defaultValue = "1") long pageNumber,
|
||||
@RequestParam(defaultValue = "20") long pageSize,
|
||||
HttpServletRequest servletRequest) {
|
||||
SysApiKey token = resolveAuthenticatedApiKey(servletRequest, apiKey);
|
||||
ImportCallerContext caller =
|
||||
new ImportCallerContext(ImportCallerType.PUBLIC_API, token.getId());
|
||||
BigInteger knowledgeId =
|
||||
importFacade.getOwnedKnowledgeId(caller, taskId);
|
||||
assertImportPermission(token, servletRequest.getRequestURI(), knowledgeId);
|
||||
PublicDocumentImportDtos.StatusResponse response =
|
||||
importFacade.getStatus(
|
||||
caller,
|
||||
taskId,
|
||||
itemStatus,
|
||||
pageNumber,
|
||||
pageSize
|
||||
);
|
||||
return Result.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对异常任务执行断点重试。
|
||||
*
|
||||
* @param apiKey 访问令牌
|
||||
* @param request 重试请求
|
||||
* @param servletRequest Servlet 请求
|
||||
* @return 重试结果
|
||||
*/
|
||||
@PostMapping(value = "/retry", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
public Result<PublicDocumentImportDtos.RetryResponse> retry(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@JsonBody PublicDocumentImportDtos.RetryRequest request,
|
||||
HttpServletRequest servletRequest) {
|
||||
SysApiKey token = resolveAuthenticatedApiKey(servletRequest, apiKey);
|
||||
ImportCallerContext caller =
|
||||
new ImportCallerContext(ImportCallerType.PUBLIC_API, token.getId());
|
||||
BigInteger taskId = request == null ? null : request.getTaskId();
|
||||
if (taskId == null) {
|
||||
throw new BusinessException("taskId 不能为空");
|
||||
}
|
||||
BigInteger knowledgeId =
|
||||
importFacade.getOwnedKnowledgeId(caller, taskId);
|
||||
assertImportPermission(token, servletRequest.getRequestURI(), knowledgeId);
|
||||
PublicDocumentImportDtos.RetryResponse response =
|
||||
importFacade.retry(caller, request);
|
||||
audit(
|
||||
token,
|
||||
"API重试批量导入任务",
|
||||
servletRequest.getRequestURI(),
|
||||
Map.of(
|
||||
"knowledgeId", knowledgeId,
|
||||
"taskId", response.getTaskId(),
|
||||
"retriedCount", response.getRetriedCount()
|
||||
)
|
||||
);
|
||||
return Result.ok(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并校验 metadata JSON Part。
|
||||
*
|
||||
* @param metadataPart metadata Part
|
||||
* @return 批量元数据
|
||||
*/
|
||||
private PublicDocumentImportDtos.BatchMetadata parseMetadata(
|
||||
Part metadataPart) {
|
||||
if (metadataPart == null || metadataPart.getSize() <= 0) {
|
||||
throw new BusinessException("metadata 不能为空");
|
||||
}
|
||||
if (metadataPart.getSize() > MAX_METADATA_BYTES) {
|
||||
throw new BusinessException(413, 41304, "metadata 不能超过1MiB");
|
||||
}
|
||||
String contentType = metadataPart.getContentType();
|
||||
try {
|
||||
if (contentType == null
|
||||
|| !MediaType.APPLICATION_JSON.includes(
|
||||
MediaType.parseMediaType(contentType))) {
|
||||
throw new BusinessException(415, 41503,
|
||||
"metadata Part 必须使用 application/json");
|
||||
}
|
||||
} catch (InvalidMediaTypeException error) {
|
||||
throw new BusinessException(415, 41503,
|
||||
"metadata Part Content-Type 无效", error);
|
||||
}
|
||||
try {
|
||||
String json = new String(
|
||||
metadataPart.getInputStream().readAllBytes(),
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
PublicDocumentImportDtos.BatchMetadata metadata =
|
||||
JSON.parseObject(
|
||||
json,
|
||||
PublicDocumentImportDtos.BatchMetadata.class
|
||||
);
|
||||
if (metadata == null) {
|
||||
throw new BusinessException("metadata 不能为空");
|
||||
}
|
||||
return metadata;
|
||||
} catch (JSONException error) {
|
||||
throw new BusinessException(400, 40021,
|
||||
"metadata JSON 格式无效", error);
|
||||
} catch (java.io.IOException error) {
|
||||
throw new BusinessException(500, 50023,
|
||||
"读取 metadata 失败", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言访问令牌具有知识导入权限。
|
||||
*
|
||||
* @param token 访问令牌
|
||||
* @param requestUri 请求 URI
|
||||
* @param knowledgeId 知识库 ID
|
||||
*/
|
||||
private void assertImportPermission(SysApiKey token,
|
||||
String requestUri,
|
||||
BigInteger knowledgeId) {
|
||||
permissionService.assertApiShare(
|
||||
token.getId(),
|
||||
requestUri,
|
||||
knowledgeId,
|
||||
KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 复用拦截器已经完成认证的访问令牌。
|
||||
*
|
||||
* @param request Servlet 请求
|
||||
* @param apiKey 访问令牌明文
|
||||
* @return 已认证访问令牌
|
||||
*/
|
||||
private SysApiKey resolveAuthenticatedApiKey(
|
||||
HttpServletRequest request,
|
||||
String apiKey) {
|
||||
Object authenticated =
|
||||
request.getAttribute(
|
||||
PublicApiInterceptor.AUTHENTICATED_API_KEY_ATTRIBUTE
|
||||
);
|
||||
if (authenticated instanceof SysApiKey token) {
|
||||
return token;
|
||||
}
|
||||
// 兼容控制器单测和绕过 MVC 拦截器的内部直接调用。
|
||||
return sysApiKeyService.getSysApiKey(apiKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 断言知识库存在且为文档类型。
|
||||
*
|
||||
* @param knowledgeId 知识库 ID
|
||||
*/
|
||||
private void requireDocumentKnowledge(BigInteger knowledgeId) {
|
||||
DocumentCollection knowledge =
|
||||
knowledgeId == null ? null : documentCollectionService.getById(knowledgeId);
|
||||
if (knowledge == null) {
|
||||
throw new BusinessException(404, 404, "知识库不存在");
|
||||
}
|
||||
if (!knowledge.isDocumentCollection()) {
|
||||
throw new BusinessException("当前知识库类型不支持文档导入");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录不含令牌明文的 Public API 审计。
|
||||
*
|
||||
* @param token 访问令牌
|
||||
* @param actionName 操作名称
|
||||
* @param actionUrl 操作 URI
|
||||
* @param detail 业务详情
|
||||
*/
|
||||
private void audit(SysApiKey token,
|
||||
String actionName,
|
||||
String actionUrl,
|
||||
Map<String, Object> detail) {
|
||||
Map<String, Object> payload = new HashMap<>(detail);
|
||||
payload.put("apiKeyId", token.getId());
|
||||
payload.put("channel", "API");
|
||||
auditService.log(
|
||||
null,
|
||||
actionName,
|
||||
"KNOWLEDGE_API_SHARE_WRITE",
|
||||
actionUrl,
|
||||
payload
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,12 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import tech.easyflow.ai.documentimport.DocumentImportDtos;
|
||||
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
|
||||
import tech.easyflow.ai.entity.Document;
|
||||
import tech.easyflow.ai.entity.DocumentChunk;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
import tech.easyflow.ai.entity.FaqItem;
|
||||
import tech.easyflow.ai.entity.Model;
|
||||
import tech.easyflow.ai.enums.KnowledgeShareActionScope;
|
||||
import tech.easyflow.ai.enums.KnowledgeApiPermissionScope;
|
||||
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
|
||||
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
|
||||
import tech.easyflow.ai.service.DocumentChunkService;
|
||||
@@ -42,6 +41,8 @@ import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.filestorage.FileStorageService;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.common.web.jsonbody.JsonBody;
|
||||
import tech.easyflow.publicapi.dto.PublicKnowledgeDetailResponse;
|
||||
import tech.easyflow.publicapi.dto.PublicKnowledgeSearchResultItem;
|
||||
import tech.easyflow.system.entity.SysApiKey;
|
||||
import tech.easyflow.system.service.SysApiKeyService;
|
||||
|
||||
@@ -89,28 +90,43 @@ public class PublicKnowledgeShareController {
|
||||
* 获取知识库详情。
|
||||
*/
|
||||
@GetMapping("/detail")
|
||||
public Result<DocumentCollection> detail(
|
||||
public Result<PublicKnowledgeDetailResponse> detail(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger knowledgeId,
|
||||
@RequestParam(defaultValue = "1") int pageNumber,
|
||||
@RequestParam(defaultValue = "50") int pageSize,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
validateDocumentPage(pageNumber, pageSize);
|
||||
DocumentCollection knowledge = documentCollectionService.getDetail(knowledgeId.toString());
|
||||
if (knowledge == null) {
|
||||
throw new BusinessException("知识库不存在");
|
||||
}
|
||||
Page<Document> documents = knowledge.isDocumentCollection()
|
||||
? documentService.getDocumentList(
|
||||
knowledgeId.toString(),
|
||||
pageSize,
|
||||
pageNumber,
|
||||
null
|
||||
)
|
||||
: new Page<>(Collections.emptyList(), pageNumber, pageSize, 0L);
|
||||
audit(apiKey, "API读取知识库详情", "KNOWLEDGE_API_SHARE_ACCESS", request.getRequestURI(), Map.of("knowledgeId", knowledgeId));
|
||||
return Result.ok(documentCollectionService.getDetail(knowledgeId.toString()));
|
||||
return Result.ok(new PublicKnowledgeDetailResponse(knowledge, documents));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检索知识库。
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public Result<List<KnowledgeSearchResultItem>> search(
|
||||
public Result<List<PublicKnowledgeSearchResultItem>> search(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger knowledgeId,
|
||||
@RequestParam String keyword,
|
||||
@RequestParam(required = false) String retrievalMode,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.SEARCH.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
KnowledgeRetrievalRequest retrievalRequest = new KnowledgeRetrievalRequest();
|
||||
retrievalRequest.setKnowledgeId(knowledgeId);
|
||||
retrievalRequest.setQuery(keyword);
|
||||
@@ -128,14 +144,19 @@ public class PublicKnowledgeShareController {
|
||||
public Result<Page<Document>> documentPage(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger knowledgeId,
|
||||
@RequestParam(required = false) String title,
|
||||
@RequestParam(required = false) BigInteger documentId,
|
||||
@RequestParam(defaultValue = "10") int pageSize,
|
||||
@RequestParam(defaultValue = "1") int pageNumber,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
return Result.ok(documentService.getDocumentList(knowledgeId.toString(), pageSize, pageNumber, title));
|
||||
return Result.ok(documentService.getDocumentListById(
|
||||
knowledgeId.toString(),
|
||||
pageSize,
|
||||
pageNumber,
|
||||
documentId
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +170,7 @@ public class PublicKnowledgeShareController {
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) throws Exception {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
Document document = requireDocument(documentId, knowledgeId);
|
||||
response.setContentType("application/octet-stream");
|
||||
@@ -169,11 +190,11 @@ public class PublicKnowledgeShareController {
|
||||
@PostMapping("/document/remove")
|
||||
public Result<?> removeDocument(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger knowledgeId,
|
||||
@JsonBody("id") String id,
|
||||
@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
|
||||
@JsonBody(value = "id", required = true) String id,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_DELETE.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
requireDocument(new BigInteger(id), knowledgeId);
|
||||
audit(apiKey, "API删除文档", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "documentId", id));
|
||||
@@ -189,7 +210,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.AnalyzeRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API分析文档导入", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.analyzeImport(request);
|
||||
@@ -204,7 +225,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.PreviewRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API预览文档导入", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.previewImport(request);
|
||||
@@ -219,7 +240,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.CommitRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API提交文档导入", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.commitImport(request);
|
||||
@@ -231,7 +252,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.TaskCreateRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API创建文档导入任务", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.createImportTask(request);
|
||||
@@ -244,7 +265,7 @@ public class PublicKnowledgeShareController {
|
||||
@RequestParam BigInteger taskId,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
Result<DocumentImportDtos.TaskDetailResponse> result = documentService.getImportTaskDetail(taskId);
|
||||
if (result.getData() == null || result.getData().getKnowledgeId() == null
|
||||
@@ -260,7 +281,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.PreviewRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API预览文档分块", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.previewImportTask(request);
|
||||
@@ -272,7 +293,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.TaskStartIndexRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API启动文档向量化", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.startIndexTask(request);
|
||||
@@ -284,7 +305,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.TaskRetryRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API重试文档解析", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.retryParseTask(request);
|
||||
@@ -296,7 +317,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody DocumentImportDtos.TaskRetryRequest request,
|
||||
HttpServletRequest servletRequest
|
||||
) {
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, servletRequest.getRequestURI(), request.getKnowledgeId(), KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireDocumentKnowledge(request.getKnowledgeId());
|
||||
audit(apiKey, "API重试文档向量化", "KNOWLEDGE_API_SHARE_WRITE", servletRequest.getRequestURI(), Map.of("knowledgeId", request.getKnowledgeId()));
|
||||
return documentService.retryIndexTask(request);
|
||||
@@ -314,7 +335,7 @@ public class PublicKnowledgeShareController {
|
||||
@RequestParam(defaultValue = "10") long pageSize,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
requireDocument(documentId, knowledgeId);
|
||||
QueryWrapper wrapper = QueryWrapper.create()
|
||||
@@ -329,11 +350,11 @@ public class PublicKnowledgeShareController {
|
||||
@PostMapping("/documentChunk/update")
|
||||
public Result<?> updateDocumentChunk(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger knowledgeId,
|
||||
@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
|
||||
@JsonBody DocumentChunk documentChunk,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_UPDATE.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
DocumentChunk current = requireDocumentChunk(documentChunk.getId(), knowledgeId);
|
||||
boolean success = documentChunkService.updateById(documentChunk);
|
||||
@@ -369,11 +390,11 @@ public class PublicKnowledgeShareController {
|
||||
@PostMapping("/documentChunk/remove")
|
||||
public Result<?> removeDocumentChunk(
|
||||
@RequestHeader("ApiKey") String apiKey,
|
||||
@RequestParam BigInteger knowledgeId,
|
||||
@JsonBody("id") BigInteger chunkId,
|
||||
@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
|
||||
@JsonBody(value = "id", required = true) BigInteger chunkId,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_DELETE.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
|
||||
requireDocumentKnowledge(knowledgeId);
|
||||
requireDocumentChunk(chunkId, knowledgeId);
|
||||
DocumentCollection knowledge = documentCollectionService.getById(knowledgeId);
|
||||
@@ -410,7 +431,7 @@ public class PublicKnowledgeShareController {
|
||||
@RequestParam(defaultValue = "10") long pageSize,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
requireFaqKnowledge(knowledgeId);
|
||||
faqCategoryService.ensureDefaultCategory(knowledgeId);
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
@@ -447,7 +468,7 @@ public class PublicKnowledgeShareController {
|
||||
@RequestParam String id,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.VIEW.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
requireFaqKnowledge(knowledgeId);
|
||||
FaqItem faqItem = requireFaq(new BigInteger(id), knowledgeId);
|
||||
return Result.ok(faqItem);
|
||||
@@ -462,7 +483,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody FaqItem entity,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), entity.getCollectionId(), KnowledgeShareActionScope.CONTENT_CREATE.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), entity.getCollectionId(), KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
|
||||
requireFaqKnowledge(entity.getCollectionId());
|
||||
audit(apiKey, "API新增FAQ", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", entity.getCollectionId()));
|
||||
return Result.ok(faqItemService.saveFaqItem(entity));
|
||||
@@ -478,7 +499,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody FaqItem entity,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_UPDATE.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
|
||||
requireFaqKnowledge(knowledgeId);
|
||||
requireFaq(entity.getId(), knowledgeId);
|
||||
audit(apiKey, "API更新FAQ", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "faqId", entity.getId()));
|
||||
@@ -495,7 +516,7 @@ public class PublicKnowledgeShareController {
|
||||
@JsonBody("id") BigInteger id,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.CONTENT_DELETE.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
|
||||
requireFaqKnowledge(knowledgeId);
|
||||
requireFaq(id, knowledgeId);
|
||||
audit(apiKey, "API删除FAQ", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "faqId", id));
|
||||
@@ -512,7 +533,7 @@ public class PublicKnowledgeShareController {
|
||||
BigInteger collectionId,
|
||||
HttpServletRequest request
|
||||
) {
|
||||
assertApiShare(apiKey, request.getRequestURI(), collectionId, KnowledgeShareActionScope.IMPORT_EXPORT.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), collectionId, KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireFaqKnowledge(collectionId);
|
||||
audit(apiKey, "API导入FAQ Excel", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", collectionId));
|
||||
return Result.ok(faqItemService.importFromExcel(collectionId, file));
|
||||
@@ -528,7 +549,7 @@ public class PublicKnowledgeShareController {
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) throws Exception {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.IMPORT_EXPORT.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_IMPORT.name());
|
||||
requireFaqKnowledge(knowledgeId);
|
||||
response.setContentType("application/octet-stream");
|
||||
response.setHeader(
|
||||
@@ -550,7 +571,7 @@ public class PublicKnowledgeShareController {
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response
|
||||
) throws Exception {
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeShareActionScope.IMPORT_EXPORT.name());
|
||||
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
|
||||
requireFaqKnowledge(knowledgeId);
|
||||
String fileName = "faq_export_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + ".xlsx";
|
||||
response.setContentType("application/octet-stream");
|
||||
@@ -610,6 +631,22 @@ public class PublicKnowledgeShareController {
|
||||
return knowledge;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验详情接口中的文档分页参数。
|
||||
*
|
||||
* @param pageNumber 页码
|
||||
* @param pageSize 每页条数
|
||||
* @throws BusinessException 页码小于 1 或每页条数不在 1 到 100 之间时抛出
|
||||
*/
|
||||
private void validateDocumentPage(int pageNumber, int pageSize) {
|
||||
if (pageNumber < 1) {
|
||||
throw new BusinessException("pageNumber 必须大于等于 1");
|
||||
}
|
||||
if (pageSize < 1 || pageSize > 100) {
|
||||
throw new BusinessException("pageSize 必须在 1 到 100 之间");
|
||||
}
|
||||
}
|
||||
|
||||
private Document requireDocument(BigInteger documentId, BigInteger knowledgeId) {
|
||||
Document document = documentService.getById(documentId);
|
||||
if (document == null || document.getCollectionId() == null || document.getCollectionId().compareTo(knowledgeId) != 0) {
|
||||
@@ -642,15 +679,38 @@ public class PublicKnowledgeShareController {
|
||||
knowledgeShareAuditService.log(null, actionName, actionType, actionUrl, payload);
|
||||
}
|
||||
|
||||
private List<KnowledgeSearchResultItem> toKnowledgeSearchResult(List<com.easyagents.core.document.Document> documents) {
|
||||
List<KnowledgeSearchResultItem> result = new java.util.ArrayList<>();
|
||||
private List<PublicKnowledgeSearchResultItem> toKnowledgeSearchResult(
|
||||
List<com.easyagents.core.document.Document> documents
|
||||
) {
|
||||
List<PublicKnowledgeSearchResultItem> result = new java.util.ArrayList<>();
|
||||
for (com.easyagents.core.document.Document document : documents) {
|
||||
KnowledgeSearchResultItem item = new KnowledgeSearchResultItem();
|
||||
PublicKnowledgeSearchResultItem item =
|
||||
new PublicKnowledgeSearchResultItem();
|
||||
item.setContent(document.getContent());
|
||||
String resultType =
|
||||
asString(document.getMetadata("resultType"));
|
||||
item.setResultType(resultType);
|
||||
Object renderMarkdown = document.getMetadata("renderMarkdown");
|
||||
item.setRenderMarkdown(renderMarkdown == null ? null : String.valueOf(renderMarkdown));
|
||||
Object sourceFileName = document.getMetadata("sourceFileName");
|
||||
item.setSourceFileName(sourceFileName == null ? null : String.valueOf(sourceFileName));
|
||||
String documentName =
|
||||
sourceFileName == null ? null : String.valueOf(sourceFileName);
|
||||
item.setSourceFileName(documentName);
|
||||
if (DocumentCollection.TYPE_FAQ.equalsIgnoreCase(resultType)) {
|
||||
item.setFaqId(asBigInteger(document.getMetadata("faqId")));
|
||||
item.setQuestion(asString(document.getMetadata("question")));
|
||||
item.setAnswerText(
|
||||
asString(document.getMetadata("answerText"))
|
||||
);
|
||||
item.setCategoryId(
|
||||
asBigInteger(document.getMetadata("categoryId"))
|
||||
);
|
||||
} else {
|
||||
item.setDocumentName(documentName);
|
||||
item.setDocumentId(
|
||||
asBigInteger(document.getMetadata("documentId"))
|
||||
);
|
||||
}
|
||||
item.setScore(document.getScore());
|
||||
Object hitSource = document.getMetadata("hitSource");
|
||||
item.setHitSource(hitSource == null ? null : String.valueOf(hitSource));
|
||||
@@ -661,6 +721,36 @@ public class PublicKnowledgeShareController {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将检索元数据转换为字符串。
|
||||
*
|
||||
* @param value 元数据值
|
||||
* @return 字符串值;原值为空时返回 {@code null}
|
||||
*/
|
||||
private String asString(Object value) {
|
||||
return value == null ? null : String.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将检索元数据转换为大整数 ID。
|
||||
*
|
||||
* @param value 元数据值
|
||||
* @return 大整数 ID;原值为空或格式无效时返回 {@code null}
|
||||
*/
|
||||
private BigInteger asBigInteger(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof BigInteger) {
|
||||
return (BigInteger) value;
|
||||
}
|
||||
try {
|
||||
return new BigInteger(String.valueOf(value));
|
||||
} catch (NumberFormatException ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Double asDouble(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
package tech.easyflow.publicapi.dto;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import tech.easyflow.ai.entity.Document;
|
||||
import tech.easyflow.ai.entity.DocumentCollection;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 公开知识库详情响应。
|
||||
*
|
||||
* <p>知识库原有字段保持在响应顶层,文档摘要通过 {@code documents} 分页返回。</p>
|
||||
*/
|
||||
public class PublicKnowledgeDetailResponse extends DocumentCollection {
|
||||
|
||||
/**
|
||||
* 已上传文档的分页摘要。
|
||||
*/
|
||||
private final Page<DocumentSummary> documents;
|
||||
|
||||
/**
|
||||
* 创建公开知识库详情响应。
|
||||
*
|
||||
* @param knowledge 知识库基本信息
|
||||
* @param documentPage 文档实体分页;FAQ 知识库可传 {@code null}
|
||||
*/
|
||||
public PublicKnowledgeDetailResponse(
|
||||
DocumentCollection knowledge,
|
||||
Page<Document> documentPage
|
||||
) {
|
||||
BeanUtil.copyProperties(knowledge, this);
|
||||
this.documents = toDocumentSummaryPage(documentPage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档分页摘要。
|
||||
*
|
||||
* @return 文档分页摘要
|
||||
*/
|
||||
public Page<DocumentSummary> getDocuments() {
|
||||
return documents;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将文档实体分页映射为公开摘要分页。
|
||||
*
|
||||
* @param source 文档实体分页
|
||||
* @return 不包含内部路径、正文和配置的公开摘要分页
|
||||
*/
|
||||
private static Page<DocumentSummary> toDocumentSummaryPage(Page<Document> source) {
|
||||
if (source == null) {
|
||||
return new Page<>(Collections.emptyList(), 1, 50, 0L);
|
||||
}
|
||||
List<DocumentSummary> records = source.getRecords() == null
|
||||
? Collections.emptyList()
|
||||
: source.getRecords().stream().map(DocumentSummary::new).toList();
|
||||
return new Page<>(
|
||||
records,
|
||||
source.getPageNumber(),
|
||||
source.getPageSize(),
|
||||
source.getTotalRow()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开文档摘要。
|
||||
*/
|
||||
public static class DocumentSummary {
|
||||
|
||||
/**
|
||||
* 文档 ID。
|
||||
*/
|
||||
private final BigInteger id;
|
||||
|
||||
/**
|
||||
* 文档标题。
|
||||
*/
|
||||
private final String title;
|
||||
|
||||
/**
|
||||
* 文档类型。
|
||||
*/
|
||||
private final String documentType;
|
||||
|
||||
/**
|
||||
* 文档内容类型。
|
||||
*/
|
||||
private final String contentType;
|
||||
|
||||
/**
|
||||
* 文档处理状态。
|
||||
*/
|
||||
private final String processStatus;
|
||||
|
||||
/**
|
||||
* 文档分块数。
|
||||
*/
|
||||
private final long chunkCount;
|
||||
|
||||
/**
|
||||
* 处理进度百分比。
|
||||
*/
|
||||
private final Integer progressPercent;
|
||||
|
||||
/**
|
||||
* 创建时间。
|
||||
*/
|
||||
private final Date created;
|
||||
|
||||
/**
|
||||
* 最后修改时间。
|
||||
*/
|
||||
private final Date modified;
|
||||
|
||||
/**
|
||||
* 从文档实体创建公开摘要。
|
||||
*
|
||||
* @param document 文档实体
|
||||
*/
|
||||
public DocumentSummary(Document document) {
|
||||
this.id = document.getId();
|
||||
this.title = document.getTitle();
|
||||
this.documentType = document.getDocumentType();
|
||||
this.contentType = document.getContentType();
|
||||
this.processStatus = document.getProcessStatus();
|
||||
this.chunkCount = document.getDisplayChunkCount();
|
||||
this.progressPercent = document.getProgressPercent();
|
||||
this.created = document.getCreated();
|
||||
this.modified = document.getModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档 ID。
|
||||
*
|
||||
* @return 文档 ID
|
||||
*/
|
||||
public BigInteger getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档标题。
|
||||
*
|
||||
* @return 文档标题
|
||||
*/
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档类型。
|
||||
*
|
||||
* @return 文档类型
|
||||
*/
|
||||
public String getDocumentType() {
|
||||
return documentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档内容类型。
|
||||
*
|
||||
* @return 文档内容类型
|
||||
*/
|
||||
public String getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档处理状态。
|
||||
*
|
||||
* @return 文档处理状态
|
||||
*/
|
||||
public String getProcessStatus() {
|
||||
return processStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档分块数。
|
||||
*
|
||||
* @return 文档分块数
|
||||
*/
|
||||
public long getChunkCount() {
|
||||
return chunkCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理进度百分比。
|
||||
*
|
||||
* @return 处理进度百分比
|
||||
*/
|
||||
public Integer getProgressPercent() {
|
||||
return progressPercent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取创建时间。
|
||||
*
|
||||
* @return 创建时间
|
||||
*/
|
||||
public Date getCreated() {
|
||||
return created;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最后修改时间。
|
||||
*
|
||||
* @return 最后修改时间
|
||||
*/
|
||||
public Date getModified() {
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package tech.easyflow.publicapi.dto;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import tech.easyflow.ai.dto.KnowledgeSearchResultItem;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* 公开知识库检索结果。
|
||||
*
|
||||
* <p>根据命中来源补充文档或 FAQ 标识,便于调用方继续查询对应详情。</p>
|
||||
*/
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public class PublicKnowledgeSearchResultItem extends KnowledgeSearchResultItem {
|
||||
|
||||
/**
|
||||
* 命中来源类型。
|
||||
*/
|
||||
private String resultType;
|
||||
|
||||
/**
|
||||
* 来源文档 ID。
|
||||
*/
|
||||
private BigInteger documentId;
|
||||
|
||||
/**
|
||||
* 来源文档名称。
|
||||
*/
|
||||
private String documentName;
|
||||
|
||||
/**
|
||||
* 来源 FAQ ID。
|
||||
*/
|
||||
private BigInteger faqId;
|
||||
|
||||
/**
|
||||
* 来源 FAQ 问题。
|
||||
*/
|
||||
private String question;
|
||||
|
||||
/**
|
||||
* 来源 FAQ 纯文本答案。
|
||||
*/
|
||||
private String answerText;
|
||||
|
||||
/**
|
||||
* 来源 FAQ 分类 ID。
|
||||
*/
|
||||
private BigInteger categoryId;
|
||||
|
||||
/**
|
||||
* 获取命中来源类型。
|
||||
*
|
||||
* @return {@code DOCUMENT} 或 {@code FAQ}
|
||||
*/
|
||||
public String getResultType() {
|
||||
return resultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置命中来源类型。
|
||||
*
|
||||
* @param resultType 命中来源类型
|
||||
*/
|
||||
public void setResultType(String resultType) {
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源文档 ID。
|
||||
*
|
||||
* @return 来源文档 ID
|
||||
*/
|
||||
public BigInteger getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源文档 ID。
|
||||
*
|
||||
* @param documentId 来源文档 ID
|
||||
*/
|
||||
public void setDocumentId(BigInteger documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源文档名称。
|
||||
*
|
||||
* @return 来源文档名称
|
||||
*/
|
||||
public String getDocumentName() {
|
||||
return documentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源文档名称。
|
||||
*
|
||||
* @param documentName 来源文档名称
|
||||
*/
|
||||
public void setDocumentName(String documentName) {
|
||||
this.documentName = documentName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源 FAQ ID。
|
||||
*
|
||||
* @return 来源 FAQ ID
|
||||
*/
|
||||
public BigInteger getFaqId() {
|
||||
return faqId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源 FAQ ID。
|
||||
*
|
||||
* @param faqId 来源 FAQ ID
|
||||
*/
|
||||
public void setFaqId(BigInteger faqId) {
|
||||
this.faqId = faqId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源 FAQ 问题。
|
||||
*
|
||||
* @return 来源 FAQ 问题
|
||||
*/
|
||||
public String getQuestion() {
|
||||
return question;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源 FAQ 问题。
|
||||
*
|
||||
* @param question 来源 FAQ 问题
|
||||
*/
|
||||
public void setQuestion(String question) {
|
||||
this.question = question;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源 FAQ 纯文本答案。
|
||||
*
|
||||
* @return 来源 FAQ 纯文本答案
|
||||
*/
|
||||
public String getAnswerText() {
|
||||
return answerText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源 FAQ 纯文本答案。
|
||||
*
|
||||
* @param answerText 来源 FAQ 纯文本答案
|
||||
*/
|
||||
public void setAnswerText(String answerText) {
|
||||
this.answerText = answerText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取来源 FAQ 分类 ID。
|
||||
*
|
||||
* @return 来源 FAQ 分类 ID
|
||||
*/
|
||||
public BigInteger getCategoryId() {
|
||||
return categoryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置来源 FAQ 分类 ID。
|
||||
*
|
||||
* @param categoryId 来源 FAQ 分类 ID
|
||||
*/
|
||||
public void setCategoryId(BigInteger categoryId) {
|
||||
this.categoryId = categoryId;
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,21 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import tech.easyflow.common.domain.Result;
|
||||
import tech.easyflow.common.util.ResponseUtil;
|
||||
import tech.easyflow.system.entity.SysApiKey;
|
||||
import tech.easyflow.system.service.SysApiKeyService;
|
||||
|
||||
/**
|
||||
* Public API 访问令牌与接口权限拦截器。
|
||||
*/
|
||||
@Component
|
||||
public class PublicApiInterceptor implements HandlerInterceptor {
|
||||
|
||||
/**
|
||||
* 请求中已完成认证的访问令牌属性名,供后续资源级鉴权复用。
|
||||
*/
|
||||
public static final String AUTHENTICATED_API_KEY_ATTRIBUTE =
|
||||
PublicApiInterceptor.class.getName() + ".authenticatedApiKey";
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(PublicApiInterceptor.class);
|
||||
|
||||
@Resource
|
||||
@@ -31,7 +41,12 @@ public class PublicApiInterceptor implements HandlerInterceptor {
|
||||
ResponseUtil.renderJson(response, failed);
|
||||
return false;
|
||||
}
|
||||
sysApiKeyService.checkApikeyPermission(apiKey, requestURI);
|
||||
SysApiKey authenticatedApiKey =
|
||||
sysApiKeyService.checkApikeyPermission(apiKey, requestURI);
|
||||
request.setAttribute(
|
||||
AUTHENTICATED_API_KEY_ATTRIBUTE,
|
||||
authenticatedApiKey
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user