feat(audit): 补齐摄取管道入口并沉淀完成度审计

This commit is contained in:
2026-06-01 06:04:30 +08:00
parent 1d401c6841
commit c8245ba0d6
10 changed files with 505 additions and 2 deletions

View File

@@ -1,14 +1,18 @@
package com.bruce.rag.controller;
import com.bruce.common.domain.model.RequestResult;
import com.bruce.rag.dto.request.IngestionRunCreateRequest;
import com.bruce.rag.service.IIngestionRunService;
import com.bruce.rag.vo.IngestionRunVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -25,6 +29,16 @@ public class IngestionRunController {
private final IIngestionRunService ingestionRunService;
@Operation(summary = "创建文件解析管道聚合视图")
@PostMapping
public RequestResult<IngestionRunVO> create(@Valid @RequestBody IngestionRunCreateRequest request) {
log.info("文件解析管道创建开始storeId={}, documentId={}", request.getStoreId(), request.getDocumentId());
IngestionRunVO view = ingestionRunService.createRun(request);
log.info("文件解析管道创建结束runId={}, storeId={}, documentId={}",
view.getRunId(), view.getStoreId(), view.getDocumentId());
return RequestResult.success(view);
}
@Operation(summary = "查询文件解析管道聚合视图")
@GetMapping("/{runId}")
public RequestResult<IngestionRunVO> detail(@PathVariable("runId") String runId,

View File

@@ -0,0 +1,24 @@
package com.bruce.rag.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 文件解析管道创建请求。
* <p>
* 首轮实现不额外落摄取运行表,而是基于知识库与文档主数据生成可追踪的聚合 runId
* 让前端能够按统一入口进入管道详情页。
*/
@Data
@Schema(description = "文件解析管道创建请求")
public class IngestionRunCreateRequest {
@NotNull(message = "知识库ID不能为空")
@Schema(description = "知识库ID")
private Long storeId;
@NotNull(message = "文档ID不能为空")
@Schema(description = "文档ID")
private Long documentId;
}

View File

@@ -1,5 +1,6 @@
package com.bruce.rag.service;
import com.bruce.rag.dto.request.IngestionRunCreateRequest;
import com.bruce.rag.vo.IngestionRunVO;
/**
@@ -7,6 +8,11 @@ import com.bruce.rag.vo.IngestionRunVO;
*/
public interface IIngestionRunService {
/**
* 创建文件解析管道视图入口。
*/
IngestionRunVO createRun(IngestionRunCreateRequest request);
/**
* 按知识库和文档聚合摄取流水线视图。
*/

View File

@@ -3,6 +3,7 @@ package com.bruce.rag.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bruce.modelprovider.dto.response.RagStoreModelConfigResponse;
import com.bruce.modelprovider.service.IRagStoreModelConfigService;
import com.bruce.rag.dto.request.IngestionRunCreateRequest;
import com.bruce.rag.dto.response.RagDocumentResponse;
import com.bruce.rag.dto.response.RagStoreResponse;
import com.bruce.rag.entity.RagChunk;
@@ -53,6 +54,19 @@ public class IngestionRunServiceImpl implements IIngestionRunService {
private final IRagChunkEmbeddingService ragChunkEmbeddingService;
private final IRagStoreModelConfigService ragStoreModelConfigService;
@Override
public IngestionRunVO createRun(IngestionRunCreateRequest request) {
if (request == null) {
throw new IllegalArgumentException("文件解析管道创建请求不能为空");
}
log.info("文件解析管道创建聚合开始storeId={}, documentId={}", request.getStoreId(), request.getDocumentId());
IngestionRunVO view = getRun(request.getStoreId(), request.getDocumentId());
view.setRunId(buildRunId(request.getStoreId(), request.getDocumentId()));
log.info("文件解析管道创建聚合结束runId={}, storeId={}, documentId={}",
view.getRunId(), view.getStoreId(), view.getDocumentId());
return view;
}
@Override
public IngestionRunVO getRun(Long storeId, Long documentId) {
log.info("文件解析管道聚合开始storeId={}, documentId={}", storeId, documentId);
@@ -104,6 +118,10 @@ public class IngestionRunServiceImpl implements IIngestionRunService {
return view;
}
private String buildRunId(Long storeId, Long documentId) {
return "ingestion-" + safeNumber(storeId) + "-" + safeNumber(documentId);
}
private IngestionRunFileVO toFileVO(RagDocumentResponse document) {
IngestionRunFileVO file = new IngestionRunFileVO();
file.setDocumentId(document.getId());

View File

@@ -0,0 +1,86 @@
package com.bruce.rag.controller;
import com.bruce.common.handler.GlobalExceptionHandler;
import com.bruce.rag.service.IIngestionRunService;
import com.bruce.rag.vo.IngestionRunVO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* 验证文件解析管道聚合接口的请求绑定与响应结构。
*/
@ExtendWith(MockitoExtension.class)
class IngestionRunControllerTests {
private MockMvc mockMvc;
@Mock
private IIngestionRunService ingestionRunService;
@InjectMocks
private IngestionRunController ingestionRunController;
@BeforeEach
void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(ingestionRunController)
.setControllerAdvice(new GlobalExceptionHandler())
.build();
}
@Test
void createShouldReturnStructuredRunView() throws Exception {
IngestionRunVO run = new IngestionRunVO();
run.setRunId("ingestion-1001-11");
run.setStoreId(1001L);
run.setDocumentId(11L);
when(ingestionRunService.createRun(any())).thenReturn(run);
mockMvc.perform(post("/api/knowledge/ingestion-runs")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{
"storeId": 1001,
"documentId": 11
}
"""))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultcode").value("0"))
.andExpect(jsonPath("$.data.runId").value("ingestion-1001-11"))
.andExpect(jsonPath("$.data.storeId").value("1001"))
.andExpect(jsonPath("$.data.documentId").value("11"));
}
@Test
void detailShouldReturnStructuredRunView() throws Exception {
IngestionRunVO run = new IngestionRunVO();
run.setRunId("run-20260601");
run.setStoreId(1001L);
run.setDocumentId(11L);
when(ingestionRunService.getRun(1001L, 11L)).thenReturn(run);
mockMvc.perform(get("/api/knowledge/ingestion-runs/run-20260601")
.param("storeId", "1001")
.param("documentId", "11"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultcode").value("0"))
.andExpect(jsonPath("$.data.runId").value("run-20260601"))
.andExpect(jsonPath("$.data.storeId").value("1001"))
.andExpect(jsonPath("$.data.documentId").value("11"));
}
}

View File

@@ -2,6 +2,7 @@ package com.bruce.rag.ingestion;
import com.bruce.modelprovider.dto.response.RagStoreModelConfigResponse;
import com.bruce.modelprovider.service.IRagStoreModelConfigService;
import com.bruce.rag.dto.request.IngestionRunCreateRequest;
import com.bruce.rag.dto.response.RagDocumentResponse;
import com.bruce.rag.dto.response.RagStoreResponse;
import com.bruce.rag.entity.RagChunk;
@@ -51,6 +52,43 @@ class IngestionRunServiceTests {
@InjectMocks
private IngestionRunServiceImpl ingestionRunService;
@Test
void createRunShouldGenerateStableRunId() {
RagStoreResponse store = new RagStoreResponse();
store.setId(1001L);
store.setStoreCode("PROD_DOC");
store.setStoreName("产品制度库");
RagDocumentResponse document = new RagDocumentResponse();
document.setId(11L);
document.setStoreId(1001L);
document.setDocumentTitle("售前方案模板.pdf");
document.setParseStatus("PARSED");
document.setIndexStatus("INDEXED");
document.setCreateTime(new Date(1748780000000L));
document.setUpdateTime(new Date(1748780300000L));
when(ragStoreService.getResponseById(1001L)).thenReturn(store);
when(ragDocumentService.getResponseById(11L)).thenReturn(document);
when(ragDocumentParseResultService.getByDocumentId(11L)).thenReturn(null);
when(ragChunkService.list(org.mockito.ArgumentMatchers.<com.baomidou.mybatisplus.core.conditions.Wrapper<RagChunk>>any()))
.thenReturn(List.of());
when(ragChunkEmbeddingService.list(org.mockito.ArgumentMatchers.<com.baomidou.mybatisplus.core.conditions.Wrapper<RagChunkEmbedding>>any()))
.thenReturn(List.of());
when(ragStoreModelConfigService.getByStoreId(1001L)).thenReturn(null);
IngestionRunCreateRequest request = new IngestionRunCreateRequest();
request.setStoreId(1001L);
request.setDocumentId(11L);
IngestionRunVO view = ingestionRunService.createRun(request);
assertNotNull(view);
assertEquals("ingestion-1001-11", view.getRunId());
assertEquals(1001L, view.getStoreId());
assertEquals(11L, view.getDocumentId());
}
@Test
void getRunShouldAggregatePipelinePreviewAndLogs() {
RagStoreResponse store = new RagStoreResponse();