feat: 异步同步知识库分块检索索引

This commit is contained in:
2026-09-04 11:32:52 +08:00
parent 1e59063c37
commit 0f126ad489
38 changed files with 2628 additions and 231 deletions

View File

@@ -1,10 +1,6 @@
package tech.easyflow.publicapi.controller;
import cn.hutool.core.io.IoUtil;
import com.easyagents.core.model.embedding.EmbeddingModel;
import com.easyagents.core.store.DocumentStore;
import com.easyagents.core.store.StoreOptions;
import com.easyagents.core.store.StoreResult;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
@@ -18,11 +14,15 @@ 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.DocumentChunkDeleteResult;
import tech.easyflow.ai.dto.DocumentChunkAsyncUpdateResult;
import tech.easyflow.ai.dto.DocumentChunkSyncRetryRequest;
import tech.easyflow.ai.dto.DocumentChunkSyncStatus;
import tech.easyflow.ai.dto.DocumentChunkSyncStatusRequest;
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.KnowledgeApiPermissionScope;
import tech.easyflow.ai.rag.KnowledgeRetrievalModes;
import tech.easyflow.ai.rag.KnowledgeRetrievalRequest;
@@ -33,9 +33,7 @@ import tech.easyflow.ai.service.FaqCategoryService;
import tech.easyflow.ai.service.FaqItemService;
import tech.easyflow.ai.service.KnowledgeShareAuditService;
import tech.easyflow.ai.service.KnowledgeSharePermissionService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.impl.KnowledgeSharePermissionServiceImpl;
import tech.easyflow.ai.support.DocumentStoreLifecycleSupport;
import tech.easyflow.ai.vo.FaqImportResultVo;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.filestorage.FileStorageService;
@@ -81,8 +79,6 @@ public class PublicKnowledgeShareController {
private FaqItemService faqItemService;
@Resource
private FaqCategoryService faqCategoryService;
@Resource
private ModelService modelService;
@Resource(name = "default")
private FileStorageService fileStorageService;
@@ -351,37 +347,20 @@ public class PublicKnowledgeShareController {
public Result<?> updateDocumentChunk(
@RequestHeader("ApiKey") String apiKey,
@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
@JsonBody DocumentChunk documentChunk,
@JsonBody(required = true, skipConvertError = false)
DocumentChunk documentChunk,
HttpServletRequest request
) {
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
requireDocumentKnowledge(knowledgeId);
DocumentChunk current = requireDocumentChunk(documentChunk.getId(), knowledgeId);
boolean success = documentChunkService.updateById(documentChunk);
if (success) {
DocumentCollection knowledge = documentCollectionService.getById(knowledgeId);
DocumentStore documentStore = knowledge.toDocumentStore();
if (documentStore == null) {
return Result.fail(2, "知识库没有配置向量库");
}
try {
Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId());
if (model == null) {
return Result.fail(3, "知识库没有配置向量模型");
}
EmbeddingModel embeddingModel = model.toEmbeddingModel();
documentStore.setEmbeddingModel(embeddingModel);
StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection());
com.easyagents.core.document.Document doc = com.easyagents.core.document.Document.of(documentChunk.getContent());
doc.setId(current.getId());
StoreResult result = documentStore.update(doc, options);
audit(apiKey, "API更新文档 Chunk", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", documentChunk.getId()));
return Result.ok(result);
} finally {
DocumentStoreLifecycleSupport.closeQuietly(documentStore);
}
}
return Result.ok(false);
DocumentChunk updated = documentChunkService.updateContent(
knowledgeId,
current.getId(),
documentChunk.getContent()
);
audit(apiKey, "API更新文档 Chunk", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", documentChunk.getId()));
return Result.ok(DocumentChunkAsyncUpdateResult.from(updated));
}
/**
@@ -397,25 +376,51 @@ public class PublicKnowledgeShareController {
assertApiShare(apiKey, request.getRequestURI(), knowledgeId, KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
requireDocumentKnowledge(knowledgeId);
requireDocumentChunk(chunkId, knowledgeId);
DocumentCollection knowledge = documentCollectionService.getById(knowledgeId);
DocumentStore documentStore = knowledge.toDocumentStore();
if (documentStore == null) {
return Result.fail(2, "知识库没有配置向量库");
}
try {
Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId());
if (model == null) {
return Result.fail(3, "知识库没有配置向量模型");
}
documentStore.setEmbeddingModel(model.toEmbeddingModel());
StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection());
documentStore.delete(Collections.singletonList(chunkId), options);
documentChunkService.removeById(chunkId);
audit(apiKey, "API删除文档 Chunk", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", chunkId));
return Result.ok(true);
} finally {
DocumentStoreLifecycleSupport.closeQuietly(documentStore);
DocumentChunkDeleteResult removed = documentChunkService.deleteChunk(
knowledgeId,
chunkId
);
audit(apiKey, "API删除文档 Chunk", "KNOWLEDGE_API_SHARE_WRITE", request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", chunkId));
return Result.ok(removed != null);
}
@PostMapping("/documentChunk/syncStatus")
public Result<List<DocumentChunkSyncStatus>> documentChunkSyncStatus(
@RequestHeader("ApiKey") String apiKey,
@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
@JsonBody(required = true, skipConvertError = false)
DocumentChunkSyncStatusRequest statusRequest,
HttpServletRequest request
) {
assertApiShare(apiKey, request.getRequestURI(), knowledgeId,
KnowledgeApiPermissionScope.KNOWLEDGE_READ.name());
requireDocumentKnowledge(knowledgeId);
requireDocument(statusRequest.getDocumentId(), knowledgeId);
return Result.ok(documentChunkService.listIndexSyncStatus(
knowledgeId, statusRequest.getDocumentId(), statusRequest.getIds()
));
}
@PostMapping("/documentChunk/retrySync")
public Result<DocumentChunk> retryDocumentChunkSync(
@RequestHeader("ApiKey") String apiKey,
@JsonBody(value = "knowledgeId", required = true) BigInteger knowledgeId,
@JsonBody(required = true, skipConvertError = false)
DocumentChunkSyncRetryRequest retryRequest,
HttpServletRequest request
) {
assertApiShare(apiKey, request.getRequestURI(), knowledgeId,
KnowledgeApiPermissionScope.KNOWLEDGE_MAINTENANCE.name());
requireDocumentKnowledge(knowledgeId);
if (retryRequest.getIndexSyncVersion() == null) {
throw new BusinessException("同步版本不能为空");
}
DocumentChunk chunk = documentChunkService.retryIndexSync(
knowledgeId, retryRequest.getId(), retryRequest.getIndexSyncVersion()
);
audit(apiKey, "API重试文档 Chunk 索引同步", "KNOWLEDGE_API_SHARE_WRITE",
request.getRequestURI(), Map.of("knowledgeId", knowledgeId, "chunkId", retryRequest.getId()));
return Result.ok(chunk);
}
/**

View File

@@ -66,6 +66,8 @@ public class PublicKnowledgeShareControllerContractTest {
JsonBody chunkBody = chunk.getAnnotation(JsonBody.class);
Assert.assertNotNull(chunkBody);
Assert.assertEquals("", chunkBody.value());
Assert.assertTrue(chunkBody.required());
Assert.assertFalse(chunkBody.skipConvertError());
}
/**