From 9917d10e5931eb41c44a6042bb1645b23f44e80e Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Wed, 13 May 2026 16:28:57 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=BE=81=E4=BF=A1=E8=A7=A3?= =?UTF-8?q?=E6=9E=90=E8=BF=94=E5=9B=9E=E8=A7=A3=E6=9E=90=E5=92=8C=E6=97=A5?= =?UTF-8?q?=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../impl/CcdiCreditInfoServiceImpl.java | 2 +- .../CcdiCreditInfoServiceImplTest.java | 2 +- .../ruoyi/lsfx/client/CreditParseClient.java | 21 ++++++++-- .../CreditParsePayloadDeserializer.java | 33 ++++++++++++++++ .../domain/response/CreditParseResponse.java | 2 + .../java/com/ruoyi/lsfx/util/HttpUtil.java | 39 +++++++++++++++++++ .../controller/CreditParseControllerTest.java | 28 +++++++------ .../2026-05-13-credit-parse-call-log.md | 39 +++++++++++++++++++ .../services/credit_debug_service.py | 2 +- 9 files changed, 149 insertions(+), 19 deletions(-) create mode 100644 ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayloadDeserializer.java create mode 100644 docs/reports/implementation/2026-05-13-credit-parse-call-log.md diff --git a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java index e02d5931..1591ca73 100644 --- a/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java +++ b/ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/impl/CcdiCreditInfoServiceImpl.java @@ -176,7 +176,7 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService { throw new RuntimeException("征信解析结果为空"); } CreditParseResponse mappingOutputFields = response.getData().getMappingOutputFields(); - if (!Boolean.TRUE.equals(response.getSuccess()) || response.getCode() == null || response.getCode() != 1000) { + if (!Boolean.TRUE.equals(response.getSuccess())) { throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析平台调用失败")); } if (!"0".equals(mappingOutputFields.getStatusCode())) { diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java index 1a82547c..117f5b8f 100644 --- a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/service/CcdiCreditInfoServiceImplTest.java @@ -150,7 +150,7 @@ class CcdiCreditInfoServiceImplTest { CreditParseInvokeResponse invokeResponse = new CreditParseInvokeResponse(); invokeResponse.setSuccess(true); - invokeResponse.setCode(1000); + invokeResponse.setCode(99999); invokeResponse.setData(data); return invokeResponse; } diff --git a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java index 6f5237c7..238cd6e8 100644 --- a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java +++ b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/CreditParseClient.java @@ -1,5 +1,6 @@ package com.ruoyi.lsfx.client; +import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse; @@ -20,6 +21,9 @@ public class CreditParseClient { @Resource private HttpUtil httpUtil; + @Resource + private ObjectMapper objectMapper; + @Value("${credit-parse.api.url}") private String creditParseUrl; @@ -39,8 +43,6 @@ public class CreditParseClient { public CreditParseInvokeResponse parse(String model, String remotePath) { long startTime = System.currentTimeMillis(); String actualModel = StringUtils.isBlank(model) ? defaultModel : model; - log.info("【征信解析】开始调用: model={}, remotePath={}", actualModel, remotePath); - try { Map params = new HashMap<>(); params.put("serialNum", buildSerialNum()); @@ -49,8 +51,11 @@ public class CreditParseClient { params.put("remotePath", remotePath); params.put("model", actualModel); - CreditParseInvokeResponse response = httpUtil.postUrlEncodedForm( - creditParseUrl, params, null, CreditParseInvokeResponse.class); + log.info("【征信解析】调用请求: url={}, params={}", creditParseUrl, toJson(params)); + String responseJson = httpUtil.postUrlEncodedFormForString(creditParseUrl, params, null); + log.info("【征信解析】调用返回JSON: {}", responseJson); + + CreditParseInvokeResponse response = objectMapper.readValue(responseJson, CreditParseInvokeResponse.class); long elapsed = System.currentTimeMillis() - startTime; log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms", @@ -70,4 +75,12 @@ public class CreditParseClient { private String buildSerialNum() { return "CCDI_CREDIT_" + System.currentTimeMillis() + "_" + IdUtils.fastSimpleUUID(); } + + private String toJson(Object value) { + try { + return objectMapper.writeValueAsString(value); + } catch (Exception e) { + return String.valueOf(value); + } + } } diff --git a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayloadDeserializer.java b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayloadDeserializer.java new file mode 100644 index 00000000..cebbbccf --- /dev/null +++ b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParsePayloadDeserializer.java @@ -0,0 +1,33 @@ +package com.ruoyi.lsfx.domain.response; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.ObjectCodec; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.DeserializationContext; + +import java.io.IOException; + +public class CreditParsePayloadDeserializer extends JsonDeserializer { + + @Override + public CreditParsePayload deserialize(JsonParser parser, DeserializationContext context) throws IOException { + ObjectCodec codec = parser.getCodec(); + JsonNode node = codec.readTree(parser); + if (node == null || node.isNull()) { + return null; + } + if (node.isTextual()) { + String payloadText = node.asText(); + if (payloadText == null || payloadText.trim().isEmpty()) { + return null; + } + node = codec.readTree(codec.getFactory().createParser(payloadText)); + } + if (!node.isObject()) { + throw JsonMappingException.from(parser, "征信解析payload格式不支持"); + } + return codec.treeToValue(node, CreditParsePayload.class); + } +} diff --git a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java index 39b0b557..d8ab0665 100644 --- a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java +++ b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/response/CreditParseResponse.java @@ -1,6 +1,7 @@ package com.ruoyi.lsfx.domain.response; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; @Data @@ -11,5 +12,6 @@ public class CreditParseResponse { @JsonProperty("status_code") private String statusCode; + @JsonDeserialize(using = CreditParsePayloadDeserializer.class) private CreditParsePayload payload; } diff --git a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java index 966578c9..bc7f15fc 100644 --- a/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java +++ b/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java @@ -247,6 +247,45 @@ public class HttpUtil { } } + /** + * 发送POST请求(application/x-www-form-urlencoded格式)并返回原始JSON字符串 + * @param url 请求URL + * @param params 表单参数 + * @param headers 请求头 + * @return 原始响应内容 + */ + public String postUrlEncodedFormForString(String url, Map params, Map headers) { + try { + HttpHeaders httpHeaders = createHeaders(headers); + httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap body = new LinkedMultiValueMap<>(); + if (params != null) { + params.forEach((key, value) -> { + if (value != null) { + body.add(key, value.toString()); + } + }); + } + + HttpEntity> requestEntity = new HttpEntity<>(body, httpHeaders); + ResponseEntity response = restTemplate.postForEntity(url, requestEntity, String.class); + + if (!response.getStatusCode().is2xxSuccessful()) { + throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode()); + } + + String responseBody = response.getBody(); + if (responseBody == null) { + throw new LsfxApiException("API返回数据为空"); + } + + return responseBody; + } catch (RestClientException e) { + throw new LsfxApiException("网络请求失败: " + e.getMessage(), e); + } + } + /** * 上传文件(Multipart格式) * @param url 请求URL diff --git a/ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java b/ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java index bd3f4888..7c693421 100644 --- a/ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java +++ b/ccdi-lsfx/src/test/java/com/ruoyi/lsfx/controller/CreditParseControllerTest.java @@ -1,5 +1,6 @@ package com.ruoyi.lsfx.controller; +import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.lsfx.client.CreditParseClient; import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse; @@ -68,35 +69,38 @@ class CreditParseControllerTest { @Test @SuppressWarnings({"unchecked", "rawtypes"}) - void creditParseClient_shouldPostUrlEncodedRemotePathParameters() { + void creditParseClient_shouldParseSuccessResponseWithStringPayload() throws Exception { HttpUtil httpUtil = mock(HttpUtil.class); CreditParseClient parseClient = new CreditParseClient(); + ObjectMapper objectMapper = new ObjectMapper(); ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil); ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature"); ReflectionTestUtils.setField(parseClient, "orgCode", "902000"); ReflectionTestUtils.setField(parseClient, "runType", "1"); ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL"); + ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper); - CreditParseInvokeResponse response = new CreditParseInvokeResponse(); - response.setSuccess(true); - response.setCode(1000); - when(httpUtil.postUrlEncodedForm( + String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"},\"lx_debt\":{\"uncle_bank_house_bal\":\"1\"},\"lx_publictype\":{\"civil_cnt\":1}}"; + + when(httpUtil.postUrlEncodedFormForString( eq("http://tz/api/service/interface/invokeService/xfeature"), org.mockito.ArgumentMatchers.>any(), - isNull(), - eq(CreditParseInvokeResponse.class) - )).thenReturn(response); + isNull() + )).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\",\"payload\":" + + objectMapper.writeValueAsString(payload) + "}}}"); String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html"; CreditParseInvokeResponse actual = parseClient.parse(remotePath); - assertSame(response, actual); + assertEquals(true, actual.getSuccess()); + assertEquals(10000, actual.getCode()); + assertEquals("330101199001010011", actual.getData().getMappingOutputFields() + .getPayload().getLxHeader().get("query_cert_no")); ArgumentCaptor> paramsCaptor = ArgumentCaptor.forClass((Class) Map.class); - verify(httpUtil).postUrlEncodedForm( + verify(httpUtil).postUrlEncodedFormForString( eq("http://tz/api/service/interface/invokeService/xfeature"), paramsCaptor.capture(), - isNull(), - eq(CreditParseInvokeResponse.class) + isNull() ); Map params = paramsCaptor.getValue(); diff --git a/docs/reports/implementation/2026-05-13-credit-parse-call-log.md b/docs/reports/implementation/2026-05-13-credit-parse-call-log.md new file mode 100644 index 00000000..2831c6a3 --- /dev/null +++ b/docs/reports/implementation/2026-05-13-credit-parse-call-log.md @@ -0,0 +1,39 @@ +# 征信解析接口调用日志补充实施记录 + +## 背景 + +为便于联调排查,需要在调用天座征信解析接口时记录实际请求地址、表单参数以及接口返回的原始 JSON。 +后续根据真实返回样例确认外层是否成功只看 `success` 字段,`code` 仅记录日志不参与成功判断,且 `payload` 可能以 JSON 字符串形式返回。 + +## 修改内容 + +1. `CreditParseClient` + - 调用前打印 `credit-parse.api.url` 和本次表单参数 `serialNum/orgCode/runType/remotePath/model`。 + - 调用后打印接口返回的原始 JSON 字符串。 + - 原始 JSON 打印后再反序列化为现有 `CreditParseInvokeResponse`,保持后续业务处理逻辑不变。 + - 按最新返回结构兼容字符串形式的 `payload`,`code` 仅保留日志输出。 + +2. `HttpUtil` + - 新增 `postUrlEncodedFormForString` 方法。 + - 该方法沿用 `application/x-www-form-urlencoded` 提交流程,但返回原始响应字符串,供征信解析调用日志记录使用。 + +3. 测试 + - 调整 `CreditParseControllerTest` 中征信解析客户端测试,验证新方法仍提交 `serialNum/orgCode/runType/remotePath/model` 参数,并可解析返回 JSON。 + +4. `CcdiCreditInfoServiceImpl` + - 平台外层成功判断只检查 `success=true`,不再检查 `code`。 + +5. `lsfx-mock-server` + - 征信解析 Mock 外层 `code` 同步调整为 `10000`,保持本地联调返回结构一致。 + +## 影响范围 + +- 仅影响征信解析接口调用日志与该接口的响应读取方式。 +- 不改变请求参数、接口地址配置、返回 DTO、征信信息落库和页面交互。 + +## 验证 + +- `mvn -pl ccdi-lsfx -Dtest=CreditParseControllerTest test`:通过,已覆盖调用日志和 `payload` 字符串解析。 +- `mvn -pl ccdi-info-collection -am -Dtest=CcdiCreditInfoServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`:通过,已覆盖 `success=true` 时不依赖固定 `code`。 +- `mvn -pl ccdi-lsfx,ccdi-info-collection -am compile`:通过。 +- `git diff --check`:通过。 diff --git a/lsfx-mock-server/services/credit_debug_service.py b/lsfx-mock-server/services/credit_debug_service.py index b05f5389..272135bb 100644 --- a/lsfx-mock-server/services/credit_debug_service.py +++ b/lsfx-mock-server/services/credit_debug_service.py @@ -67,7 +67,7 @@ class CreditDebugService: def wrap_mapping_response(self, mapping_output_fields: dict) -> dict: return { "success": True, - "code": 1000, + "code": 10000, "data": { "mappingOutputFields": mapping_output_fields, },