Refactor credit parse to use remote HTML paths

This commit is contained in:
wkc
2026-05-13 14:20:42 +08:00
parent b822cc202e
commit be443d1b31
18 changed files with 473 additions and 171 deletions

View File

@@ -14,8 +14,10 @@ import com.ruoyi.info.collection.mapper.CcdiCreditInfoQueryMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper;
import com.ruoyi.info.collection.service.ICcdiCreditInfoService;
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
import com.ruoyi.lsfx.domain.response.CreditParsePayload;
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import jakarta.annotation.Resource;
@@ -23,8 +25,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
@@ -39,6 +39,9 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
@Resource
private CreditParseClient creditParseClient;
@Resource
private CreditHtmlStorageService creditHtmlStorageService;
@Resource
private CreditInfoPayloadAssembler assembler;
@@ -141,37 +144,20 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
}
}
private void handleSingleFile(MultipartFile multipartFile, String userName) throws IOException {
File tempFile = createTempFile(multipartFile);
try {
CreditParseResponse response = creditParseClient.parse("LXCUSTALL", "PERSON", tempFile);
CreditParsePayload payload = requireResponse(response).getPayload();
Map<String, Object> header = requireHeader(payload);
String personId = stringValue(header.get("query_cert_no"));
String personName = stringValue(header.get("query_cust_name"));
LocalDate queryDate = parseQueryDate(stringValue(header.get("report_time")));
ensurePersonIdPresent(personId);
ensureLatestQueryDate(personId, queryDate);
private void handleSingleFile(MultipartFile multipartFile, String userName) throws Exception {
CreditHtmlStorageService.StoredCreditHtml storedHtml = creditHtmlStorageService.save(multipartFile);
CreditParseInvokeResponse response = creditParseClient.parse(storedHtml.remotePath());
CreditParsePayload payload = requireResponse(response).getPayload();
Map<String, Object> header = requireHeader(payload);
String personId = stringValue(header.get("query_cert_no"));
String personName = stringValue(header.get("query_cust_name"));
LocalDate queryDate = parseQueryDate(stringValue(header.get("report_time")));
ensurePersonIdPresent(personId);
ensureLatestQueryDate(personId, queryDate);
List<CcdiDebtsInfo> debts = assembler.buildDebts(personId, personName, queryDate, payload);
CcdiCreditNegativeInfo negative = assembler.buildNegative(personId, personName, queryDate, payload);
replaceEmployeeCredit(personId, debts, negative, userName);
} finally {
if (tempFile.exists()) {
tempFile.delete();
}
}
}
private File createTempFile(MultipartFile multipartFile) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
String suffix = ".html";
if (originalFilename != null && originalFilename.contains(".")) {
suffix = originalFilename.substring(originalFilename.lastIndexOf('.'));
}
File tempFile = File.createTempFile("credit-info-", suffix);
multipartFile.transferTo(tempFile);
return tempFile;
List<CcdiDebtsInfo> debts = assembler.buildDebts(personId, personName, queryDate, payload);
CcdiCreditNegativeInfo negative = assembler.buildNegative(personId, personName, queryDate, payload);
replaceEmployeeCredit(personId, debts, negative, userName);
}
private void validateHtmlFile(MultipartFile file) {
@@ -185,14 +171,21 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
}
}
private CreditParseResponse requireResponse(CreditParseResponse response) {
if (response == null || response.getPayload() == null) {
private CreditParseResponse requireResponse(CreditParseInvokeResponse response) {
if (response == null || response.getData() == null || response.getData().getMappingOutputFields() == null) {
throw new RuntimeException("征信解析结果为空");
}
if (!"0".equals(response.getStatusCode())) {
throw new RuntimeException(stringValue(response.getMessage(), "征信解析失败"));
CreditParseResponse mappingOutputFields = response.getData().getMappingOutputFields();
if (!Boolean.TRUE.equals(response.getSuccess()) || response.getCode() == null || response.getCode() != 1000) {
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析平台调用失败"));
}
return response;
if (!"0".equals(mappingOutputFields.getStatusCode())) {
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析失败"));
}
if (mappingOutputFields.getPayload() == null) {
throw new RuntimeException("征信解析结果为空");
}
return mappingOutputFields;
}
private Map<String, Object> requireHeader(CreditParsePayload payload) {

View File

@@ -0,0 +1,44 @@
package com.ruoyi.info.collection.service.support;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
/**
* 征信 HTML 服务器落盘与远程访问地址生成。
*/
@Component
public class CreditHtmlStorageService {
private static final String CREDIT_HTML_DIR = "credit-html";
private static final String[] HTML_EXTENSIONS = {"html", "htm"};
@Value("${credit-parse.api.file-public-base-url}")
private String filePublicBaseUrl;
public StoredCreditHtml save(MultipartFile file) throws Exception {
String profilePath = FileUploadUtils.upload(getCreditHtmlBaseDir(), file, HTML_EXTENSIONS);
return new StoredCreditHtml(profilePath, buildRemotePath(profilePath));
}
private String getCreditHtmlBaseDir() {
return RuoYiConfig.getProfile() + File.separator + CREDIT_HTML_DIR;
}
private String buildRemotePath(String profilePath) {
if (StringUtils.isBlank(filePublicBaseUrl)) {
throw new IllegalStateException("征信HTML公开访问地址未配置");
}
String normalizedBaseUrl = StringUtils.stripEnd(filePublicBaseUrl.trim(), "/");
String normalizedProfilePath = profilePath.startsWith("/") ? profilePath : "/" + profilePath;
return normalizedBaseUrl + normalizedProfilePath;
}
public record StoredCreditHtml(String profilePath, String remotePath) {
}
}

View File

@@ -1,5 +1,6 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo;
import com.ruoyi.info.collection.domain.CcdiDebtsInfo;
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO;
@@ -7,26 +8,33 @@ import com.ruoyi.info.collection.mapper.CcdiCreditInfoQueryMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper;
import com.ruoyi.info.collection.service.impl.CcdiCreditInfoServiceImpl;
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeData;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
import com.ruoyi.lsfx.domain.response.CreditParsePayload;
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
@@ -41,6 +49,9 @@ class CcdiCreditInfoServiceImplTest {
@Mock
private CreditParseClient creditParseClient;
@Mock
private CreditHtmlStorageService creditHtmlStorageService;
@Mock
private CreditInfoPayloadAssembler assembler;
@@ -54,11 +65,15 @@ class CcdiCreditInfoServiceImplTest {
private CcdiCreditInfoQueryMapper queryMapper;
@Test
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() {
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() throws Exception {
MockMultipartFile file = new MockMultipartFile(
"files", "family.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
when(creditHtmlStorageService.save(any()))
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
"/profile/credit-html/2026/05/12/family_1.html",
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html"));
when(creditParseClient.parse(anyString()))
.thenReturn(successResponse("330101199202020022", "李四", "2026-03-24"));
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
.thenReturn(List.of(buildDebt("330101199202020022")));
@@ -69,15 +84,20 @@ class CcdiCreditInfoServiceImplTest {
assertEquals(1, result.getSuccessCount());
assertEquals(0, result.getFailureCount());
verify(creditParseClient).parse("http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html");
verify(debtsInfoMapper).deleteByPersonId("330101199202020022");
verify(negativeInfoMapper).deleteByPersonId("330101199202020022");
}
@Test
void uploadHtmlFiles_shouldRejectOlderReportDate() {
void uploadHtmlFiles_shouldRejectOlderReportDate() throws Exception {
MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
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"));
when(creditParseClient.parse(anyString()))
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
when(queryMapper.selectLatestQueryDate("330101199001010011"))
.thenReturn(LocalDate.parse("2026-03-05"));
@@ -88,7 +108,29 @@ class CcdiCreditInfoServiceImplTest {
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
}
private CreditParseResponse successResponse(String personId, String personName, String reportTime) {
@Test
void creditHtmlStorage_shouldStoreHtmlUnderProfileAndBuildRemotePath(@TempDir Path profileDir) throws Exception {
String oldProfile = RuoYiConfig.getProfile();
new RuoYiConfig().setProfile(profileDir.toString());
try {
CreditHtmlStorageService storageService = new CreditHtmlStorageService();
ReflectionTestUtils.setField(storageService, "filePublicBaseUrl", "http://127.0.0.1:62318/");
MockMultipartFile file = new MockMultipartFile(
"files", "credit.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
CreditHtmlStorageService.StoredCreditHtml storedHtml = storageService.save(file);
assertTrue(storedHtml.profilePath().startsWith("/profile/credit-html/"));
assertTrue(storedHtml.profilePath().endsWith(".html"));
assertEquals("http://127.0.0.1:62318" + storedHtml.profilePath(), storedHtml.remotePath());
Path savedFile = profileDir.resolve(storedHtml.profilePath().substring("/profile/".length()));
assertTrue(Files.exists(savedFile));
} finally {
new RuoYiConfig().setProfile(oldProfile);
}
}
private CreditParseInvokeResponse successResponse(String personId, String personName, String reportTime) {
CreditParsePayload payload = new CreditParsePayload();
Map<String, Object> header = new HashMap<>();
header.put("query_cert_no", personId);
@@ -99,9 +141,18 @@ class CcdiCreditInfoServiceImplTest {
payload.setLxPublictype(Map.of("civil_cnt", 1));
CreditParseResponse response = new CreditParseResponse();
response.setMessage("成功");
response.setStatusCode("0");
response.setPayload(payload);
return response;
CreditParseInvokeData data = new CreditParseInvokeData();
data.setMappingOutputFields(response);
CreditParseInvokeResponse invokeResponse = new CreditParseInvokeResponse();
invokeResponse.setSuccess(true);
invokeResponse.setCode(1000);
invokeResponse.setData(data);
return invokeResponse;
}
private CcdiDebtsInfo buildDebt(String personId) {