348 lines
15 KiB
Markdown
348 lines
15 KiB
Markdown
# 开发风险明细涉疑交易明细后端 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
|
||
<result property="customerCertNo" column="customer_cert_no" />
|
||
<result property="customerSocialCreditCode" column="customer_social_credit_code" />
|
||
...
|
||
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
|
||
<select id="selectSuspiciousTransactionPage" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO">
|
||
SELECT merged.bankStatementId,
|
||
MAX(merged.trxDate) AS trxDate,
|
||
MAX(merged.suspiciousPersonName) AS suspiciousPersonName,
|
||
MAX(merged.relatedPersonName) AS relatedPersonName,
|
||
MAX(merged.relatedStaffName) AS relatedStaffName,
|
||
MAX(merged.relatedStaffCode) AS relatedStaffCode,
|
||
MAX(merged.relationType) AS relationType,
|
||
MAX(merged.userMemo) AS userMemo,
|
||
MAX(merged.cashType) AS cashType,
|
||
MAX(merged.displayAmount) AS displayAmount,
|
||
MAX(merged.hasModelRuleHit) AS hasModelRuleHit,
|
||
MAX(merged.hasNameListHit) AS hasNameListHit
|
||
FROM (...)
|
||
GROUP BY merged.bankStatementId
|
||
</select>
|
||
```
|
||
|
||
- [ ] **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<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO);
|
||
ExcelUtil<CcdiProjectSuspiciousTransactionExcel> 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 "接通结果总览涉疑交易导出能力"
|
||
```
|