Files
ccdi/docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md
2026-04-22 09:52:32 +08:00

462 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 中介库导入改造后端实施计划
> **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.
> **执行约束:** 仓库约定不开 subagent执行时使用 `superpowers:executing-plans`。
**Goal:** 完成中介库后端导入改造,统一 `related_num_id` 为“关联中介本人证件号码”,新增“导入中介信息”和“导入中介实体关联关系”两条异步导入链路,并保证手工维护、统一查询和级联删除与新语义一致。
**Architecture:** 后端保持 `CcdiIntermediaryController + CcdiIntermediaryServiceImpl + ccdi_biz_intermediary/ccdi_intermediary_enterprise_relation` 的现有主线,不新增平行模块。外部 API 继续保持 `bizId` 契约,服务层内部把亲属链路统一转换为“本人证件号码”处理;导入链路对齐员工数据导入,拆分为“中介信息导入”和“中介实体关联关系导入”两套独立任务与失败记录。
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MySQL, Redis, EasyExcel, JUnit 5, Maven, Markdown
---
## 文件结构与职责
**设计与计划基线**
- `docs/design/2026-04-20-intermediary-import-refactor-design.md`
当前需求和边界的唯一设计基线,实施不得偏离其中已确认口径。
**SQL 与迁移**
- Create: `sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql`
负责把历史 `related_num_id = 本人 biz_id` 迁移为 `related_num_id = 本人 person_id`
- Create: `sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql`
负责补齐或校正 `ccdi_person_sub_type` 字典项,供导入模板下拉使用。
**中介主链路**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java`
调整 `relatedNumId` 字段注释语义。
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java`
暴露新的校验与导入能力方法。
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java`
完成亲属主从语义切换、手工维护逻辑修正、关系唯一性判断和级联删除。
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml`
把统一列表中的亲属关联条件从 `parent.biz_id` 改为 `parent.person_id`
**导入链路**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java`
重组导入接口,保留中介信息导入,新增实体关联关系导入。
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java`
`personSubType` 使用字典下拉,移除废弃的 `relationType` 列,重命名 `relatedNumId` 模板文案。
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java`
对齐新模板字段,移除废弃字段。
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java`
调整中介信息导入接口定义。
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java`
重写为“本人阶段 + 亲属阶段”的混合导入实现。
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryEnterpriseRelationExcel.java`
中介实体关联关系导入模板实体。
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryEnterpriseRelationImportFailureVO.java`
实体关联关系失败记录 VO。
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryEnterpriseRelationImportService.java`
实体关联关系导入服务接口。
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java`
实体关联关系异步导入实现。
**既有 DTO / VO / Mapper 补充**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonAddDTO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryPersonEditDTO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryRelativeAddDTO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryRelativeEditDTO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryPersonDetailVO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CcdiIntermediaryRelativeVO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryEnterpriseRelationMapper.java`
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml`
**测试**
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryServiceImplTest.java`
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryMapperTest.java`
- Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiIntermediaryControllerTest.java`
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryPersonImportServiceImplTest.java`
- Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryEnterpriseRelationImportServiceImplTest.java`
## 实施任务
### Task 1: 编写迁移脚本并锁定实施窗口
**Files:**
- Create: `sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql`
- Create: `sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql`
- Reference: `sql/migration/2026-04-17-fix-intermediary-person-sub-type-dict.sql`
- Reference: `sql/dpc_intermediary_dict_data_20260129.sql`
- [ ] **Step 1: 写迁移前校验 SQL**
在迁移脚本头部先写校验查询,明确输出:
```sql
-- 1. 找不到对应本人 biz_id 的亲属
-- 2. 本人 person_id 为空的记录
-- 3. 迁移后同一中介本人下 related_num_id + person_id 冲突的记录
```
- [ ] **Step 2: 写正式迁移 SQL**
将旧语义统一改为新语义:
```sql
UPDATE ccdi_biz_intermediary child
JOIN ccdi_biz_intermediary parent
ON child.related_num_id = parent.biz_id
SET child.related_num_id = parent.person_id
WHERE child.person_sub_type <> '本人';
```
- [ ] **Step 3: 写 `ccdi_person_sub_type` 字典修正脚本**
至少补齐:
```sql
/ / / / /
```
Expected: 模板下拉和后端校验使用同一字典源。
- [ ] **Step 4: 用 UTF-8 脚本执行 SQL 验证**
Run:
```bash
bin/mysql_utf8_exec.sh sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql
bin/mysql_utf8_exec.sh sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql
```
Expected: SQL 正常执行,无乱码,无唯一性冲突。
- [ ] **Step 5: 提交迁移脚本**
```bash
git add sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql
git commit -m "新增中介导入迁移脚本"
```
### Task 2: 切换中介模块内部主从语义
**Files:**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java`
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryRelativeAddDTO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryRelativeEditDTO.java`
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryServiceImplTest.java`
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryMapperTest.java`
- [ ] **Step 1: 先写失败测试,覆盖亲属新语义**
补测试场景:
```java
// 1. 手工新增亲属时 related_num_id 写本人 person_id
// 2. 查询亲属列表按 parent.person_id 关联
// 3. 删除本人时按本人 person_id 级联删除亲属
// 4. 相同亲属 person_id 挂到不同本人时允许成功
```
- [ ] **Step 2: 调整服务层内部转换逻辑**
关键改造点:
```java
CcdiBizIntermediary owner = requireIntermediaryPerson(bizId);
relative.setRelatedNumId(owner.getPersonId());
```
以及:
```java
wrapper.eq(CcdiBizIntermediary::getRelatedNumId, owner.getPersonId())
```
- [ ] **Step 3: 收敛唯一性校验**
新增两个明确规则:
```java
// 本人personId 全表唯一
// 亲属:同一 relatedNumId + personId 唯一
```
Expected: 不再把亲属 `personId` 误当成全表唯一。
- [ ] **Step 4: 修改联合查询 SQL**
把亲属联表条件改成:
```xml
ON child.related_num_id = parent.person_id
```
Expected: 统一列表在迁移后仍能查出亲属记录。
- [ ] **Step 5: 运行服务层与 Mapper 测试**
Run:
```bash
mvn -pl ccdi-info-collection -am -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest test
```
Expected: 目标测试通过,新的主从语义断言成立。
- [ ] **Step 6: 提交语义切换代码**
```bash
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiBizIntermediary.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryService.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryMapper.xml ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryRelativeAddDTO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiIntermediaryRelativeEditDTO.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiIntermediaryMapperTest.java
git commit -m "调整中介亲属关联语义"
```
### Task 3: 重构中介信息导入为“本人 + 亲属”混合导入
**Files:**
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java`
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryPersonImportServiceImplTest.java`
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiIntermediaryControllerTest.java`
- [ ] **Step 1: 先写导入失败测试**
覆盖:
```java
// 1. personSubType 使用字典下拉字段
// 2. personSubType=本人 且 relatedNumId 非空 -> 失败
// 3. personSubType!=本人 且 relatedNumId 为空 -> 失败
// 4. 亲属引用本次导入前面成功的本人 -> 成功
// 5. 同一亲属挂不同本人 -> 成功
// 6. 同一本人名下重复亲属 -> 失败
```
- [ ] **Step 2: 调整 Excel 模板实体**
最小改动:
```java
@DictDropdown(dictType = "ccdi_person_sub_type")
private String personSubType;
@ExcelProperty("关联中介本人证件号码")
private String relatedNumId;
```
同时删除:
```java
private String relationType;
```
- [ ] **Step 3: 按“两阶段”重写导入实现**
核心流程:
```java
List<CcdiIntermediaryPersonExcel> owners = ...
List<CcdiIntermediaryPersonExcel> relatives = ...
// owners: personId 全表唯一
// relatives: relatedNumId + personId 唯一
```
Expected: 中介本人和亲属在一个文件中可混合导入。
- [ ] **Step 4: 统一失败记录字段**
`IntermediaryPersonImportFailureVO` 只保留当前模板字段,不再暴露废弃的 `relationType`
- [ ] **Step 5: 调整 Controller 返回文案与模板下载**
Expected: 下载模板时 `personSubType` 下拉来源于 `ccdi_person_sub_type`,返回的失败记录字段与模板一致。
- [ ] **Step 6: 运行导入链路测试**
Run:
```bash
mvn -pl ccdi-info-collection -am -Dtest=CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryControllerTest test
```
Expected: 混合导入与失败口径通过。
- [ ] **Step 7: 提交中介信息导入改造**
```bash
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryPersonExcel.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryPersonImportFailureVO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryPersonImportService.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryPersonImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryPersonImportServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiIntermediaryControllerTest.java
git commit -m "改造中介信息导入链路"
```
### Task 4: 新增中介实体关联关系导入链路
**Files:**
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryEnterpriseRelationExcel.java`
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryEnterpriseRelationImportFailureVO.java`
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryEnterpriseRelationImportService.java`
- Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java`
- Modify: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java`
- Modify: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml`
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryEnterpriseRelationImportServiceImplTest.java`
- Test: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiIntermediaryControllerTest.java`
- [ ] **Step 1: 先写关系导入测试**
覆盖:
```java
// 1. 中介本人证件号码不存在 -> 失败
// 2. socialCreditCode 不存在于机构表 -> 失败
// 3. 同一文件内相同 personId + socialCreditCode -> 失败
// 4. 数据库内已存在相同 intermediary_biz_id + socialCredit_code -> 失败
// 5. 正常导入成功并写入关系表
```
- [ ] **Step 2: 新建 Excel 与失败记录对象**
字段固定为:
```java
ownerPersonId
socialCreditCode
relationPersonPost
remark
```
- [ ] **Step 3: 新建异步导入服务**
服务逻辑:
```java
// 证件号码 -> 查本人 bizId
// 社会信用代码 -> 校验机构存在
// intermediaryBizId + socialCreditCode 唯一
```
- [ ] **Step 4: 补 Controller 接口**
新增:
```java
/importEnterpriseRelationTemplate
/importEnterpriseRelationData
/importEnterpriseRelationStatus/{taskId}
/importEnterpriseRelationFailures/{taskId}
```
- [ ] **Step 5: 运行关系导入测试**
Run:
```bash
mvn -pl ccdi-info-collection -am -Dtest=CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiIntermediaryControllerTest test
```
Expected: 实体关联关系导入链路完整通过。
- [ ] **Step 6: 提交关系导入代码**
```bash
git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/excel/CcdiIntermediaryEnterpriseRelationExcel.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/IntermediaryEnterpriseRelationImportFailureVO.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiIntermediaryEnterpriseRelationImportService.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryEnterpriseRelationImportServiceImpl.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiIntermediaryController.java ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiIntermediaryServiceImpl.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiIntermediaryEnterpriseRelationMapper.xml ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiIntermediaryEnterpriseRelationImportServiceImplTest.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiIntermediaryControllerTest.java
git commit -m "新增中介实体关联关系导入"
```
### Task 5: 做完整回归验证并更新文档
**Files:**
- Modify: `docs/design/2026-04-20-intermediary-import-refactor-design.md`
- Modify: `docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md`
- Reference: `bin/mysql_utf8_exec.sh`
- [ ] **Step 1: 运行后端完整相关测试集合**
Run:
```bash
mvn -pl ccdi-info-collection -am -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest,CcdiIntermediaryControllerTest,CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest test
```
Expected: 全部 PASS。
- [ ] **Step 2: 编译信息采集模块,确认没有遗漏编译错误**
Run:
```bash
mvn -pl ccdi-info-collection -am clean compile
```
Expected: `BUILD SUCCESS`
- [ ] **Step 3: 记录 SQL 执行与测试结果**
在计划文档底部补:
```markdown
- 实际执行的 SQL 脚本
- 实际执行的 Maven 命令
- PASS / FAIL 结果
```
- [ ] **Step 4: 提交文档回写**
```bash
git add docs/design/2026-04-20-intermediary-import-refactor-design.md docs/plans/backend/2026-04-20-intermediary-import-backend-implementation.md
git commit -m "补充中介导入后端实施记录"
```
## 验证命令
```bash
bin/mysql_utf8_exec.sh sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql
bin/mysql_utf8_exec.sh sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql
mvn -pl ccdi-info-collection -am -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest,CcdiIntermediaryControllerTest,CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest test
mvn -pl ccdi-info-collection -am clean compile
```
## 完成标准
- `related_num_id` 已按“关联中介本人证件号码”完成迁移和代码切换
- 手工新增亲属、查询亲属、删除本人级联删除全部与新语义一致
- 中介信息导入支持本人和亲属混合导入
- 同一亲属证件号允许关联多个不同中介本人
- 中介实体关联关系导入独立可用,且只写关系表
- `relationType` 已从导入链路中移除
- `personSubType` 模板下拉已切换为 `ccdi_person_sub_type`
- 后端测试与编译验证通过
## 执行结果
- SQL 脚本:
`sql/migration/2026-04-20-fix-ccdi-person-sub-type-dict.sql`
`sql/migration/2026-04-20-migrate-intermediary-related-num-id-to-person-id.sql`
结果:已执行 `bin/mysql_utf8_exec.sh`
- SQL 核查:
`post_migration_missing_parent = 1025`
`legacy_biz_id_reference = 0`
`owner_person_id_empty_after_migration = 754`
结果:可迁移数据已切换完成,旧 `biz_id` 语义残留清零,但历史脏数据仍需后续清洗。
- Maven 命令:
`mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest test`
结果PASS
- Maven 命令:
`mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest,CcdiIntermediaryControllerTest test`
结果PASS
- Maven 命令:
`mvn -pl ccdi-info-collection -am -Dsurefire.failIfNoSpecifiedTests=false -Dtest=CcdiIntermediaryServiceImplTest,CcdiIntermediaryMapperTest,CcdiIntermediaryControllerTest,CcdiIntermediaryPersonImportServiceImplTest,CcdiIntermediaryEnterpriseRelationImportServiceImplTest test`
结果PASS
- Maven 命令:
`mvn -pl ccdi-info-collection -am clean compile`
结果PASSBUILD SUCCESS