16 KiB
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 与异常体系,但不复用 LsfxAnalysisClient 和 lsfx.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映射外部返回的message、status_code、payload。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.javaccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/LsfxTestController.javaccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.javadocs/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:
-
编译失败,提示
CreditParseController或parse(...)不存在。 -
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,提示
CreditParseResponse或CreditParsePayload不存在。 -
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 风格:
-
请求开始记录文件名、
model、hType -
请求结束记录耗时和
statusCode -
异常时抛
LsfxApiException("征信解析调用失败: ...", e) -
Step 5: 增加独立配置
在 ruoyi-admin/src/main/resources/application-dev.yml 与 ruoyi-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 == null或file.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:
-
CreditParseClientTestPASS -
CreditParseControllerTestPASS -
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技能中的子代理审阅环节。 - 实施时如果发现外部接口真实返回结构与说明书不一致,应先修正响应映射,再更新实施记录,不要额外扩展业务逻辑。