调整征信解析返回解析和日志
This commit is contained in:
@@ -176,7 +176,7 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
throw new RuntimeException("征信解析结果为空");
|
throw new RuntimeException("征信解析结果为空");
|
||||||
}
|
}
|
||||||
CreditParseResponse mappingOutputFields = response.getData().getMappingOutputFields();
|
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(), "征信解析平台调用失败"));
|
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析平台调用失败"));
|
||||||
}
|
}
|
||||||
if (!"0".equals(mappingOutputFields.getStatusCode())) {
|
if (!"0".equals(mappingOutputFields.getStatusCode())) {
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
|
|
||||||
CreditParseInvokeResponse invokeResponse = new CreditParseInvokeResponse();
|
CreditParseInvokeResponse invokeResponse = new CreditParseInvokeResponse();
|
||||||
invokeResponse.setSuccess(true);
|
invokeResponse.setSuccess(true);
|
||||||
invokeResponse.setCode(1000);
|
invokeResponse.setCode(99999);
|
||||||
invokeResponse.setData(data);
|
invokeResponse.setData(data);
|
||||||
return invokeResponse;
|
return invokeResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ruoyi.lsfx.client;
|
package com.ruoyi.lsfx.client;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
@@ -20,6 +21,9 @@ public class CreditParseClient {
|
|||||||
@Resource
|
@Resource
|
||||||
private HttpUtil httpUtil;
|
private HttpUtil httpUtil;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
@Value("${credit-parse.api.url}")
|
@Value("${credit-parse.api.url}")
|
||||||
private String creditParseUrl;
|
private String creditParseUrl;
|
||||||
|
|
||||||
@@ -39,8 +43,6 @@ public class CreditParseClient {
|
|||||||
public CreditParseInvokeResponse parse(String model, String remotePath) {
|
public CreditParseInvokeResponse parse(String model, String remotePath) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
String actualModel = StringUtils.isBlank(model) ? defaultModel : model;
|
String actualModel = StringUtils.isBlank(model) ? defaultModel : model;
|
||||||
log.info("【征信解析】开始调用: model={}, remotePath={}", actualModel, remotePath);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
params.put("serialNum", buildSerialNum());
|
params.put("serialNum", buildSerialNum());
|
||||||
@@ -49,8 +51,11 @@ public class CreditParseClient {
|
|||||||
params.put("remotePath", remotePath);
|
params.put("remotePath", remotePath);
|
||||||
params.put("model", actualModel);
|
params.put("model", actualModel);
|
||||||
|
|
||||||
CreditParseInvokeResponse response = httpUtil.postUrlEncodedForm(
|
log.info("【征信解析】调用请求: url={}, params={}", creditParseUrl, toJson(params));
|
||||||
creditParseUrl, params, null, CreditParseInvokeResponse.class);
|
String responseJson = httpUtil.postUrlEncodedFormForString(creditParseUrl, params, null);
|
||||||
|
log.info("【征信解析】调用返回JSON: {}", responseJson);
|
||||||
|
|
||||||
|
CreditParseInvokeResponse response = objectMapper.readValue(responseJson, CreditParseInvokeResponse.class);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms",
|
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms",
|
||||||
@@ -70,4 +75,12 @@ public class CreditParseClient {
|
|||||||
private String buildSerialNum() {
|
private String buildSerialNum() {
|
||||||
return "CCDI_CREDIT_" + System.currentTimeMillis() + "_" + IdUtils.fastSimpleUUID();
|
return "CCDI_CREDIT_" + System.currentTimeMillis() + "_" + IdUtils.fastSimpleUUID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toJson(Object value) {
|
||||||
|
try {
|
||||||
|
return objectMapper.writeValueAsString(value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return String.valueOf(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<CreditParsePayload> {
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.lsfx.domain.response;
|
package com.ruoyi.lsfx.domain.response;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@@ -11,5 +12,6 @@ public class CreditParseResponse {
|
|||||||
@JsonProperty("status_code")
|
@JsonProperty("status_code")
|
||||||
private String statusCode;
|
private String statusCode;
|
||||||
|
|
||||||
|
@JsonDeserialize(using = CreditParsePayloadDeserializer.class)
|
||||||
private CreditParsePayload payload;
|
private CreditParsePayload payload;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<String, Object> params, Map<String, String> headers) {
|
||||||
|
try {
|
||||||
|
HttpHeaders httpHeaders = createHeaders(headers);
|
||||||
|
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
|
||||||
|
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
|
||||||
|
if (params != null) {
|
||||||
|
params.forEach((key, value) -> {
|
||||||
|
if (value != null) {
|
||||||
|
body.add(key, value.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, httpHeaders);
|
||||||
|
ResponseEntity<String> 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格式)
|
* 上传文件(Multipart格式)
|
||||||
* @param url 请求URL
|
* @param url 请求URL
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ruoyi.lsfx.controller;
|
package com.ruoyi.lsfx.controller;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
@@ -68,35 +69,38 @@ class CreditParseControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
void creditParseClient_shouldPostUrlEncodedRemotePathParameters() {
|
void creditParseClient_shouldParseSuccessResponseWithStringPayload() throws Exception {
|
||||||
HttpUtil httpUtil = mock(HttpUtil.class);
|
HttpUtil httpUtil = mock(HttpUtil.class);
|
||||||
CreditParseClient parseClient = new CreditParseClient();
|
CreditParseClient parseClient = new CreditParseClient();
|
||||||
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
|
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
|
||||||
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
|
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
|
||||||
ReflectionTestUtils.setField(parseClient, "orgCode", "902000");
|
ReflectionTestUtils.setField(parseClient, "orgCode", "902000");
|
||||||
ReflectionTestUtils.setField(parseClient, "runType", "1");
|
ReflectionTestUtils.setField(parseClient, "runType", "1");
|
||||||
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
|
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
|
||||||
|
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
|
||||||
|
|
||||||
CreditParseInvokeResponse response = new CreditParseInvokeResponse();
|
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}}";
|
||||||
response.setSuccess(true);
|
|
||||||
response.setCode(1000);
|
when(httpUtil.postUrlEncodedFormForString(
|
||||||
when(httpUtil.postUrlEncodedForm(
|
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||||
isNull(),
|
isNull()
|
||||||
eq(CreditParseInvokeResponse.class)
|
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\",\"payload\":"
|
||||||
)).thenReturn(response);
|
+ objectMapper.writeValueAsString(payload) + "}}}");
|
||||||
|
|
||||||
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
||||||
CreditParseInvokeResponse actual = parseClient.parse(remotePath);
|
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<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
ArgumentCaptor<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
||||||
verify(httpUtil).postUrlEncodedForm(
|
verify(httpUtil).postUrlEncodedFormForString(
|
||||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||||
paramsCaptor.capture(),
|
paramsCaptor.capture(),
|
||||||
isNull(),
|
isNull()
|
||||||
eq(CreditParseInvokeResponse.class)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, Object> params = paramsCaptor.getValue();
|
Map<String, Object> params = paramsCaptor.getValue();
|
||||||
|
|||||||
@@ -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`:通过。
|
||||||
@@ -67,7 +67,7 @@ class CreditDebugService:
|
|||||||
def wrap_mapping_response(self, mapping_output_fields: dict) -> dict:
|
def wrap_mapping_response(self, mapping_output_fields: dict) -> dict:
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"code": 1000,
|
"code": 10000,
|
||||||
"data": {
|
"data": {
|
||||||
"mappingOutputFields": mapping_output_fields,
|
"mappingOutputFields": mapping_output_fields,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user