393 lines
13 KiB
Markdown
393 lines
13 KiB
Markdown
|
|
# Credit Info Direct Storage 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:** 将征信维护后端从“员工征信口径”调整为“征信对象口径”,上传时不再校验员工或亲属归属,解析成功后按 `person_id` 直接覆盖入库,并让列表查询直接基于征信表聚合。
|
|||
|
|
|
|||
|
|
**Architecture:** 保留现有 `CcdiCreditInfoController -> ICcdiCreditInfoService -> CcdiCreditInfoServiceImpl -> CcdiCreditInfoQueryMapper.xml` 链路,不新增表、不改接口路径。核心改造点是删除上传链路中的员工存在性校验,收敛查询 DTO/VO 到征信对象字段,并将列表 SQL 从 `ccdi_base_staff` 驱动改成“征信业务表驱动”的按证件号聚合查询。
|
|||
|
|
|
|||
|
|
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, JUnit 5, Mockito, Maven, MySQL
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 文件结构与职责
|
|||
|
|
|
|||
|
|
**修改文件**
|
|||
|
|
|
|||
|
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiCreditInfoQueryDTO.java`
|
|||
|
|
删除员工口径查询字段,仅保留征信对象筛选条件。
|
|||
|
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoListVO.java`
|
|||
|
|
删除 `staffId`、`deptName` 等员工字段,只保留征信对象摘要字段。
|
|||
|
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java`
|
|||
|
|
删除 `CcdiBaseStaffMapper` 依赖与 `ensureStaffExists` 校验,保留最新日期与覆盖写入逻辑。
|
|||
|
|
- `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml`
|
|||
|
|
列表与摘要查询改为直接从征信业务表按 `person_id` 聚合,不再 `JOIN ccdi_base_staff`。
|
|||
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java`
|
|||
|
|
锁定“非员工征信对象也可上传成功”和“旧日期仍失败”行为。
|
|||
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java`
|
|||
|
|
锁定列表 SQL 已去掉 `ccdi_base_staff`、`staffId`、`maintained` 依赖。
|
|||
|
|
- `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiCreditInfoControllerTest.java`
|
|||
|
|
保持控制层分页委派契约,确认 DTO 精简后接口仍可调用。
|
|||
|
|
- `docs/reports/implementation/2026-03-24-credit-info-direct-storage-backend-record.md`
|
|||
|
|
后端实施记录。
|
|||
|
|
|
|||
|
|
**参考文件**
|
|||
|
|
|
|||
|
|
- `docs/design/2026-03-24-credit-info-direct-storage-design.md`
|
|||
|
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiCreditInfoController.java`
|
|||
|
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapper.java`
|
|||
|
|
- `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoDetailVO.java`
|
|||
|
|
|
|||
|
|
## Task 1: 锁定“直接入库”后端契约
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java`
|
|||
|
|
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java`
|
|||
|
|
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiCreditInfoControllerTest.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 先把服务测试改成征信对象口径**
|
|||
|
|
|
|||
|
|
在 `CcdiCreditInfoServiceImplTest` 新增或改写用例,明确非员工证件号也能成功上传:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() {
|
|||
|
|
MockMultipartFile file = new MockMultipartFile(
|
|||
|
|
"files", "family.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
|
|||
|
|
|
|||
|
|
when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
|
|||
|
|
.thenReturn(successResponse("330101199202020022", "李四", "2026-03-24"));
|
|||
|
|
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
|||
|
|
.thenReturn(List.of(buildDebt("330101199202020022")));
|
|||
|
|
when(assembler.buildNegative(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
|
|||
|
|
.thenReturn(buildNegative("330101199202020022"));
|
|||
|
|
|
|||
|
|
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
|||
|
|
|
|||
|
|
assertEquals(1, result.getSuccessCount());
|
|||
|
|
assertEquals(0, result.getFailureCount());
|
|||
|
|
verify(debtsInfoMapper).deleteByPersonId("330101199202020022");
|
|||
|
|
verify(negativeInfoMapper).deleteByPersonId("330101199202020022");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 删除测试里对员工表 Mock 的依赖**
|
|||
|
|
|
|||
|
|
清理以下旧逻辑:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Mock
|
|||
|
|
private CcdiBaseStaffMapper baseStaffMapper;
|
|||
|
|
|
|||
|
|
when(baseStaffMapper.selectOne(any())).thenReturn(baseStaff(...));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
并同步移除不再使用的 `CcdiBaseStaff`、`CcdiBaseStaffMapper`、`any()` 导入。
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 改写 SQL 契约测试**
|
|||
|
|
|
|||
|
|
把 `CcdiCreditInfoQueryMapperXmlTest` 改成下面这组断言:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
assertFalse(source.contains("FROM ccdi_base_staff"),
|
|||
|
|
"征信维护列表不应再从员工表驱动查询");
|
|||
|
|
assertFalse(source.contains("query.staffId"),
|
|||
|
|
"征信维护列表不应再接收柜员号筛选");
|
|||
|
|
assertFalse(source.contains("query.maintained"),
|
|||
|
|
"征信维护列表不应再接收是否已维护筛选");
|
|||
|
|
assertTrue(source.contains("person_id"),
|
|||
|
|
"征信维护列表必须按征信对象证件号聚合");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 运行失败测试确认改造点被覆盖**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest,CcdiCreditInfoQueryMapperXmlTest,CcdiCreditInfoControllerTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
|
|||
|
|
- FAIL,原因应集中在服务实现仍校验员工存在、查询 XML 仍依赖员工表、DTO/VO 仍残留旧字段。
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: 提交测试契约**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java \
|
|||
|
|
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java \
|
|||
|
|
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiCreditInfoControllerTest.java
|
|||
|
|
git commit -m "新增征信直接入库后端测试契约"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Task 2: 收敛 DTO / VO 到征信对象字段
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiCreditInfoQueryDTO.java`
|
|||
|
|
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoListVO.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 精简查询 DTO**
|
|||
|
|
|
|||
|
|
将 `CcdiCreditInfoQueryDTO` 改为只保留:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Data
|
|||
|
|
public class CcdiCreditInfoQueryDTO implements Serializable {
|
|||
|
|
|
|||
|
|
@Serial
|
|||
|
|
private static final long serialVersionUID = 1L;
|
|||
|
|
|
|||
|
|
private String name;
|
|||
|
|
private String idCard;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 精简列表 VO**
|
|||
|
|
|
|||
|
|
将 `CreditInfoListVO` 改为只保留:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private String name;
|
|||
|
|
private String idCard;
|
|||
|
|
private Date queryDate;
|
|||
|
|
private Long debtCount;
|
|||
|
|
private BigDecimal debtTotalAmount;
|
|||
|
|
private Integer civilCnt;
|
|||
|
|
private Integer enforceCnt;
|
|||
|
|
private Integer admCnt;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
删除:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private Long staffId;
|
|||
|
|
private String deptName;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 运行单测确认编译通过**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoControllerTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
|
|||
|
|
- PASS,控制层在 DTO 精简后仍能正常分页委派。
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 提交 DTO / VO 调整**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiCreditInfoQueryDTO.java \
|
|||
|
|
ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoListVO.java
|
|||
|
|
git commit -m "精简征信维护后端查询字段"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Task 3: 改造上传服务为“直接入库”
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java`
|
|||
|
|
- Review: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssembler.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 移除员工主数据依赖**
|
|||
|
|
|
|||
|
|
删除成员与导入:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Resource
|
|||
|
|
private CcdiBaseStaffMapper baseStaffMapper;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
以及:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
ensureStaffExists(personId);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 删除 `ensureStaffExists` 方法**
|
|||
|
|
|
|||
|
|
从实现中彻底移除:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private void ensureStaffExists(String personId) { ... }
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
保留对以下字段的校验:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
String personId = stringValue(header.get("query_cert_no"));
|
|||
|
|
String personName = stringValue(header.get("query_cust_name"));
|
|||
|
|
LocalDate queryDate = parseQueryDate(stringValue(header.get("report_time")));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 保持最新日期覆盖逻辑**
|
|||
|
|
|
|||
|
|
保留并验证以下路径仍然成立:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
ensureLatestQueryDate(personId, queryDate);
|
|||
|
|
replaceEmployeeCredit(personId, debts, negative, userName);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
这里不用改方法名,优先走最短路径;只要行为变成“按征信对象本人证件号覆盖写入”即可。
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 运行服务测试**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
|
|||
|
|
- PASS,非员工征信对象可以上传成功
|
|||
|
|
- PASS,旧日期上传仍然失败
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: 提交服务实现调整**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java \
|
|||
|
|
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java
|
|||
|
|
git commit -m "调整征信维护后端直接入库逻辑"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Task 4: 重写列表 SQL 为征信表聚合查询
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml`
|
|||
|
|
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 设计征信对象主集合**
|
|||
|
|
|
|||
|
|
在 XML 中构造统一主集合,覆盖“只有负债”“只有负面信息”“两者都有”三种场景:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT person_id
|
|||
|
|
FROM ccdi_debts_info
|
|||
|
|
GROUP BY person_id
|
|||
|
|
UNION
|
|||
|
|
SELECT person_id
|
|||
|
|
FROM ccdi_credit_negative_info
|
|||
|
|
GROUP BY person_id
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 改写分页查询 SQL**
|
|||
|
|
|
|||
|
|
参考结构:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
SELECT
|
|||
|
|
keys.person_id AS id_card,
|
|||
|
|
COALESCE(debt_agg.person_name, neg.person_name) AS name,
|
|||
|
|
COALESCE(debt_agg.query_date, neg.query_date) AS query_date,
|
|||
|
|
IFNULL(debt_agg.debt_count, 0) AS debt_count,
|
|||
|
|
IFNULL(debt_agg.debt_total_amount, 0) AS debt_total_amount,
|
|||
|
|
IFNULL(neg.civil_cnt, 0) AS civil_cnt,
|
|||
|
|
IFNULL(neg.enforce_cnt, 0) AS enforce_cnt,
|
|||
|
|
IFNULL(neg.adm_cnt, 0) AS adm_cnt
|
|||
|
|
FROM (
|
|||
|
|
SELECT person_id FROM ccdi_debts_info GROUP BY person_id
|
|||
|
|
UNION
|
|||
|
|
SELECT person_id FROM ccdi_credit_negative_info GROUP BY person_id
|
|||
|
|
) keys
|
|||
|
|
LEFT JOIN (
|
|||
|
|
SELECT
|
|||
|
|
person_id,
|
|||
|
|
MAX(person_name) AS person_name,
|
|||
|
|
MAX(query_date) AS query_date,
|
|||
|
|
COUNT(*) AS debt_count,
|
|||
|
|
SUM(debt_total_amount) AS debt_total_amount
|
|||
|
|
FROM ccdi_debts_info
|
|||
|
|
GROUP BY person_id
|
|||
|
|
) debt_agg ON debt_agg.person_id = keys.person_id
|
|||
|
|
LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = keys.person_id
|
|||
|
|
<where>
|
|||
|
|
<if test="query != null and query.name != null and query.name != ''">
|
|||
|
|
AND COALESCE(debt_agg.person_name, neg.person_name) LIKE CONCAT('%', #{query.name}, '%')
|
|||
|
|
</if>
|
|||
|
|
<if test="query != null and query.idCard != null and query.idCard != ''">
|
|||
|
|
AND keys.person_id LIKE CONCAT('%', #{query.idCard}, '%')
|
|||
|
|
</if>
|
|||
|
|
</where>
|
|||
|
|
ORDER BY COALESCE(debt_agg.query_date, neg.query_date) DESC, keys.person_id DESC
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 改写详情摘要查询**
|
|||
|
|
|
|||
|
|
`selectCreditInfoSummaryByPersonId` 同样改为直接从征信表聚合,不再依赖员工表。至少保证:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
WHERE keys.person_id = #{personId}
|
|||
|
|
LIMIT 1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 运行 XML 契约测试**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoQueryMapperXmlTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
|
|||
|
|
- PASS,XML 不再出现 `ccdi_base_staff`、`query.staffId`、`query.maintained`
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: 提交查询 SQL 改造**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml \
|
|||
|
|
ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapperXmlTest.java
|
|||
|
|
git commit -m "调整征信维护后端聚合查询口径"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Task 5: 全量回归与实施记录
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Create: `docs/reports/implementation/2026-03-24-credit-info-direct-storage-backend-record.md`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: 运行后端回归测试**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest,CcdiCreditInfoQueryMapperXmlTest,CcdiCreditInfoControllerTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
|
|||
|
|
- PASS,直接入库、聚合查询、控制层分页委派相关测试全部通过。
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: 编写后端实施记录**
|
|||
|
|
|
|||
|
|
在 `docs/reports/implementation/2026-03-24-credit-info-direct-storage-backend-record.md` 中记录:
|
|||
|
|
|
|||
|
|
- 实施目标
|
|||
|
|
- 实际修改文件
|
|||
|
|
- 直接入库口径
|
|||
|
|
- SQL 聚合调整
|
|||
|
|
- 验证命令与结果
|
|||
|
|
- 若测试期间启动本地服务,结束后已关闭进程
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: 检查工作区仅包含本次改动**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git status --short
|
|||
|
|
git diff --staged --name-only
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected:
|
|||
|
|
|
|||
|
|
- 仅出现本任务相关的后端源码、测试和实施记录文件。
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: 提交后端收尾**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add docs/reports/implementation/2026-03-24-credit-info-direct-storage-backend-record.md
|
|||
|
|
git commit -m "补充征信维护后端直接入库实施记录"
|
|||
|
|
```
|