# 开发风险明细涉疑交易明细后端 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.
>
> **Repo note:** 本仓库 `AGENTS.md` 明确禁止开启 subagent,执行本计划时请在当前会话使用 `superpowers:executing-plans`。
**Goal:** 为结果总览页新增“涉疑交易明细”后端能力,支持按“全部可疑人员类型 / 名单库命中 / 模型规则命中”查询和导出,同时保证同一流水只展示一条。
**Architecture:** 以后端结果总览专用接口承载本功能,不污染现有通用流水明细查询接口。查询以 `ccdi_bank_statement` 为基表,分别聚合“规则名称包含可疑”的命中结果和“交易对手方命中中介库”的命中结果,再按 `bank_statement_id` 去重输出展示所需业务字段。由于数据库当前缺少交易对手方证件号与统一社会信用代码字段,先补齐表结构、LSFX 响应映射和本地入库链路,再落查询聚合。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis XML, MyBatis Plus `Page`, Maven, JUnit 5, Mockito, MySQL 8, ExcelUtil
---
### Task 1: 补齐交易对手方身份字段入库链路
**Files:**
- Create: `sql/migration/2026-03-27-ccdi-bank-statement-counterparty-identity-columns.sql`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java`
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml`
- Modify: `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/GetBankStatementResponse.java`
- Modify: `lsfx-mock-server/models/response.py`
- Modify: `lsfx-mock-server/services/statement_service.py`
- Modify: `lsfx-mock-server/services/statement_rule_samples.py`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatementTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java`
- Test: `lsfx-mock-server/tests/test_statement_service.py`
- [ ] **Step 1: 先写失败测试,锁定新增字段名和映射行为**
```java
@Test
void testFromResponse_ShouldMapCounterpartyIdentityFields() {
BankStatementItem item = new BankStatementItem();
item.setCustomerCertNo("330101199001011234");
item.setCustomerSocialCreditCode("91330100123456789X");
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
assertEquals("330101199001011234", entity.getCustomerCertNo());
assertEquals("91330100123456789X", entity.getCustomerSocialCreditCode());
}
```
- [ ] **Step 2: 运行测试,确认当前实现确实缺字段**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiBankStatementTest,CcdiBankStatementMapperXmlTest test
```
Expected:
- `CcdiBankStatementTest` 编译失败或断言失败,提示缺少 `customerCertNo` / `customerSocialCreditCode`
- `CcdiBankStatementMapperXmlTest` 尚未覆盖新列
- [ ] **Step 3: 实现最小字段补齐**
```sql
ALTER TABLE `ccdi_bank_statement`
ADD COLUMN `customer_cert_no` varchar(50) NULL COMMENT '交易对手方证件号' AFTER `customer_reference`,
ADD COLUMN `customer_social_credit_code` varchar(50) NULL COMMENT '交易对手方统一社会信用代码' AFTER `customer_cert_no`;
```
```java
private String customerCertNo;
private String customerSocialCreditCode;
entity.setCustomerCertNo(item.getCustomerCertNo());
entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode());
```
```xml
...
customer_bank, customer_reference, customer_cert_no, customer_social_credit_code,
...
#{item.customerBank}, #{item.customerReference}, #{item.customerCertNo}, #{item.customerSocialCreditCode},
```
- [ ] **Step 4: 同步本地 LSFX mock 契约,保证联调可生成这两个字段**
Run:
```bash
pytest lsfx-mock-server/tests/test_statement_service.py -v
```
Expected:
- mock 返回结构包含 `customerCertNo`
- mock 返回结构包含 `customerSocialCreditCode`
- [ ] **Step 5: 提交本任务**
```bash
git add sql/migration/2026-03-27-ccdi-bank-statement-counterparty-identity-columns.sql \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatement.java \
ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankStatementMapper.xml \
ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/GetBankStatementResponse.java \
lsfx-mock-server/models/response.py \
lsfx-mock-server/services/statement_service.py \
lsfx-mock-server/services/statement_rule_samples.py \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/domain/entity/CcdiBankStatementTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiBankStatementMapperXmlTest.java \
lsfx-mock-server/tests/test_statement_service.py
git commit -m "补齐涉疑交易明细对手方身份字段"
```
### Task 2: 建立结果总览涉疑交易接口契约
**Files:**
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectSuspiciousTransactionQueryDTO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectSuspiciousTransactionItemVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectSuspiciousTransactionPageVO.java`
- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectSuspiciousTransactionExcel.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceStructureTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerContractTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java`
- [ ] **Step 1: 先加结构测试,锁定新 DTO、接口方法和控制器路由**
```java
assertNotNull(clazz.getMethod(
"getSuspiciousTransactions",
Class.forName("com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO")
));
```
```java
assertEquals("/suspicious-transactions", getMapping.value()[0]);
assertEquals(List.of("projectId", "suspiciousType", "pageNum", "pageSize"), fieldNames);
```
- [ ] **Step 2: 运行控制器与结构测试,确认当前缺少接口**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest test
```
Expected:
- 结构测试报 `NoSuchMethodException`
- 控制器契约测试提示缺少 `/suspicious-transactions`
- [ ] **Step 3: 写最小 DTO / VO / Controller / Service 签名**
```java
public class CcdiProjectSuspiciousTransactionQueryDTO {
private Long projectId;
private String suspiciousType;
private Integer pageNum;
private Integer pageSize;
}
```
```java
@GetMapping("/suspicious-transactions")
public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
return AjaxResult.success(overviewService.getSuspiciousTransactions(queryDTO));
}
```
- [ ] **Step 4: 增加导出接口契约**
```java
@PostMapping("/suspicious-transactions/export")
public void exportSuspiciousTransactions(HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
...
}
```
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest test
```
Expected: PASS
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/dto/CcdiProjectSuspiciousTransactionQueryDTO.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectSuspiciousTransactionItemVO.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectSuspiciousTransactionPageVO.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectSuspiciousTransactionExcel.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/CcdiProjectOverviewServiceStructureTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerContractTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java
git commit -m "新增结果总览涉疑交易接口契约"
```
### Task 3: 落地涉疑交易聚合 SQL 与服务逻辑
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java`
- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java`
- Create: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceSuspiciousTransactionTest.java`
- [ ] **Step 1: 先写 SQL 结构测试和服务测试,锁定去重与筛选口径**
```java
assertTrue(suspiciousSql.contains("rule_name like '%可疑%'"));
assertTrue(suspiciousSql.contains("ccdi_biz_intermediary"));
assertTrue(suspiciousSql.contains("ccdi_enterprise_base_info"));
assertTrue(suspiciousSql.contains("group by merged.bankStatementId"));
```
```java
assertEquals(1, result.getRows().size());
assertTrue(result.getRows().getFirst().getHasModelRuleHit());
assertTrue(result.getRows().getFirst().getHasNameListHit());
```
- [ ] **Step 2: 运行测试,确认当前尚无聚合查询实现**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceSuspiciousTransactionTest test
```
Expected:
- SQL 测试找不到 `selectSuspiciousTransactionPage`
- 服务测试编译失败或返回空实现
- [ ] **Step 3: 在 Mapper XML 中实现“三层聚合 + 按流水去重”**
```xml
```
- [ ] **Step 4: 在 Service 中补查询类型标准化、项目存在性校验和分页返回**
```java
if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) {
queryDTO.setSuspiciousType("ALL");
}
queryDTO.setSuspiciousType(queryDTO.getSuspiciousType().trim().toUpperCase());
```
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceSuspiciousTransactionTest,CcdiProjectOverviewServiceImplTest test
```
Expected: PASS
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java \
ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceSuspiciousTransactionTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java
git commit -m "实现结果总览涉疑交易聚合查询"
```
### Task 4: 接通导出与回归验证
**Files:**
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java`
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java`
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerContractTest.java`
- [ ] **Step 1: 先补导出控制器测试**
```java
Method method = CcdiProjectOverviewController.class.getMethod(
"exportSuspiciousTransactions",
HttpServletResponse.class,
CcdiProjectSuspiciousTransactionQueryDTO.class
);
assertEquals("/suspicious-transactions/export", postMapping.value()[0]);
```
- [ ] **Step 2: 运行导出相关测试,确认导出接口尚未闭环**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest,CcdiProjectOverviewControllerContractTest test
```
Expected: FAIL,提示导出接口缺失或未调用服务
- [ ] **Step 3: 实现导出列表查询与 Excel 输出**
```java
List rows = overviewService.exportSuspiciousTransactions(queryDTO);
ExcelUtil util = new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
util.exportExcel(response, rows, "涉疑交易明细");
```
- [ ] **Step 4: 运行后端回归测试**
Run:
```bash
mvn -pl ccdi-project -Dtest=CcdiBankStatementTest,CcdiBankStatementMapperXmlTest,CcdiProjectOverviewServiceStructureTest,CcdiProjectOverviewControllerContractTest,CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest,CcdiProjectOverviewServiceSuspiciousTransactionTest test
```
Expected: PASS
- [ ] **Step 5: 提交本任务**
```bash
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java \
ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java \
ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerContractTest.java
git commit -m "接通结果总览涉疑交易导出能力"
```