# Credit Info Maintenance 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:** 在 `ccdi-info-collection` 模块中新增征信维护后端能力,支持批量上传征信 HTML、调用现有 `ccdi-lsfx` 征信解析客户端、按员工维度覆盖写入 `ccdi_debts_info` 与 `ccdi_credit_negative_info`,并提供列表、详情、删除接口。 **Architecture:** 业务入口放在 `ccdi-info-collection`,由独立 `CcdiCreditInfoController` 暴露上传、列表、详情、删除接口。上传链路直接复用 `ccdi-lsfx` 的 `CreditParseClient`,通过 `query_cert_no` 归户到员工,并由独立 payload 装配器把 `lx_debt` 转成负债明细、把 `lx_publictype` 转成负面信息;最终按员工身份证号在单事务内先删后插,保证“只保留最新征信”。 **Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, Jackson, JUnit 5, Mockito, Maven, MySQL --- ## 文件结构与职责 **新增文件** - `sql/migration/2026-03-23-create-credit-info-tables.sql` 创建 `ccdi_debts_info` 与 `ccdi_credit_negative_info` 两张业务表。 - `sql/ccdi_credit_info_menu.sql` 在“信息维护”下新增“征信维护”菜单与按钮权限。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiDebtsInfo.java` 负债明细实体。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiCreditNegativeInfo.java` 负面信息实体。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiCreditInfoQueryDTO.java` 列表查询条件 DTO。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoListVO.java` 员工维度摘要列表 VO。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoDetailVO.java` 征信详情聚合 VO。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoNegativeVO.java` 负面信息展示 VO。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoUploadResultVO.java` 批量上传结果 VO。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoUploadFailureVO.java` 单文件失败结果 VO。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiDebtsInfoMapper.java` 负债表增删查 Mapper。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiCreditNegativeInfoMapper.java` 负面信息表增删查 Mapper。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapper.java` 员工维度摘要列表与详情聚合查询 Mapper。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiCreditInfoService.java` 征信维护业务服务接口。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java` 征信维护业务服务实现。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssembler.java` 负责把 `CreditParseResponse` 装配成负债明细和负面信息对象。 - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiCreditInfoController.java` 征信维护上传、列表、详情、删除接口。 - `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml` 聚合查询 SQL。 - `docs/reports/implementation/2026-03-23-credit-info-maintenance-backend-implementation.md` 后端实施记录。 **修改文件** - `ccdi-info-collection/pom.xml` 显式引入 `ccdi-lsfx` 依赖,允许业务服务直接复用 `CreditParseClient`。 **测试文件** - `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssemblerTest.java` 锁定 `lx_debt` 与 `lx_publictype` 的映射规则。 - `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java` 锁定批量上传、最新征信覆盖、单员工失败隔离与删除行为。 - `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiCreditInfoControllerTest.java` 锁定接口入参、分页、删除与返回结构。 **参考文件** - `docs/design/2026-03-23-credit-info-maintenance-design.md` - `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java` - `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java` - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiBaseStaffController.java` - `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiAssetInfoImportServiceImpl.java` - `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiBaseStaffMapper.xml` ## Task 1: 建表脚本与菜单脚本 **Files:** - Create: `sql/migration/2026-03-23-create-credit-info-tables.sql` - Create: `sql/ccdi_credit_info_menu.sql` - Review: `assets/征信解析/ccdi_debts_info.xlsx` - Review: `docs/design/2026-03-23-credit-info-maintenance-design.md` - [ ] **Step 1: 先写脚本契约检查命令** Run: ```bash test -f sql/migration/2026-03-23-create-credit-info-tables.sql ``` Expected: - FAIL,因为建表脚本尚不存在。 - [ ] **Step 2: 编写两张业务表脚本** 在 `sql/migration/2026-03-23-create-credit-info-tables.sql` 中创建: ```sql CREATE TABLE `ccdi_debts_info` ( `debt_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `person_id` VARCHAR(18) NOT NULL COMMENT '员工身份证号', `person_name` VARCHAR(100) DEFAULT NULL COMMENT '员工姓名', `query_date` DATE DEFAULT NULL COMMENT '征信查询日期', `debt_main_type` VARCHAR(50) DEFAULT NULL COMMENT '负债大类', `debt_sub_type` VARCHAR(50) DEFAULT NULL COMMENT '负债小类', `creditor_type` VARCHAR(50) DEFAULT NULL COMMENT '债权人类型', `debt_name` VARCHAR(100) DEFAULT NULL COMMENT '负债名称', `principal_balance` DECIMAL(18,2) DEFAULT NULL COMMENT '负债本金余额', `debt_total_amount` DECIMAL(18,2) DEFAULT NULL COMMENT '负债总额', `debt_status` VARCHAR(20) DEFAULT NULL COMMENT '负债状态', `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`debt_id`), KEY `idx_person_id` (`person_id`), KEY `idx_query_date` (`query_date`), KEY `idx_person_query_date` (`person_id`, `query_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工征信负债明细'; CREATE TABLE `ccdi_credit_negative_info` ( `negative_id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `person_id` VARCHAR(18) NOT NULL COMMENT '员工身份证号', `person_name` VARCHAR(100) DEFAULT NULL COMMENT '员工姓名', `query_date` DATE DEFAULT NULL COMMENT '征信查询日期', `civil_cnt` INT DEFAULT 0 COMMENT '民事案件笔数', `enforce_cnt` INT DEFAULT 0 COMMENT '强制执行笔数', `adm_cnt` INT DEFAULT 0 COMMENT '行政处罚笔数', `civil_lmt` DECIMAL(18,2) DEFAULT 0 COMMENT '民事案件金额', `enforce_lmt` DECIMAL(18,2) DEFAULT 0 COMMENT '强制执行金额', `adm_lmt` DECIMAL(18,2) DEFAULT 0 COMMENT '行政处罚金额', `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`negative_id`), UNIQUE KEY `uk_person_id` (`person_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工征信负面信息'; ``` - [ ] **Step 3: 编写菜单与权限脚本** 在 `sql/ccdi_credit_info_menu.sql` 中新增: ```sql SET @parent_menu_id = (SELECT menu_id FROM sys_menu WHERE menu_name='信息维护' AND parent_id=0 LIMIT 1); INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES ('征信维护', @parent_menu_id, 4, 'creditInfo', 'ccdiCreditInfo/index', 1, 0, 'C', '0', '0', 'ccdi:creditInfo:list', 'document', 'admin', NOW(), '', NULL, '员工征信维护菜单'); SET @menu_id = LAST_INSERT_ID(); INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark) VALUES ('征信查询', @menu_id, 1, '', '', 1, 0, 'F', '0', '0', 'ccdi:creditInfo:query', '#', 'admin', NOW(), ''), ('征信上传', @menu_id, 2, '', '', 1, 0, 'F', '0', '0', 'ccdi:creditInfo:upload', '#', 'admin', NOW(), ''), ('征信删除', @menu_id, 3, '', '', 1, 0, 'F', '0', '0', 'ccdi:creditInfo:remove', '#', 'admin', NOW(), ''); ``` - [ ] **Step 4: 检查脚本关键字** Run: ```bash rg -n "ccdi_debts_info|ccdi_credit_negative_info|征信维护|ccdi:creditInfo" sql/migration/2026-03-23-create-credit-info-tables.sql sql/ccdi_credit_info_menu.sql ``` Expected: - PASS,能看到两张表名、菜单名和权限前缀。 - [ ] **Step 5: 提交 SQL 脚本** ```bash git add sql/migration/2026-03-23-create-credit-info-tables.sql sql/ccdi_credit_info_menu.sql git commit -m "新增征信维护建表与菜单脚本" ``` ## Task 2: 接入 `ccdi-lsfx` 依赖并建立对象骨架 **Files:** - Modify: `ccdi-info-collection/pom.xml` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiDebtsInfo.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/CcdiCreditNegativeInfo.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/dto/CcdiCreditInfoQueryDTO.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoListVO.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoDetailVO.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoNegativeVO.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoUploadResultVO.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/vo/CreditInfoUploadFailureVO.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiDebtsInfoMapper.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiCreditNegativeInfoMapper.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/CcdiCreditInfoQueryMapper.java` - [ ] **Step 1: 先写编译失败用例** 在 `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java` 先写最小测试骨架,显式引用待创建的对象: ```java class CcdiCreditInfoServiceImplTest { @Test void shouldCompileCreditInfoContracts() { CcdiCreditInfoQueryDTO queryDTO = new CcdiCreditInfoQueryDTO(); CreditInfoUploadResultVO resultVO = new CreditInfoUploadResultVO(); assertNotNull(queryDTO); assertNotNull(resultVO); } } ``` - [ ] **Step 2: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test ``` Expected: - FAIL,提示相关 DTO / VO 不存在,且 `ccdi-info-collection` 还不能解析 `CreditParseClient` 依赖。 - [ ] **Step 3: 增加模块依赖** 在 `ccdi-info-collection/pom.xml` 中追加: ```xml com.ruoyi ccdi-lsfx 3.9.1 ``` - [ ] **Step 4: 建立实体与 VO 骨架** 实体最小结构示例: ```java @Data @TableName("ccdi_debts_info") public class CcdiDebtsInfo { @TableId(type = IdType.AUTO) private Long debtId; private String personId; private String personName; private Date queryDate; private String debtMainType; private String debtSubType; private String creditorType; private String debtName; private BigDecimal principalBalance; private BigDecimal debtTotalAmount; private String debtStatus; private String createBy; private Date createTime; private String updateBy; private Date updateTime; } ``` 查询 DTO 最小结构: ```java @Data public class CcdiCreditInfoQueryDTO { private String name; private String staffId; private String idCard; private String maintained; } ``` - [ ] **Step 5: 建立 Mapper 接口骨架** 至少补齐如下方法: ```java List selectByPersonId(String personId); int deleteByPersonId(String personId); int insertBatch(@Param("list") List list); CreditInfoListVO selectCreditInfoSummaryByPersonId(String personId); ``` - [ ] **Step 6: 运行测试确认通过** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test ``` Expected: - PASS,合同对象已补齐,模块可成功编译测试。 - [ ] **Step 7: 提交对象骨架** ```bash git add ccdi-info-collection/pom.xml ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java git commit -m "新增征信维护对象与依赖骨架" ``` ## Task 3: 实现 payload 装配器 **Files:** - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssembler.java` - Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssemblerTest.java` - [ ] **Step 1: 先写负债映射失败用例** 在 `CreditInfoPayloadAssemblerTest.java` 中先锁定 `lx_debt` 的 7 组映射: ```java @Test void shouldConvertDebtPayloadToSevenTypedRows() { CreditParsePayload payload = new CreditParsePayload(); Map debt = new HashMap<>(); debt.put("uncle_bank_house_bal", "50000"); debt.put("uncle_bank_house_lmt", "100000"); debt.put("uncle_bank_house_state", "正常"); debt.put("uncle_not_bank_bal", "2000"); debt.put("uncle_not_bank_lmt", "3000"); debt.put("uncle_not_bank_state", "逾期"); payload.setLxDebt(debt); List rows = assembler.buildDebts("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload); assertEquals(2, rows.size()); assertEquals("住房贷款", rows.get(0).getDebtSubType()); assertEquals("非银", rows.get(1).getCreditorType()); } ``` - [ ] **Step 2: 再写负面信息失败用例** ```java @Test void shouldBuildNegativeInfoFromPublicTypePayload() { CreditParsePayload payload = new CreditParsePayload(); Map publictype = new HashMap<>(); publictype.put("civil_cnt", 2); publictype.put("civil_lmt", "9800"); payload.setLxPublictype(publictype); CcdiCreditNegativeInfo info = assembler.buildNegative("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload); assertEquals(2, info.getCivilCnt()); assertEquals(new BigDecimal("9800"), info.getCivilLmt()); } ``` - [ ] **Step 3: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CreditInfoPayloadAssemblerTest test ``` Expected: - FAIL,提示 `CreditInfoPayloadAssembler` 不存在。 - [ ] **Step 4: 编写最小装配实现** 示例结构: ```java @Component public class CreditInfoPayloadAssembler { public List buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) { List rows = new ArrayList<>(); rows.add(buildDebtRow("uncle_bank_house", "银行", "住房贷款", "银行", "未结清银行住房贷款", personId, personName, queryDate, payload.getLxDebt())); // 其余 6 组同理 return rows.stream().filter(Objects::nonNull).toList(); } public CcdiCreditNegativeInfo buildNegative(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) { Map source = payload.getLxPublictype(); CcdiCreditNegativeInfo info = new CcdiCreditNegativeInfo(); info.setPersonId(personId); info.setPersonName(personName); info.setQueryDate(java.sql.Date.valueOf(queryDate)); info.setCivilCnt(toInteger(source.get("civil_cnt"))); return info; } } ``` - [ ] **Step 5: 补上 0 值过滤断言** 再追加测试: ```java @Test void shouldSkipDebtRowWhenAllMetricsAreEmpty() { CreditParsePayload payload = new CreditParsePayload(); payload.setLxDebt(new HashMap<>()); assertTrue(assembler.buildDebts("3301", "张三", LocalDate.parse("2026-03-01"), payload).isEmpty()); } ``` - [ ] **Step 6: 运行测试确认通过** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CreditInfoPayloadAssemblerTest test ``` Expected: - PASS - [ ] **Step 7: 提交装配器** ```bash git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssembler.java ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/support/CreditInfoPayloadAssemblerTest.java git commit -m "新增征信解析结果装配器" ``` ## Task 4: 实现批量上传与最新征信覆盖服务 **Files:** - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiCreditInfoService.java` - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java` - Modify: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java` - [ ] **Step 1: 先写批量上传失败用例** 在 `CcdiCreditInfoServiceImplTest.java` 中覆盖“一个员工失败不影响其他员工”: ```java @Test void uploadHtmlFiles_shouldIsolateFailuresPerEmployee() { MockMultipartFile successFile = new MockMultipartFile("files", "a.html", "text/html", "a".getBytes(StandardCharsets.UTF_8)); MockMultipartFile failFile = new MockMultipartFile("files", "b.html", "text/html", "b".getBytes(StandardCharsets.UTF_8)); when(creditParseClient.parse(anyString(), anyString(), any(File.class))) .thenReturn(successResponse("330101199001010011", "张三", "2026-03-03")) .thenThrow(new RuntimeException("征信解析失败")); CreditInfoUploadResultVO result = service.upload(Arrays.asList(successFile, failFile)); assertEquals(1, result.getSuccessCount()); assertEquals(1, result.getFailureCount()); assertEquals("征信解析失败", result.getFailures().get(0).getReason()); } ``` - [ ] **Step 2: 再写“旧日期拒绝覆盖”失败用例** ```java @Test void uploadHtmlFiles_shouldRejectOlderReportDate() { when(queryMapper.selectLatestQueryDate("330101199001010011")) .thenReturn(LocalDate.parse("2026-03-05")); CreditInfoUploadResultVO result = service.upload(List.of(file)); assertEquals(0, result.getSuccessCount()); assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason()); } ``` - [ ] **Step 3: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test ``` Expected: - FAIL,提示 `ICcdiCreditInfoService` / `CcdiCreditInfoServiceImpl` 不存在或上传方法未实现。 - [ ] **Step 4: 实现最小上传服务** 服务核心流程: ```java @Transactional(rollbackFor = Exception.class) public void replaceEmployeeCredit(String personId, List debts, CcdiCreditNegativeInfo negative, String userName) { debtsInfoMapper.deleteByPersonId(personId); negativeInfoMapper.deleteByPersonId(personId); if (!debts.isEmpty()) { debts.forEach(item -> { item.setCreateBy(userName); item.setUpdateBy(userName); }); debtsInfoMapper.insertBatch(debts); } negative.setCreateBy(userName); negative.setUpdateBy(userName); negativeInfoMapper.insert(negative); } ``` 批量上传方法要求: - 遍历 `MultipartFile[]` - 非 `.html/.htm` 文件直接记失败 - 通过 `CreditParseClient.parse("LXCUSTALL", "PERSON", tempFile)` 解析 - 用 `query_cert_no` 查询员工是否存在 - 用 `report_time` 判断是否为最新 - 同一员工事务内执行覆盖写入 - 聚合成功数、失败数、失败清单 - [ ] **Step 5: 补充“删除旧数据再插新数据”的验证** 追加断言: ```java verify(debtsInfoMapper).deleteByPersonId("330101199001010011"); verify(negativeInfoMapper).deleteByPersonId("330101199001010011"); verify(debtsInfoMapper).insertBatch(anyList()); verify(negativeInfoMapper).insert(any(CcdiCreditNegativeInfo.class)); ``` - [ ] **Step 6: 运行测试确认通过** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test ``` Expected: - PASS - [ ] **Step 7: 提交上传服务** ```bash git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/ICcdiCreditInfoService.java 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 5: 实现列表聚合、详情与删除接口 **Files:** - Create: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiCreditInfoController.java` - Create: `ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml` - Create: `ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiCreditInfoControllerTest.java` - [ ] **Step 1: 先写控制器失败用例** ```java @Test void list_shouldDelegateWithPageRequest() { when(service.selectCreditInfoPage(any(), any())).thenReturn(new Page<>(1, 10, 0)); TableDataInfo result = controller.list(new CcdiCreditInfoQueryDTO()); assertEquals(0L, result.getTotal()); } @Test void remove_shouldCallDeleteByPersonId() { when(service.deleteByPersonId("330101199001010011")).thenReturn(1); AjaxResult result = controller.remove("330101199001010011"); assertEquals(200, result.get("code")); } ``` - [ ] **Step 2: 运行测试确认失败** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoControllerTest test ``` Expected: - FAIL,提示 `CcdiCreditInfoController` 不存在。 - [ ] **Step 3: 编写最小聚合 SQL** 在 `CcdiCreditInfoQueryMapper.xml` 中实现员工维度列表聚合: ```xml ``` - [ ] **Step 4: 编写控制器** 接口定义: ```java @PostMapping("/upload") public AjaxResult upload(@RequestParam("files") MultipartFile[] files) { ... } @GetMapping("/list") public TableDataInfo list(CcdiCreditInfoQueryDTO queryDTO) { ... } @GetMapping("/{personId}") public AjaxResult detail(@PathVariable String personId) { ... } @DeleteMapping("/{personId}") public AjaxResult remove(@PathVariable String personId) { ... } ``` 权限要求: - `ccdi:creditInfo:list` - `ccdi:creditInfo:query` - `ccdi:creditInfo:upload` - `ccdi:creditInfo:remove` - [ ] **Step 5: 运行测试确认通过** Run: ```bash mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoControllerTest test ``` Expected: - PASS - [ ] **Step 6: 做一次模块回归** Run: ```bash mvn -pl ccdi-info-collection -am test -Dtest=CreditInfoPayloadAssemblerTest,CcdiCreditInfoServiceImplTest,CcdiCreditInfoControllerTest mvn -pl ccdi-info-collection -am compile ``` Expected: - PASS,测试与编译均通过。 - [ ] **Step 7: 提交接口与聚合查询** ```bash git add ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/CcdiCreditInfoController.java ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiCreditInfoQueryMapper.xml ccdi-info-collection/src/test/java/com/ruoyi/info/collection/controller/CcdiCreditInfoControllerTest.java git commit -m "新增征信维护查询与接口" ``` ## Task 6: 联调验证与实施记录 **Files:** - Create: `docs/reports/implementation/2026-03-23-credit-info-maintenance-backend-implementation.md` - [ ] **Step 1: 启动 Mock 服务** Run: ```bash cd lsfx-mock-server python3 main.py ``` Expected: - 本地监听 `http://localhost:8000` - [ ] **Step 2: 启动后端并执行上传联调** Run: ```bash mvn -pl ruoyi-admin -am spring-boot:run curl -F 'files=@assets/征信解析员工样本/0001_徐伟_2040.html;type=text/html' http://127.0.0.1:62318/ccdi/creditInfo/upload ``` Expected: - 返回 `AjaxResult.code = 200` - `data.successCount = 1` - `data.failureCount = 0` - [ ] **Step 3: 验证列表、详情、删除接口** Run: ```bash curl 'http://127.0.0.1:62318/ccdi/creditInfo/list?pageNum=1&pageSize=10' curl 'http://127.0.0.1:62318/ccdi/creditInfo/320101199001010030' curl -X DELETE 'http://127.0.0.1:62318/ccdi/creditInfo/320101199001010030' ``` Expected: - 列表能返回员工维度摘要 - 详情能返回负债列表和负面信息 - 删除后再次查询详情应为空或提示未维护征信 - [ ] **Step 4: 记录实施结果** 在实施记录中至少写明: - 实际修改文件 - 关键测试命令与结果 - 使用 `bin/mysql_utf8_exec.sh` 执行 SQL 的命令 - 启停的后端与 mock 进程及已停止说明 - [ ] **Step 5: 提交实施记录** ```bash git add docs/reports/implementation/2026-03-23-credit-info-maintenance-backend-implementation.md git commit -m "补充征信维护后端实施记录" ``` ## Review Notes - 仓库协作约定禁止开启 subagent,因此本计划执行时使用 `superpowers:executing-plans`,不走子代理审阅。 - SQL 执行必须使用 `bin/mysql_utf8_exec.sh `,不要直接手写 `mysql -e`。 - 联调完成后要主动关闭 `ruoyi-admin` 与 `lsfx-mock-server` 进程,避免残留端口。