Files
ccdi/docs/plans/backend/2026-03-23-credit-info-maintenance-backend-implementation.md
2026-03-23 22:39:15 +08:00

26 KiB
Raw Blame History

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_infoccdi_credit_negative_info,并提供列表、详情、删除接口。

Architecture: 业务入口放在 ccdi-info-collection,由独立 CcdiCreditInfoController 暴露上传、列表、详情、删除接口。上传链路直接复用 ccdi-lsfxCreditParseClient,通过 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_infoccdi_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_debtlx_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:

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 中创建:

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 中新增:

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:

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 脚本

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 先写最小测试骨架,显式引用待创建的对象:

class CcdiCreditInfoServiceImplTest {

    @Test
    void shouldCompileCreditInfoContracts() {
        CcdiCreditInfoQueryDTO queryDTO = new CcdiCreditInfoQueryDTO();
        CreditInfoUploadResultVO resultVO = new CreditInfoUploadResultVO();
        assertNotNull(queryDTO);
        assertNotNull(resultVO);
    }
}
  • Step 2: 运行测试确认失败

Run:

mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test

Expected:

  • FAIL提示相关 DTO / VO 不存在,且 ccdi-info-collection 还不能解析 CreditParseClient 依赖。

  • Step 3: 增加模块依赖

ccdi-info-collection/pom.xml 中追加:

<dependency>
    <groupId>com.ruoyi</groupId>
    <artifactId>ccdi-lsfx</artifactId>
    <version>3.9.1</version>
</dependency>
  • Step 4: 建立实体与 VO 骨架

实体最小结构示例:

@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 最小结构:

@Data
public class CcdiCreditInfoQueryDTO {
    private String name;
    private String staffId;
    private String idCard;
    private String maintained;
}
  • Step 5: 建立 Mapper 接口骨架

至少补齐如下方法:

List<CcdiDebtsInfo> selectByPersonId(String personId);
int deleteByPersonId(String personId);
int insertBatch(@Param("list") List<CcdiDebtsInfo> list);
CreditInfoListVO selectCreditInfoSummaryByPersonId(String personId);
  • Step 6: 运行测试确认通过

Run:

mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test

Expected:

  • PASS合同对象已补齐模块可成功编译测试。

  • Step 7: 提交对象骨架

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 组映射:

@Test
void shouldConvertDebtPayloadToSevenTypedRows() {
    CreditParsePayload payload = new CreditParsePayload();
    Map<String, Object> 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<CcdiDebtsInfo> 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: 再写负面信息失败用例
@Test
void shouldBuildNegativeInfoFromPublicTypePayload() {
    CreditParsePayload payload = new CreditParsePayload();
    Map<String, Object> 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:

mvn -pl ccdi-info-collection -Dtest=CreditInfoPayloadAssemblerTest test

Expected:

  • FAIL提示 CreditInfoPayloadAssembler 不存在。

  • Step 4: 编写最小装配实现

示例结构:

@Component
public class CreditInfoPayloadAssembler {

    public List<CcdiDebtsInfo> buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) {
        List<CcdiDebtsInfo> 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<String, Object> 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 值过滤断言

再追加测试:

@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:

mvn -pl ccdi-info-collection -Dtest=CreditInfoPayloadAssemblerTest test

Expected:

  • PASS

  • Step 7: 提交装配器

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 中覆盖“一个员工失败不影响其他员工”:

@Test
void uploadHtmlFiles_shouldIsolateFailuresPerEmployee() {
    MockMultipartFile successFile = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
    MockMultipartFile failFile = new MockMultipartFile("files", "b.html", "text/html", "<html>b</html>".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: 再写“旧日期拒绝覆盖”失败用例
@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:

mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test

Expected:

  • FAIL提示 ICcdiCreditInfoService / CcdiCreditInfoServiceImpl 不存在或上传方法未实现。

  • Step 4: 实现最小上传服务

服务核心流程:

@Transactional(rollbackFor = Exception.class)
public void replaceEmployeeCredit(String personId, List<CcdiDebtsInfo> 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: 补充“删除旧数据再插新数据”的验证

追加断言:

verify(debtsInfoMapper).deleteByPersonId("330101199001010011");
verify(negativeInfoMapper).deleteByPersonId("330101199001010011");
verify(debtsInfoMapper).insertBatch(anyList());
verify(negativeInfoMapper).insert(any(CcdiCreditNegativeInfo.class));
  • Step 6: 运行测试确认通过

Run:

mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoServiceImplTest test

Expected:

  • PASS

  • Step 7: 提交上传服务

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: 先写控制器失败用例

@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:

mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoControllerTest test

Expected:

  • FAIL提示 CcdiCreditInfoController 不存在。

  • Step 3: 编写最小聚合 SQL

CcdiCreditInfoQueryMapper.xml 中实现员工维度列表聚合:

<select id="selectCreditInfoPage" resultType="com.ruoyi.info.collection.domain.vo.CreditInfoListVO">
    SELECT
        s.staff_id,
        s.name,
        s.id_card,
        s.dept_name,
        debtAgg.query_date,
        IFNULL(debtAgg.debt_count, 0) AS debtCount,
        IFNULL(debtAgg.debt_total_amount, 0) AS debtTotalAmount,
        IFNULL(neg.civil_cnt, 0) AS civilCnt,
        IFNULL(neg.enforce_cnt, 0) AS enforceCnt,
        IFNULL(neg.adm_cnt, 0) AS admCnt
    FROM ccdi_base_staff s
    LEFT JOIN (
        SELECT person_id, 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
    ) debtAgg ON debtAgg.person_id = s.id_card
    LEFT JOIN ccdi_credit_negative_info neg ON neg.person_id = s.id_card
</select>
  • Step 4: 编写控制器

接口定义:

@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:

mvn -pl ccdi-info-collection -Dtest=CcdiCreditInfoControllerTest test

Expected:

  • PASS

  • Step 6: 做一次模块回归

Run:

mvn -pl ccdi-info-collection -am test -Dtest=CreditInfoPayloadAssemblerTest,CcdiCreditInfoServiceImplTest,CcdiCreditInfoControllerTest
mvn -pl ccdi-info-collection -am compile

Expected:

  • PASS测试与编译均通过。

  • Step 7: 提交接口与聚合查询

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:

cd lsfx-mock-server
python3 main.py

Expected:

  • 本地监听 http://localhost:8000

  • Step 2: 启动后端并执行上传联调

Run:

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:

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: 提交实施记录

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 <sql-file>,不要直接手写 mysql -e
  • 联调完成后要主动关闭 ruoyi-adminlsfx-mock-server 进程,避免残留端口。