Files
ccdi/docs/plans/backend/2026-03-23-credit-parse-client-backend-implementation.md

16 KiB
Raw Blame History

Credit Parse Client 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-lsfx 模块中新增独立征信解析 Client 和联调接口 POST /lsfx/credit/parse,通过独立配置调用外部征信解析服务。

Architecture: 复用 ccdi-lsfx 现有 HttpUtil 与异常体系,但不复用 LsfxAnalysisClientlsfx.api.* 配置。控制器负责最小参数校验和临时文件转换,独立 CreditParseClient 负责 multipart/form-data 调用与响应映射,结果以 AjaxResult.success(response) 形式透传。

Tech Stack: Java 21, Spring Boot 3, Spring MVC, Jackson, RestTemplate, JUnit 5, Mockito, Maven


文件结构与职责

新增文件

  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java 负责读取 credit-parse.api.url、组装 multipart/form-data、调用征信解析服务、记录日志。
  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/CreditParseController.java 负责接收 HTML 文件和可选参数、做最小校验、转换临时文件、调用 CreditParseClient、返回 AjaxResult
  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java 映射外部返回的 messagestatus_codepayload
  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayload.java 按三个主题域承载 payload,使用 Map<String, Object> 保存字段。
  • ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java 校验 CreditParseClient 的参数组装、URL 读取和异常传播。
  • ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java 校验控制器参数默认值、文件校验、成功透传和异常兜底。
  • docs/reports/implementation/2026-03-23-credit-parse-client-implementation.md 记录本次后端实施实际改动、测试命令和结果。

修改文件

  • ccdi-lsfx/pom.xml 补充测试依赖 spring-boot-starter-test
  • ruoyi-admin/src/main/resources/application-dev.yml 新增 credit-parse.api.url 配置。
  • ruoyi-admin/src/main/resources/application-nas.yml 新增 credit-parse.api.url 配置。

参考文件

  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java
  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/LsfxTestController.java
  • ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.java
  • docs/design/2026-03-23-credit-parse-client-design.md

Task 1: 补齐 ccdi-lsfx 测试基础

Files:

  • Modify: ccdi-lsfx/pom.xml

  • Create: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java

  • Create: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java

  • Step 1: 为 ccdi-lsfx 添加测试依赖

ccdi-lsfx/pom.xml 追加测试依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  • Step 2: 先写控制器失败用例

ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java 写出最小失败用例,覆盖空文件与非法后缀:

@Test
void parse_shouldRejectEmptyFile() {
    AjaxResult result = controller.parse(null, null, null);
    assertEquals(500, result.get("code"));
}

@Test
void parse_shouldRejectNonHtmlFile() {
    MockMultipartFile file = new MockMultipartFile(
        "file", "credit.pdf", "application/pdf", "x".getBytes(StandardCharsets.UTF_8)
    );
    AjaxResult result = controller.parse(file, null, null);
    assertEquals(500, result.get("code"));
}
  • Step 3: 运行测试确认失败

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseControllerTest test

Expected:

  • 编译失败,提示 CreditParseControllerparse(...) 不存在。

  • Step 4: 提交前置依赖与测试骨架

git add ccdi-lsfx/pom.xml ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java
git commit -m "新增征信解析测试基础"

Task 2: 实现响应对象

Files:

  • Create: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java

  • Create: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayload.java

  • Test: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java

  • Step 1: 先写响应映射测试

CreditParseClientTest 中先写 Jackson 反序列化测试:

@Test
void shouldDeserializeCreditParseResponse() throws Exception {
    String json = """
        {
          "message": "成功",
          "status_code": "0",
          "payload": {
            "lx_header": {"query_cert_no": "3301"},
            "lx_debt": {"uncle_bank_house_bal": "12.00"},
            "lx_publictype": {"civil_cnt": 1}
          }
        }
        """;

    CreditParseResponse response = objectMapper.readValue(json, CreditParseResponse.class);

    assertEquals("0", response.getStatusCode());
    assertEquals("3301", response.getPayload().getLxHeader().get("query_cert_no"));
}
  • Step 2: 运行测试确认失败

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseClientTest#shouldDeserializeCreditParseResponse test

Expected:

  • FAIL提示 CreditParseResponseCreditParsePayload 不存在。

  • Step 3: 编写最小响应对象

CreditParseResponse.java 中实现:

@Data
public class CreditParseResponse {

    private String message;

    @JsonProperty("status_code")
    private String statusCode;

    private CreditParsePayload payload;
}

CreditParsePayload.java 中实现:

@Data
public class CreditParsePayload {

    @JsonProperty("lx_header")
    private Map<String, Object> lxHeader;

    @JsonProperty("lx_debt")
    private Map<String, Object> lxDebt;

    @JsonProperty("lx_publictype")
    private Map<String, Object> lxPublictype;
}
  • Step 4: 运行测试确认通过

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseClientTest#shouldDeserializeCreditParseResponse test

Expected:

  • PASS

  • Step 5: 提交响应对象

git add ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayload.java ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java
git commit -m "新增征信解析响应对象"

Task 3: 实现 CreditParseClient

Files:

  • Create: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java

  • Modify: ruoyi-admin/src/main/resources/application-dev.yml

  • Modify: ruoyi-admin/src/main/resources/application-nas.yml

  • Test: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java

  • Step 1: 先写 Client 成功用例

CreditParseClientTest 中用 Mockito 模拟 HttpUtil

@Test
void shouldCallConfiguredUrlWithMultipartParams() {
    File file = new File("sample.html");
    CreditParseResponse response = new CreditParseResponse();
    response.setStatusCode("0");

    when(httpUtil.uploadFile(eq("http://credit-host/xfeature-mngs/conversation/htmlEval"), anyMap(), isNull(), eq(CreditParseResponse.class)))
        .thenReturn(response);

    CreditParseResponse actual = client.parse("LXCUSTALL", "PERSON", file);

    assertEquals("0", actual.getStatusCode());
    verify(httpUtil).uploadFile(eq("http://credit-host/xfeature-mngs/conversation/htmlEval"), argThat(params ->
        "LXCUSTALL".equals(params.get("model"))
            && "PERSON".equals(params.get("hType"))
            && file.equals(params.get("file"))
    ), isNull(), eq(CreditParseResponse.class));
}
  • Step 2: 再写 Client 异常传播用例
@Test
void shouldWrapHttpErrorsAsLsfxApiException() {
    when(httpUtil.uploadFile(anyString(), anyMap(), isNull(), eq(CreditParseResponse.class)))
        .thenThrow(new LsfxApiException("网络失败"));

    assertThrows(LsfxApiException.class,
        () -> client.parse("LXCUSTALL", "PERSON", new File("sample.html")));
}
  • Step 3: 运行测试确认失败

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseClientTest test

Expected:

  • FAIL提示 CreditParseClient 不存在。

  • Step 4: 实现 CreditParseClient

CreditParseClient.java 中实现最小调用逻辑:

@Slf4j
@Component
public class CreditParseClient {

    @Resource
    private HttpUtil httpUtil;

    @Value("${credit-parse.api.url}")
    private String creditParseUrl;

    public CreditParseResponse parse(String model, String hType, File file) {
        Map<String, Object> params = new HashMap<>();
        params.put("model", model);
        params.put("hType", hType);
        params.put("file", file);
        return httpUtil.uploadFile(creditParseUrl, params, null, CreditParseResponse.class);
    }
}

补充日志和异常包装,保持 LsfxAnalysisClient 风格:

  • 请求开始记录文件名、modelhType

  • 请求结束记录耗时和 statusCode

  • 异常时抛 LsfxApiException("征信解析调用失败: ...", e)

  • Step 5: 增加独立配置

ruoyi-admin/src/main/resources/application-dev.ymlruoyi-admin/src/main/resources/application-nas.yml 增加:

credit-parse:
  api:
    url: http://64.202.94.120:8081/xfeature-mngs/conversation/htmlEval
  • Step 6: 运行测试确认通过

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseClientTest test

Expected:

  • PASS

  • Step 7: 提交 Client 与配置

git add ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java ruoyi-admin/src/main/resources/application-dev.yml ruoyi-admin/src/main/resources/application-nas.yml ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java
git commit -m "新增征信解析客户端"

Task 4: 实现 CreditParseController

Files:

  • Create: ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/CreditParseController.java

  • Test: ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java

  • Step 1: 先写控制器成功透传用例

@Test
void shouldUseDefaultModelAndTypeWhenMissing() {
    MockMultipartFile file = new MockMultipartFile(
        "file", "credit.html", "text/html", "<html/>".getBytes(StandardCharsets.UTF_8)
    );
    CreditParseResponse response = new CreditParseResponse();
    response.setStatusCode("0");

    when(client.parse(eq("LXCUSTALL"), eq("PERSON"), any(File.class))).thenReturn(response);

    AjaxResult result = controller.parse(file, null, null);

    assertEquals(200, result.get("code"));
    assertSame(response, result.get("data"));
}
  • Step 2: 再写异常兜底用例
@Test
void shouldReturnAjaxErrorWhenClientThrows() {
    MockMultipartFile file = new MockMultipartFile(
        "file", "credit.html", "text/html", "<html/>".getBytes(StandardCharsets.UTF_8)
    );
    when(client.parse(anyString(), anyString(), any(File.class)))
        .thenThrow(new LsfxApiException("超时"));

    AjaxResult result = controller.parse(file, null, null);

    assertEquals(500, result.get("code"));
}
  • Step 3: 运行测试确认失败

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseControllerTest test

Expected:

  • FAIL提示 CreditParseController 不存在。

  • Step 4: 编写控制器最小实现

控制器结构按 LsfxTestController 风格实现:

@Tag(name = "征信解析接口测试", description = "用于测试征信解析接口")
@Anonymous
@RestController
@RequestMapping("/lsfx/credit")
public class CreditParseController {

    @Resource
    private CreditParseClient creditParseClient;

    @PostMapping("/parse")
    public AjaxResult parse(@RequestParam("file") MultipartFile file,
                            @RequestParam(required = false) String model,
                            @RequestParam(required = false) String hType) {
        // 参数校验
        // 默认值补齐
        // 临时文件转换
        // 调用 client
        // finally 删除临时文件
    }
}

校验规则固定为:

  • file == nullfile.isEmpty() -> AjaxResult.error("征信HTML文件不能为空")

  • originalFilename 为空 -> AjaxResult.error("文件名不能为空")

  • 不是 .html/.htm -> AjaxResult.error("仅支持 HTML 格式文件")

  • model 为空 -> LXCUSTALL

  • hType 为空 -> PERSON

  • Step 5: 运行测试确认通过

Run:

mvn -pl ccdi-lsfx -Dtest=CreditParseControllerTest test

Expected:

  • PASS

  • Step 6: 提交控制器

git add ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/CreditParseController.java ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java
git commit -m "新增征信解析联调接口"

Task 5: 做模块级回归验证

Files:

  • Modify: docs/reports/implementation/2026-03-23-credit-parse-client-implementation.md

  • Step 1: 运行 ccdi-lsfx 测试

Run:

mvn -pl ccdi-lsfx test

Expected:

  • CreditParseClientTest PASS

  • CreditParseControllerTest PASS

  • Step 2: 运行模块编译验证

Run:

mvn -pl ccdi-lsfx -am compile

Expected:

  • BUILD SUCCESS

  • Step 3: 手工联调检查 Swagger 与接口

Run:

mvn -pl ruoyi-admin -am spring-boot:run

打开:

  • http://localhost:62318/swagger-ui.html

检查:

  • 存在 POST /lsfx/credit/parse
  • 上传 .html 文件时能返回 AjaxResult

测试完成后停止进程。

  • Step 4: 记录实施结果

docs/reports/implementation/2026-03-23-credit-parse-client-implementation.md 写入:

  • 实际修改文件列表
  • 执行的测试命令
  • 测试结果
  • 是否完成 Swagger 联调

建议结构:

# 征信解析客户端实施记录

## 1. 改动概述

## 2. 修改文件

## 3. 测试记录

## 4. 结果说明
  • Step 5: 提交验证与实施记录
git add docs/reports/implementation/2026-03-23-credit-parse-client-implementation.md
git commit -m "补充征信解析客户端实施记录"

Task 6: 最终整理

Files:

  • Modify: docs/design/2026-03-23-credit-parse-client-design.md

  • Modify: docs/plans/backend/2026-03-23-credit-parse-client-backend-implementation.md

  • Step 1: 回看设计与实现是否一致

逐项核对:

  • 独立 CreditParseClient

  • 独立配置 credit-parse.api.url

  • 接口路径 POST /lsfx/credit/parse

  • 不接入 ccdi-project

  • 不落库

  • Step 2: 如果实现偏差,更新设计或实施记录

只允许修正文档,不允许在这个阶段临时扩需求。

  • Step 3: 检查暂存区只包含本次任务相关文件

Run:

git status --short
git diff --cached --name-only

Expected:

  • 暂存区仅包含本次征信解析相关代码与文档

  • Step 4: 最终提交

git add ccdi-lsfx/pom.xml ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/CreditParseController.java ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayload.java ccdi-lsfx/src/test/java/com/ruoyi/lsfx/client/CreditParseClientTest.java ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java ruoyi-admin/src/main/resources/application-dev.yml ruoyi-admin/src/main/resources/application-nas.yml docs/reports/implementation/2026-03-23-credit-parse-client-implementation.md
git commit -m "完成征信解析客户端后端实现"

Review Notes

  • 由于当前仓库协作约定要求“不开启 subagent”本计划不执行 writing-plans 技能中的子代理审阅环节。
  • 实施时如果发现外部接口真实返回结构与说明书不一致,应先修正响应映射,再更新实施记录,不要额外扩展业务逻辑。