Files
ccdi/docs/plans/backend/2026-03-29-project-import-history-backend-implementation.md

16 KiB
Raw Blame History

Project Import History Backend Implementation Plan

For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 为历史项目导入能力补齐后端链路,支持创建新项目后异步复制历史流水、复制成功文件记录、禁止删除历史导入记录,并自动触发现有打标与结果刷新流程。

Architecture: 后端采用最短路径实现,在现有 CcdiProjectController -> ICcdiProjectService -> CcdiProjectServiceImpl 创建项目主链路外新增“历史导入”专用接口与异步编排服务。流水复制不复用文件上传接口语义,而是直接在项目维度重建批次号、批量写入 ccdi_bank_statementccdi_file_upload_record,完成后复用 bankTagService.submitAutoRebuild(...) 与现有详情页状态轮询。

Tech Stack: Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, MySQL 8, JUnit 5, Mockito, Maven

后端验收清单

  • GET /ccdi/project/history 仅返回已完成、已归档项目,支持项目名搜索
  • POST /ccdi/project/import 能先创建新项目,再异步提交历史导入任务
  • 历史导入按 trxDate 过滤流水,并按现有唯一性口径去重
  • 新项目中的历史导入文件记录带来源标识,且不允许删除
  • 历史导入完成后自动触发打标、结果表刷新和统计刷新

Task 1: 先用 SQL/Mapper 测试锁定历史导入所需数据结构

Files:

  • Create: sql/migration/2026-03-29-add-file-upload-history-import-source-fields.sql

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiFileUploadRecord.java

  • Modify: ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java

  • Step 1: Write the failing mapper/XML assertions

CcdiBankStatementMapperXmlTest.java 或新增紧邻的 XML 测试中,先锁定以下点:

assertTrue(xml.contains("source_type"));
assertTrue(xml.contains("source_project_id"));
assertTrue(xml.contains("source_project_name"));
assertTrue(xml.contains("fur.source_project_name"));

同时确认 CcdiFileUploadRecord 实体新增字段后有对应 getter/setter。

  • Step 2: Run test to verify it fails

Run:

mvn -pl ccdi-project -Dtest=CcdiBankStatementMapperXmlTest test

Expected:

  • FAIL

  • 原因是当前文件记录表和 Mapper 还没有历史导入来源字段

  • Step 3: Add the minimal schema and mapper support

在 SQL 脚本中新增:

ALTER TABLE ccdi_file_upload_record
  ADD COLUMN source_type varchar(32) DEFAULT 'UPLOAD' COMMENT '来源类型',
  ADD COLUMN source_project_id bigint NULL COMMENT '来源项目ID',
  ADD COLUMN source_project_name varchar(100) NULL COMMENT '来源项目名称';

CcdiFileUploadRecord.java 增加:

private String sourceType;
private Long sourceProjectId;
private String sourceProjectName;

CcdiFileUploadRecordMapper.xmlresultMapselectinsertBatch 中补齐字段映射。

  • Step 4: Run test to verify it passes

Run:

mvn -pl ccdi-project -Dtest=CcdiBankStatementMapperXmlTest test

Expected:

  • PASS

  • Step 5: Commit

git add sql/migration/2026-03-29-add-file-upload-history-import-source-fields.sql ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiFileUploadRecord.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java
git commit -m "补充历史导入文件记录字段"

Task 2: 锁定项目历史列表接口与导入接口契约

Files:

  • Create: ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectImportHistoryDTO.java

  • Create: ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectHistoryListItemVO.java

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java

  • Step 1: Write the failing controller contract test

CcdiProjectControllerContractTest.java 中新增断言:

Method history = CcdiProjectController.class.getMethod("listHistoryProjects", CcdiProjectQueryDTO.class);
assertEquals("/history", history.getAnnotation(GetMapping.class).value()[0]);

Method importing = CcdiProjectController.class.getMethod("importFromHistory", CcdiProjectImportHistoryDTO.class);
assertEquals("/import", importing.getAnnotation(PostMapping.class).value()[0]);

并在控制器测试中锁定返回结构至少包含“项目创建成功后返回新项目数据”。

  • Step 2: Run test to verify it fails

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectControllerContractTest,CcdiProjectControllerTest test

Expected:

  • FAIL

  • 原因是当前控制器没有历史项目列表和导入提交两个真实接口

  • Step 3: Add DTO/VO and minimal controller signatures

在 DTO 中定义:

private String projectName;
private String description;
private List<Long> sourceProjectIds;
private String startDate;
private String endDate;

ICcdiProjectService.java 中补充:

List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO);
CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator);

CcdiProjectController.java 中增加:

@GetMapping("/history")
public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) { ... }

@PostMapping("/import")
public AjaxResult importFromHistory(@Validated @RequestBody CcdiProjectImportHistoryDTO dto) { ... }
  • Step 4: Run tests to verify the contract passes

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectControllerContractTest,CcdiProjectControllerTest test

Expected:

  • PASS

  • Step 5: Commit

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectImportHistoryDTO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectHistoryListItemVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java
git commit -m "补充历史项目导入接口契约"

Task 3: 实现历史项目列表查询与项目创建后任务提交流程

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java

  • Modify: ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java

  • Create: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectHistoryImportService.java

  • Create: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java

  • Step 1: Write the failing service tests

CcdiProjectServiceImplTest.java 中新增至少两个场景:

@Test
void shouldOnlyReturnCompletedAndArchivedHistoryProjects() { ... }

@Test
void shouldCreateProjectThenSubmitHistoryImportAfterCommit() { ... }

第二个测试至少锁定:

  • 先调用 createProject 生成本地项目

  • 仅在事务提交后委托 historyImportService.submitImport(...)

  • Step 2: Run test to verify it fails

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest test

Expected:

  • FAIL

  • Step 3: Implement the minimal query and submit flow

CcdiProjectMapper.xml 中新增历史项目查询 SQL条件固定

where p.status in ('1', '2')

并支持项目名模糊搜索。

CcdiProjectServiceImpl.java 中实现:

public List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO) { ... }

public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) {
    CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO();
    saveDTO.setProjectName(dto.getProjectName());
    saveDTO.setDescription(dto.getDescription());
    saveDTO.setConfigType("default");
    CcdiProjectVO project = createProject(saveDTO);
    TransactionSynchronizationManager.registerSynchronization(... historyImportService.submitImport(...));
    return project;
}

约束:

  • 不新增导入专用项目状态

  • 不在接口内同步执行大批量复制

  • 只在事务提交后启动异步导入

  • Step 4: Run tests to verify it passes

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest test

Expected:

  • PASS

  • Step 5: Commit

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectHistoryImportService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java
git commit -m "实现历史项目导入任务提交流程"

Task 4: 实现流水复制、批次号重映射与来源文件记录生成

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java

  • Modify: ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java

  • Modify: ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java

  • Create: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java

  • Step 1: Write the failing history import service tests

新增 CcdiProjectHistoryImportServiceImplTest.java,锁定以下行为:

@Test
void shouldFilterStatementsByTrxDateAndDeduplicateAcrossSourceProjects() { ... }

@Test
void shouldGenerateNewBatchIdsAndHistoryImportFileRecordsOnlyForSuccessfulSourceBatches() { ... }

断言重点:

  • 仅复制命中时间范围的流水

  • 新项目 batchId/logId 不等于来源批次号

  • 文件记录 sourceType = HISTORY_IMPORT

  • 文件记录携带 sourceProjectId/sourceProjectName

  • Step 2: Run test to verify it fails

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest test

Expected:

  • FAIL

  • Step 3: Implement the minimal copy pipeline

在 Mapper 中新增只读查询:

  • 查询来源项目成功文件记录
  • 按来源批次和日期区间查询流水

CcdiProjectHistoryImportServiceImpl.java 中实现:

Map<Integer, Integer> newBatchIdMapping = new HashMap<>();
for (SourceBatch batch : matchedBatches) {
    int newBatchId = nextBatchId();
    newBatchIdMapping.put(batch.getSourceLogId(), newBatchId);
}

并在复制流水时写入:

statement.setProjectId(targetProjectId);
statement.setGroupId(targetLsfxProjectId);
statement.setBatchId(newBatchId);

在生成文件记录时写入:

record.setSourceType("HISTORY_IMPORT");
record.setSourceProjectId(sourceProjectId);
record.setSourceProjectName(sourceProjectName);
record.setFileStatus("parsed_success");
  • Step 4: Run tests to verify it passes

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest,CcdiBankStatementMapperXmlTest test

Expected:

  • PASS

  • Step 5: Commit

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFileUploadRecordMapper.java ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFileUploadRecordMapper.xml ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
git commit -m "实现历史项目流水复制后端逻辑"

Task 5: 接入自动打标、删除拦截与回归验证

Files:

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java

  • Modify: ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java

  • Modify: ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java

  • Modify: docs/reports/implementation/2026-03-29-project-import-history-plan-record.md

  • Step 1: Write the failing tests for auto rebuild and delete guard

CcdiProjectHistoryImportServiceImplTest.java 中新增:

verify(bankTagService).submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD);

CcdiFileUploadServiceImplTest.java 中新增:

assertThrows(ServiceException.class, () -> service.deleteFileUploadRecord(historyRecordId, 1L));

并锁定错误信息包含“历史导入文件不支持删除”。

  • Step 2: Run test to verify it fails

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest test

Expected:

  • FAIL

  • Step 3: Implement the minimal rebuild and delete guard

在历史导入服务复制完成后补充:

refreshProjectTargetCount(targetProjectId);
bankTagService.submitAutoRebuild(targetProjectId, TriggerType.AUTO_BATCH_UPLOAD);

deleteFileUploadRecord(...) 中补充:

if ("HISTORY_IMPORT".equals(record.getSourceType())) {
    throw new ServiceException("历史导入文件不支持删除");
}
  • Step 4: Run the backend regression suite

Run:

mvn -pl ccdi-project -Dtest=CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiProjectServiceImplTest,CcdiProjectHistoryImportServiceImplTest,CcdiFileUploadServiceImplTest,CcdiBankStatementMapperXmlTest test

Expected:

  • PASS

  • Step 5: Commit

git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectHistoryImportServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java docs/reports/implementation/2026-03-29-project-import-history-plan-record.md
git commit -m "接通历史项目导入后端闭环"