Implement credit parse result polling and sentinel handling
This commit is contained in:
@@ -36,6 +36,10 @@ import java.util.Map;
|
||||
@Service
|
||||
public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
||||
|
||||
private static final int CREDIT_PARSE_SUCCESS_CODE = 10000;
|
||||
private static final int CREDIT_PARSE_SUCCESS_STATUS = 1;
|
||||
private static final int CREDIT_PARSE_SUCCESS_REASON_CODE = 200;
|
||||
|
||||
@Resource
|
||||
private CreditParseClient creditParseClient;
|
||||
|
||||
@@ -179,8 +183,14 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
||||
if (!Boolean.TRUE.equals(response.getSuccess())) {
|
||||
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析平台调用失败"));
|
||||
}
|
||||
if (!"0".equals(mappingOutputFields.getStatusCode())) {
|
||||
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析失败"));
|
||||
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_CODE).equals(response.getCode())) {
|
||||
throw new RuntimeException("征信解析平台状态码异常: " + response.getCode());
|
||||
}
|
||||
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_STATUS).equals(response.getData().getStatus())) {
|
||||
throw new RuntimeException(parseErrorMessage(response, "征信解析状态异常: " + response.getData().getStatus()));
|
||||
}
|
||||
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_REASON_CODE).equals(response.getData().getReasonCode())) {
|
||||
throw new RuntimeException(parseErrorMessage(response, "征信解析原因码异常: " + response.getData().getReasonCode()));
|
||||
}
|
||||
if (mappingOutputFields.getPayload() == null) {
|
||||
throw new RuntimeException("征信解析结果为空");
|
||||
@@ -188,6 +198,20 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
||||
return mappingOutputFields;
|
||||
}
|
||||
|
||||
private String parseErrorMessage(CreditParseInvokeResponse response, String defaultValue) {
|
||||
if (response == null || response.getData() == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
String reasonMessage = stringValue(response.getData().getReasonMessage());
|
||||
if (!isBlank(reasonMessage)) {
|
||||
return reasonMessage;
|
||||
}
|
||||
if (response.getData().getMappingOutputFields() != null) {
|
||||
return stringValue(response.getData().getMappingOutputFields().getMessage(), defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private Map<String, Object> requireHeader(CreditParsePayload payload) {
|
||||
Map<String, Object> header = payload.getLxHeader();
|
||||
if (header == null || header.isEmpty()) {
|
||||
|
||||
@@ -19,6 +19,8 @@ import java.util.Objects;
|
||||
@Component
|
||||
public class CreditInfoPayloadAssembler {
|
||||
|
||||
private static final BigDecimal MISSING_SENTINEL = new BigDecimal("-9999");
|
||||
|
||||
private static final List<DebtMapping> DEBT_MAPPINGS = List.of(
|
||||
new DebtMapping("uncle_bank_house", "银行", "住房贷款", "银行", "未结清银行住房贷款"),
|
||||
new DebtMapping("uncle_bank_car", "银行", "汽车贷款", "银行", "未结清银行汽车贷款"),
|
||||
@@ -26,7 +28,7 @@ public class CreditInfoPayloadAssembler {
|
||||
new DebtMapping("uncle_bank_consume", "银行", "消费贷款", "银行", "未结清银行消费贷款"),
|
||||
new DebtMapping("uncle_bank_other", "银行", "其他贷款", "银行", "未结清银行其他贷款"),
|
||||
new DebtMapping("uncle_not_bank", "非银", "非银行贷款", "非银", "未结清非银行贷款"),
|
||||
new DebtMapping("uncle_credit_cart", "银行", "信用卡", "银行", "未结清信用卡")
|
||||
new DebtMapping("uncle_credit_card", "银行", "信用卡", "银行", "未结清信用卡")
|
||||
);
|
||||
|
||||
public List<CcdiDebtsInfo> buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) {
|
||||
@@ -61,9 +63,13 @@ public class CreditInfoPayloadAssembler {
|
||||
|
||||
private CcdiDebtsInfo buildDebtRow(String personId, String personName, LocalDate queryDate,
|
||||
Map<String, Object> source, DebtMapping mapping) {
|
||||
Object stateValue = source.get(mapping.prefix() + "_state");
|
||||
if (isMissingSentinel(stateValue)) {
|
||||
return null;
|
||||
}
|
||||
BigDecimal principalBalance = toBigDecimal(source.get(mapping.prefix() + "_bal"));
|
||||
BigDecimal debtTotalAmount = toBigDecimal(source.get(mapping.prefix() + "_lmt"));
|
||||
String debtStatus = toStringValue(source.get(mapping.prefix() + "_state"));
|
||||
String debtStatus = toStringValue(stateValue);
|
||||
if (isEmptyMetrics(principalBalance, debtTotalAmount, debtStatus)) {
|
||||
return null;
|
||||
}
|
||||
@@ -97,6 +103,9 @@ public class CreditInfoPayloadAssembler {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (isMissingSentinel(value)) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof BigDecimal decimal) {
|
||||
return decimal;
|
||||
}
|
||||
@@ -111,10 +120,28 @@ public class CreditInfoPayloadAssembler {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (isMissingSentinel(value)) {
|
||||
return null;
|
||||
}
|
||||
String text = Objects.toString(value, "").trim();
|
||||
return text.isEmpty() ? null : text;
|
||||
}
|
||||
|
||||
private boolean isMissingSentinel(Object value) {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
String text = Objects.toString(value, "").trim();
|
||||
if (text.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return new BigDecimal(text).compareTo(MISSING_SENTINEL) == 0;
|
||||
} catch (NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
|
||||
@@ -108,6 +108,45 @@ class CcdiCreditInfoServiceImplTest {
|
||||
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadHtmlFiles_shouldRejectInvalidPlatformCode() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
when(creditHtmlStorageService.save(any()))
|
||||
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
|
||||
"/profile/credit-html/2026/05/12/a_1.html",
|
||||
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
|
||||
CreditParseInvokeResponse response = successResponse("330101199001010011", "张三", "2026-03-03");
|
||||
response.setCode(99999);
|
||||
when(creditParseClient.parse(anyString())).thenReturn(response);
|
||||
|
||||
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
||||
|
||||
assertEquals(0, result.getSuccessCount());
|
||||
assertEquals("征信解析平台状态码异常: 99999", result.getFailures().get(0).getReason());
|
||||
}
|
||||
|
||||
@Test
|
||||
void uploadHtmlFiles_shouldRejectInvalidResultStatus() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
when(creditHtmlStorageService.save(any()))
|
||||
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
|
||||
"/profile/credit-html/2026/05/12/a_1.html",
|
||||
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
|
||||
CreditParseInvokeResponse response = successResponse("330101199001010011", "张三", "2026-03-03");
|
||||
response.getData().setStatus(0);
|
||||
response.getData().setReasonCode(500);
|
||||
response.getData().setReasonMessage("结果解析失败");
|
||||
when(creditParseClient.parse(anyString())).thenReturn(response);
|
||||
|
||||
CreditInfoUploadResultVO result = service.upload(List.of(file));
|
||||
|
||||
assertEquals(0, result.getSuccessCount());
|
||||
assertEquals("结果解析失败", result.getFailures().get(0).getReason());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void creditHtmlStorage_shouldStoreHtmlUnderProfileAndBuildRemotePath(@TempDir Path profileDir) throws Exception {
|
||||
String oldProfile = RuoYiConfig.getProfile();
|
||||
@@ -142,15 +181,17 @@ class CcdiCreditInfoServiceImplTest {
|
||||
|
||||
CreditParseResponse response = new CreditParseResponse();
|
||||
response.setMessage("成功");
|
||||
response.setStatusCode("0");
|
||||
response.setStatusCode("ERR_SHOULD_IGNORE");
|
||||
response.setPayload(payload);
|
||||
|
||||
CreditParseInvokeData data = new CreditParseInvokeData();
|
||||
data.setMappingOutputFields(response);
|
||||
data.setStatus(1);
|
||||
data.setReasonCode(200);
|
||||
|
||||
CreditParseInvokeResponse invokeResponse = new CreditParseInvokeResponse();
|
||||
invokeResponse.setSuccess(true);
|
||||
invokeResponse.setCode(99999);
|
||||
invokeResponse.setCode(10000);
|
||||
invokeResponse.setData(data);
|
||||
return invokeResponse;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CreditInfoPayloadAssemblerTest {
|
||||
@@ -28,13 +29,18 @@ class CreditInfoPayloadAssemblerTest {
|
||||
debt.put("uncle_not_bank_bal", "2000");
|
||||
debt.put("uncle_not_bank_lmt", "3000");
|
||||
debt.put("uncle_not_bank_state", "逾期");
|
||||
debt.put("uncle_credit_card_bal", "100");
|
||||
debt.put("uncle_credit_card_lmt", "500");
|
||||
debt.put("uncle_credit_card_state", "正常");
|
||||
payload.setLxDebt(debt);
|
||||
|
||||
List<CcdiDebtsInfo> rows = assembler.buildDebts("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload);
|
||||
|
||||
assertEquals(2, rows.size());
|
||||
assertEquals(3, rows.size());
|
||||
assertEquals("住房贷款", rows.get(0).getDebtSubType());
|
||||
assertEquals("非银", rows.get(1).getCreditorType());
|
||||
assertEquals("信用卡", rows.get(2).getDebtSubType());
|
||||
assertEquals(new BigDecimal("500"), rows.get(2).getDebtTotalAmount());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -51,6 +57,42 @@ class CreditInfoPayloadAssemblerTest {
|
||||
assertEquals(new BigDecimal("9800"), info.getCivilLmt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipDebtTypeWhenStateIsMissingSentinel() {
|
||||
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", "-9999");
|
||||
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(1, rows.size());
|
||||
assertEquals("非银行贷款", rows.get(0).getDebtSubType());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldTreatNegativeRiskMissingSentinelAsEmptyValue() {
|
||||
CreditParsePayload payload = new CreditParsePayload();
|
||||
Map<String, Object> publictype = new HashMap<>();
|
||||
publictype.put("civil_cnt", "-9999");
|
||||
publictype.put("civil_lmt", "-9999.0");
|
||||
publictype.put("enforce_cnt", 1);
|
||||
publictype.put("enforce_lmt", "1200");
|
||||
payload.setLxPublictype(publictype);
|
||||
|
||||
CcdiCreditNegativeInfo info = assembler.buildNegative("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload);
|
||||
|
||||
assertEquals(0, info.getCivilCnt());
|
||||
assertNull(info.getCivilLmt());
|
||||
assertEquals(1, info.getEnforceCnt());
|
||||
assertEquals(new BigDecimal("1200"), info.getEnforceLmt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSkipDebtRowWhenAllMetricsAreEmpty() {
|
||||
CreditParsePayload payload = new CreditParsePayload();
|
||||
|
||||
Reference in New Issue
Block a user