feat: 接入流水标签自动触发并完成验证
This commit is contained in:
@@ -4,6 +4,7 @@ import com.alibaba.excel.EasyExcel;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiFileUploadQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||
@@ -12,6 +13,7 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
|
||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||
@@ -91,6 +93,9 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
@Resource
|
||||
private CcdiBankStatementMapper bankStatementMapper;
|
||||
|
||||
@Resource
|
||||
private ICcdiBankTagService bankTagService;
|
||||
|
||||
/**
|
||||
* 获取临时文件存储目录
|
||||
*/
|
||||
@@ -414,6 +419,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
String batchId) {
|
||||
log.info("【文件上传】调度线程启动: projectId={}, batchId={}", projectId, batchId);
|
||||
|
||||
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
|
||||
|
||||
// 循环提交任务
|
||||
for (int i = 0; i < tempFilePaths.size(); i++) {
|
||||
// Critical Fix #6: 检查线程中断状态
|
||||
@@ -431,10 +438,11 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
while (!submitted && retryCount < 2) {
|
||||
try {
|
||||
// 尝试提交异步任务
|
||||
CompletableFuture.runAsync(
|
||||
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(
|
||||
() -> processFileAsync(projectId, lsfxProjectId, tempFilePath, record.getId(), batchId, record),
|
||||
fileUploadExecutor
|
||||
);
|
||||
futures.add(future);
|
||||
submitted = true;
|
||||
log.info("【文件上传】任务提交成功: fileName={}, recordId={}",
|
||||
record.getFileName(), record.getId());
|
||||
@@ -449,16 +457,24 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("【文件上传】等待被中断: fileName={}", record.getFileName());
|
||||
updateRecordStatus(record.getId(), "parsed_failed", "任务提交被中断");
|
||||
futures.add(CompletableFuture.completedFuture(Boolean.FALSE));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log.error("【文件上传】重试失败,放弃任务: fileName={}", record.getFileName());
|
||||
updateRecordStatus(record.getId(), "parsed_failed", "系统繁忙,请稍后重试");
|
||||
futures.add(CompletableFuture.completedFuture(Boolean.FALSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.whenComplete((unused, throwable) -> {
|
||||
boolean anySuccess = futures.stream().anyMatch(future -> Boolean.TRUE.equals(future.getNow(Boolean.FALSE)));
|
||||
handleTagRebuildAfterBatchCompletion(projectId, TriggerType.AUTO_BATCH_UPLOAD, anySuccess);
|
||||
});
|
||||
|
||||
log.info("【文件上传】调度线程完成: projectId={}, batchId={}", projectId, batchId);
|
||||
}
|
||||
|
||||
@@ -506,6 +522,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
String batchId) {
|
||||
log.info("【拉取本行信息】调度线程启动: projectId={}, batchId={}", projectId, batchId);
|
||||
|
||||
List<CompletableFuture<Boolean>> futures = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < records.size(); i++) {
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
log.warn("【拉取本行信息】调度线程被中断,停止提交剩余任务");
|
||||
@@ -519,10 +537,11 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
|
||||
while (!submitted && retryCount < 2) {
|
||||
try {
|
||||
CompletableFuture.runAsync(
|
||||
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(
|
||||
() -> processPullBankInfoAsync(projectId, lsfxProjectId, record, idCard, startDate, endDate),
|
||||
fileUploadExecutor
|
||||
);
|
||||
futures.add(future);
|
||||
submitted = true;
|
||||
log.info("【拉取本行信息】任务提交成功: idCard={}, recordId={}", idCard, record.getId());
|
||||
} catch (RejectedExecutionException e) {
|
||||
@@ -535,23 +554,31 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
Thread.currentThread().interrupt();
|
||||
log.error("【拉取本行信息】等待被中断: idCard={}", idCard);
|
||||
updateRecordStatus(record.getId(), "parsed_failed", "任务提交被中断");
|
||||
futures.add(CompletableFuture.completedFuture(Boolean.FALSE));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log.error("【拉取本行信息】重试失败,放弃任务: idCard={}", idCard);
|
||||
updateRecordStatus(record.getId(), "parsed_failed", "系统繁忙,请稍后重试");
|
||||
futures.add(CompletableFuture.completedFuture(Boolean.FALSE));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.whenComplete((unused, throwable) -> {
|
||||
boolean anySuccess = futures.stream().anyMatch(future -> Boolean.TRUE.equals(future.getNow(Boolean.FALSE)));
|
||||
handleTagRebuildAfterBatchCompletion(projectId, TriggerType.AUTO_PULL_BANK_INFO, anySuccess);
|
||||
});
|
||||
}
|
||||
|
||||
public void processPullBankInfoAsync(Long projectId,
|
||||
Integer lsfxProjectId,
|
||||
CcdiFileUploadRecord record,
|
||||
String idCard,
|
||||
String startDate,
|
||||
String endDate ) {
|
||||
public boolean processPullBankInfoAsync(Long projectId,
|
||||
Integer lsfxProjectId,
|
||||
CcdiFileUploadRecord record,
|
||||
String idCard,
|
||||
String startDate,
|
||||
String endDate ) {
|
||||
try {
|
||||
FetchInnerFlowRequest request = new FetchInnerFlowRequest();
|
||||
request.setGroupId(lsfxProjectId);
|
||||
@@ -573,9 +600,11 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
}
|
||||
|
||||
processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("【拉取本行信息】处理失败: idCard={}, recordId={}", idCard, record.getId(), e);
|
||||
updateFailedRecord(record, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -591,8 +620,8 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
* @param record 文件上传记录
|
||||
*/
|
||||
@Async("fileUploadExecutor")
|
||||
public void processFileAsync(Long projectId, Integer lsfxProjectId, String tempFilePath,
|
||||
Long recordId, String batchId, CcdiFileUploadRecord record) {
|
||||
public boolean processFileAsync(Long projectId, Integer lsfxProjectId, String tempFilePath,
|
||||
Long recordId, String batchId, CcdiFileUploadRecord record) {
|
||||
log.info("【文件上传】开始处理文件: fileName={}, recordId={}, tempPath={}",
|
||||
record.getFileName(), recordId, tempFilePath);
|
||||
|
||||
@@ -629,10 +658,12 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
log.info("【文件上传】文件上传成功: logId={}", logId);
|
||||
processRecordAfterLogIdReady(projectId, lsfxProjectId, record, logId);
|
||||
log.info("【文件上传】处理完成: fileName={}", record.getFileName());
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("【文件上传】处理失败: fileName={}", record.getFileName(), e);
|
||||
updateRecordStatus(recordId, "parsed_failed", e.getMessage());
|
||||
return false;
|
||||
} finally {
|
||||
// 清理临时文件
|
||||
try {
|
||||
@@ -647,6 +678,13 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTagRebuildAfterBatchCompletion(Long projectId, TriggerType triggerType, Boolean anySuccess) {
|
||||
if (!Boolean.TRUE.equals(anySuccess)) {
|
||||
return;
|
||||
}
|
||||
bankTagService.submitAutoRebuild(projectId, triggerType);
|
||||
}
|
||||
|
||||
private void processRecordAfterLogIdReady(Long projectId,
|
||||
Integer lsfxProjectId,
|
||||
CcdiFileUploadRecord record,
|
||||
|
||||
@@ -5,11 +5,13 @@ import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.read.ListAppender;
|
||||
import com.alibaba.excel.EasyExcel;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
|
||||
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
|
||||
import com.ruoyi.lsfx.domain.request.GetBankStatementRequest;
|
||||
import com.ruoyi.lsfx.domain.response.CheckParseStatusResponse;
|
||||
@@ -84,6 +86,9 @@ class CcdiFileUploadServiceImplTest {
|
||||
@Mock
|
||||
private Executor fileUploadExecutor;
|
||||
|
||||
@Mock
|
||||
private ICcdiBankTagService bankTagService;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@@ -488,6 +493,32 @@ class CcdiFileUploadServiceImplTest {
|
||||
assertEquals(3L, result.getTotal());
|
||||
}
|
||||
|
||||
@Test
|
||||
void batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded() {
|
||||
ReflectionTestUtils.invokeMethod(
|
||||
service,
|
||||
"handleTagRebuildAfterBatchCompletion",
|
||||
PROJECT_ID,
|
||||
TriggerType.AUTO_BATCH_UPLOAD,
|
||||
Boolean.TRUE
|
||||
);
|
||||
|
||||
verify(bankTagService).submitAutoRebuild(PROJECT_ID, TriggerType.AUTO_BATCH_UPLOAD);
|
||||
}
|
||||
|
||||
@Test
|
||||
void pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded() {
|
||||
ReflectionTestUtils.invokeMethod(
|
||||
service,
|
||||
"handleTagRebuildAfterBatchCompletion",
|
||||
PROJECT_ID,
|
||||
TriggerType.AUTO_PULL_BANK_INFO,
|
||||
Boolean.TRUE
|
||||
);
|
||||
|
||||
verify(bankTagService).submitAutoRebuild(PROJECT_ID, TriggerType.AUTO_PULL_BANK_INFO);
|
||||
}
|
||||
|
||||
private void captureRecordStatus(List<String> events, AtomicInteger sequence) {
|
||||
doAnswer(invocation -> {
|
||||
CcdiFileUploadRecord record = invocation.getArgument(0);
|
||||
|
||||
@@ -0,0 +1,760 @@
|
||||
# Project Bank Statement Tagging Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为项目流水新增自动打标与手动重算后端能力,支持批量上传和拉取本行信息两条链路自动触发,支持项目级互斥重算与规则级并行执行。
|
||||
|
||||
**Architecture:** 在 `ccdi-project` 中新增标签规则表、结果表、任务表及对应 Mapper;引入项目级重算协调器和规则级线程池;在 `CcdiFileUploadServiceImpl` 的批量上传与拉取本行信息收尾阶段统一申请项目级标签重算;通过独立的标签服务读取规则元数据、参数配置,调度 Mapper XML 中的规则 SQL 并写入结果表。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, JUnit 5, Mockito, Maven
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 定义手动重算接口契约
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankTagRebuildDTO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankTagService.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增控制器测试,验证接口会把 `projectId + modelCode` 透传到 Service:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void rebuild_shouldDelegateProjectAndModelCode() {
|
||||
CcdiBankTagRebuildDTO dto = new CcdiBankTagRebuildDTO();
|
||||
dto.setProjectId(40L);
|
||||
dto.setModelCode("LARGE_TRANSACTION");
|
||||
|
||||
when(bankTagService.submitRebuild(dto, "admin")).thenReturn("标签重算任务已提交");
|
||||
|
||||
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
||||
mocked.when(SecurityUtils::getUsername).thenReturn("admin");
|
||||
|
||||
AjaxResult result = controller.rebuild(dto);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
verify(bankTagService).submitRebuild(dto, "admin");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest#rebuild_shouldDelegateProjectAndModelCode
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 DTO、Controller 或 Service 契约尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
补最小接口:
|
||||
|
||||
```java
|
||||
public interface ICcdiBankTagService {
|
||||
String submitRebuild(CcdiBankTagRebuildDTO dto, String operator);
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
@PostMapping("/rebuild")
|
||||
public AjaxResult rebuild(@Validated @RequestBody CcdiBankTagRebuildDTO dto) {
|
||||
String operator = SecurityUtils.getUsername();
|
||||
return AjaxResult.success(bankTagService.submitRebuild(dto, operator));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest#rebuild_shouldDelegateProjectAndModelCode
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiBankTagRebuildDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiBankTagController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiBankTagService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiBankTagControllerTest.java
|
||||
git commit -m "test: 补充流水标签重算接口契约"
|
||||
```
|
||||
|
||||
### Task 2: 新增标签核心表结构与实体映射
|
||||
|
||||
**Files:**
|
||||
- Create: `sql/2026-03-16-bank-tagging.sql`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagRule.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagResult.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagTask.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagEntityMappingTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增一个轻量测试,校验结果实体的关键字段存在:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void bankTagResult_shouldExposeStatementAndObjectFields() {
|
||||
CcdiBankTagResult result = new CcdiBankTagResult();
|
||||
result.setProjectId(40L);
|
||||
result.setRuleCode("RULE_1");
|
||||
result.setBankStatementId(10L);
|
||||
result.setGroupId(40);
|
||||
result.setLogId(40001);
|
||||
result.setObjectType("STAFF_ID_CARD");
|
||||
result.setObjectKey("330101198801010011");
|
||||
|
||||
assertEquals(40L, result.getProjectId());
|
||||
assertEquals(40001, result.getLogId());
|
||||
assertEquals("STAFF_ID_CARD", result.getObjectType());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagEntityMappingTest#bankTagResult_shouldExposeStatementAndObjectFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是新实体尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
- 新建三张表的 SQL 脚本
|
||||
- 新建三个实体类,字段只覆盖第一版设计所需字段
|
||||
- 规则表初始化“大额交易” 8 条规则元数据
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagEntityMappingTest#bankTagResult_shouldExposeStatementAndObjectFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add sql/2026-03-16-bank-tagging.sql ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagRule.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagResult.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagTask.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankTagEntityMappingTest.java
|
||||
git commit -m "feat: 新增流水标签核心表结构与实体映射"
|
||||
```
|
||||
|
||||
### Task 3: 建立规则元数据与结果表 Mapper
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagRuleMapper.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagRuleMapper.xml`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增 XML 渲染测试,校验结果表支持按项目或项目+模型删除:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteByProjectAndOptionalModel_shouldRenderScopedDelete() throws Exception {
|
||||
String xml = readXml("mapper/ccdi/project/CcdiBankTagResultMapper.xml");
|
||||
assertTrue(xml.contains("delete from ccdi_bank_statement_tag_result"));
|
||||
assertTrue(xml.contains("project_id = #{projectId}"));
|
||||
assertTrue(xml.contains("model_code = #{modelCode}"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagResultMapperXmlTest#deleteByProjectAndOptionalModel_shouldRenderScopedDelete
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 Mapper XML 尚不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
- 规则表 Mapper 提供启用规则查询
|
||||
- 结果表 Mapper 提供批量插入和按范围删除
|
||||
- 任务表 Mapper 提供创建任务、更新任务、查询运行中任务
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagResultMapperXmlTest#deleteByProjectAndOptionalModel_shouldRenderScopedDelete
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagRuleMapper.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapper.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagRuleMapper.xml ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagResultMapper.xml ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagResultMapperXmlTest.java
|
||||
git commit -m "feat: 新增流水标签规则结果任务Mapper"
|
||||
```
|
||||
|
||||
### Task 4: 为规则 SQL 定义统一返回 VO
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagStatementHitVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagObjectHitVO.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java`
|
||||
- Create: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增 XML 测试,先校验流水级命中 SQL 会返回 `group_id` 和 `log_id`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void statementRuleSql_shouldSelectGroupIdAndLogId() throws Exception {
|
||||
String xml = readXml("mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml");
|
||||
assertTrue(xml.contains("AS groupId"));
|
||||
assertTrue(xml.contains("AS logId"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#statementRuleSql_shouldSelectGroupIdAndLogId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
先定义统一 VO 和一个最小的 XML 框架,确保:
|
||||
|
||||
- 流水级规则 SQL 返回 `bankStatementId`、`groupId`、`logId`、`reasonDetail`
|
||||
- 对象级规则 SQL 返回 `objectType`、`objectKey`、`reasonDetail`
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#statementRuleSql_shouldSelectGroupIdAndLogId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagStatementHitVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagObjectHitVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java
|
||||
git commit -m "feat: 定义流水标签规则命中返回结构"
|
||||
```
|
||||
|
||||
### Task 5: 先实现一条流水级规则的失败测试与最小通过
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
以“房车消费支出交易”为第一条规则,测试 XML 中存在 `selectHouseOrCarExpenseStatements`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void houseOrCarExpenseRule_shouldJoinBankStatementAndReturnStatementHitFields() throws Exception {
|
||||
String xml = readXml("mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml");
|
||||
assertTrue(xml.contains("selectHouseOrCarExpenseStatements"));
|
||||
assertTrue(xml.contains("bs.bank_statement_id AS bankStatementId"));
|
||||
assertTrue(xml.contains("bs.group_id AS groupId"));
|
||||
assertTrue(xml.contains("bs.batch_id AS logId"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#houseOrCarExpenseRule_shouldJoinBankStatementAndReturnStatementHitFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 XML 中补第一条规则 SQL,并返回固定原因摘要。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest#houseOrCarExpenseRule_shouldJoinBankStatementAndReturnStatementHitFields
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java
|
||||
git commit -m "test: 打通首条流水标签规则SQL"
|
||||
```
|
||||
|
||||
### Task 6: 完成剩余 7 条规则 SQL
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java`
|
||||
|
||||
**Step 1: Write the failing tests**
|
||||
|
||||
为剩余规则补 XML 存在性断言,分别覆盖:
|
||||
|
||||
- `selectTaxExpenseStatements`
|
||||
- `selectSingleLargeIncomeStatements`
|
||||
- `selectCumulativeIncomeObjects`
|
||||
- `selectAnnualTurnoverObjects`
|
||||
- `selectLargeCashDepositStatements`
|
||||
- `selectFrequentCashDepositObjects`
|
||||
- `selectLargeTransferStatements`
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
补齐 7 条规则 SQL,要求:
|
||||
|
||||
- 阈值类规则使用入参,不在 XML 中写死参数值
|
||||
- 流水级规则返回 `bankStatementId/groupId/logId/reasonDetail`
|
||||
- 对象级规则返回 `objectType/objectKey/reasonDetail`
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagAnalysisMapperXmlTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagAnalysisMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagAnalysisMapperXmlTest.java
|
||||
git commit -m "feat: 补齐大额交易全部标签规则SQL"
|
||||
```
|
||||
|
||||
### Task 7: 实现项目参数读取与规则注册表
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagRuleExecutionConfig.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证 resolver 会根据项目读取有效参数,并为规则产出执行配置:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void resolve_shouldReadEffectiveProjectParamsForThresholdRules() {
|
||||
BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);
|
||||
assertEquals("1111", config.getThresholdValue("SINGLE_TRANSACTION_AMOUNT"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=BankTagRuleConfigResolverTest#resolve_shouldReadEffectiveProjectParamsForThresholdRules
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
实现 resolver:
|
||||
|
||||
- 读取项目 `configType`
|
||||
- 选择有效 `projectId`
|
||||
- 读取模型参数
|
||||
- 按 `ruleCode` 组装执行配置
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=BankTagRuleConfigResolverTest#resolve_shouldReadEffectiveProjectParamsForThresholdRules
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/BankTagRuleExecutionConfig.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolver.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/BankTagRuleConfigResolverTest.java
|
||||
git commit -m "feat: 新增流水标签规则执行参数解析器"
|
||||
```
|
||||
|
||||
### Task 8: 实现规则级并行执行服务
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/config/BankTagThreadPoolConfig.java`
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增服务测试,验证项目级任务会先删旧结果,再并行执行规则并汇总命中数:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks() {
|
||||
service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
|
||||
verify(resultMapper).deleteByProjectAndModel(40L, null);
|
||||
verify(resultMapper).insertBatch(anyList());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest#rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
实现 `CcdiBankTagServiceImpl`:
|
||||
|
||||
- 查询启用规则
|
||||
- 删旧结果
|
||||
- 使用 `tagRuleExecutor` 并行提交每条规则
|
||||
- 汇总命中结果
|
||||
- 更新任务状态
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest#rebuildProject_shouldDeleteOldResultsBeforeSubmittingRuleTasks
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/config/BankTagThreadPoolConfig.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java
|
||||
git commit -m "feat: 实现规则级并行流水标签重算服务"
|
||||
```
|
||||
|
||||
### Task 9: 实现同项目互斥与自动补跑协调器
|
||||
|
||||
**Files:**
|
||||
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证同一项目运行中时:
|
||||
|
||||
- 手动触发会被拒绝
|
||||
- 自动触发会标记 `need_rerun`
|
||||
|
||||
```java
|
||||
@Test
|
||||
void submitManualRebuild_shouldRejectWhenProjectAlreadyRunning() {
|
||||
assertThrows(ServiceException.class, () -> coordinator.submitManual(40L, null, "admin"));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=ProjectBankTagRebuildCoordinatorTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
实现协调器:
|
||||
|
||||
- 使用 `projectId` 级别互斥
|
||||
- 自动触发遇到运行中任务时打 `need_rerun`
|
||||
- 当前任务结束后根据 `need_rerun` 触发补跑
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=ProjectBankTagRebuildCoordinatorTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinator.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/ProjectBankTagRebuildCoordinatorTest.java
|
||||
git commit -m "feat: 新增项目级流水标签重算互斥与补跑协调器"
|
||||
```
|
||||
|
||||
### Task 10: 接入批量上传收尾自动触发
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证批量上传所有文件完成后会申请项目级重算:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded() {
|
||||
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_BATCH_UPLOAD);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
重构 `submitTasksAsync`:
|
||||
|
||||
- 收集每个文件任务的 `CompletableFuture`
|
||||
- `allOf(...).whenComplete(...)` 收尾
|
||||
- 至少一个文件成功落库时申请自动重算
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#batchUploadCompletion_shouldSubmitProjectTagRebuildWhenAnyFileSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 接入批量上传完成后的自动流水打标"
|
||||
```
|
||||
|
||||
### Task 11: 接入拉取本行信息收尾自动触发
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增测试,验证拉取本行信息全部任务完成后也会申请项目级重算:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded() {
|
||||
verify(bankTagService).submitAutoRebuild(40L, TriggerType.AUTO_PULL_BANK_INFO);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
仿照批量上传链路改造 `submitPullBankInfoTasks`,在全部任务结束后申请一次自动重算。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#pullBankInfoCompletion_shouldSubmitProjectTagRebuildWhenAnyTaskSucceeded
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 接入拉取本行信息完成后的自动流水打标"
|
||||
```
|
||||
|
||||
### Task 12: 完成全量验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/plans/2026-03-16-project-bank-statement-tagging-backend-implementation.md`
|
||||
|
||||
**Step 1: Run focused backend tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiBankTagControllerTest,CcdiBankTagEntityMappingTest,CcdiBankTagResultMapperXmlTest,CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiFileUploadServiceImplTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 所有新增测试通过
|
||||
|
||||
**Step 2: Run module compile**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn clean compile -pl ccdi-project -am
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `BUILD SUCCESS`
|
||||
|
||||
**Step 3: Update verification notes**
|
||||
|
||||
在本实施计划末尾补充实际执行结果摘要和任何遗留风险。
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project sql docs/plans/2026-03-16-project-bank-statement-tagging-backend-implementation.md
|
||||
git commit -m "feat: 完成项目流水标签后端实现"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实际执行结果
|
||||
|
||||
### 已完成范围
|
||||
|
||||
- 已新增手动重算接口、标签规则/结果/任务三张核心表及实体映射
|
||||
- 已新增规则、结果、任务、分析四类 Mapper 与 XML
|
||||
- 已完成“大额交易” 8 条规则 SQL 的首版落地
|
||||
- 已完成规则参数解析器、规则级线程池、规则级并行重算服务
|
||||
- 已完成项目级互斥协调器与自动触发补跑标记逻辑
|
||||
- 已接入批量上传与拉取本行信息两条链路的批次收尾自动触发
|
||||
|
||||
### 实际验证命令
|
||||
|
||||
在 `ccdi-project` 模块目录执行:
|
||||
|
||||
```bash
|
||||
mvn test "-Dtest=CcdiBankTagControllerTest,CcdiBankTagEntityMappingTest,CcdiBankTagResultMapperXmlTest,CcdiBankTagAnalysisMapperXmlTest,BankTagRuleConfigResolverTest,CcdiBankTagServiceImplTest,ProjectBankTagRebuildCoordinatorTest,CcdiFileUploadServiceImplTest"
|
||||
mvn clean compile
|
||||
```
|
||||
|
||||
### 实际验证结果
|
||||
|
||||
- 聚焦测试集:`BUILD SUCCESS`
|
||||
- `ccdi-project` 模块编译:`BUILD SUCCESS`
|
||||
- 聚焦测试共执行 30 个测试,0 失败,0 错误
|
||||
|
||||
### 遗留风险
|
||||
|
||||
- 当前规则 SQL 已按设计稿和现有表结构落地,但尚未补集成测试验证真实数据库数据命中情况
|
||||
- 自动补跑已支持 `need_rerun` 标记与串行补跑,后续建议增加更完整的并发场景回归测试
|
||||
- 当前实施仅完成后端能力,结果查询接口与前端展示仍未接入
|
||||
Reference in New Issue
Block a user