Refactor credit parse to use remote HTML paths
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package com.ruoyi.lsfx.client;
|
||||
|
||||
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||
import com.ruoyi.lsfx.util.HttpUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -8,7 +10,6 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -22,26 +23,51 @@ public class CreditParseClient {
|
||||
@Value("${credit-parse.api.url}")
|
||||
private String creditParseUrl;
|
||||
|
||||
public CreditParseResponse parse(String model, String hType, File file) {
|
||||
@Value("${credit-parse.api.org-code:902000}")
|
||||
private String orgCode;
|
||||
|
||||
@Value("${credit-parse.api.run-type:1}")
|
||||
private String runType;
|
||||
|
||||
@Value("${credit-parse.api.model:LXCUSTALL}")
|
||||
private String defaultModel;
|
||||
|
||||
public CreditParseInvokeResponse parse(String remotePath) {
|
||||
return parse(defaultModel, remotePath);
|
||||
}
|
||||
|
||||
public CreditParseInvokeResponse parse(String model, String remotePath) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("【征信解析】开始调用: fileName={}, model={}, hType={}", file.getName(), model, hType);
|
||||
String actualModel = StringUtils.isBlank(model) ? defaultModel : model;
|
||||
log.info("【征信解析】开始调用: model={}, remotePath={}", actualModel, remotePath);
|
||||
|
||||
try {
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("model", model);
|
||||
params.put("hType", hType);
|
||||
params.put("file", file);
|
||||
params.put("serialNum", buildSerialNum());
|
||||
params.put("orgCode", orgCode);
|
||||
params.put("runType", runType);
|
||||
params.put("remotePath", remotePath);
|
||||
params.put("model", actualModel);
|
||||
|
||||
CreditParseResponse response = httpUtil.uploadFile(creditParseUrl, params, null, CreditParseResponse.class);
|
||||
CreditParseInvokeResponse response = httpUtil.postUrlEncodedForm(
|
||||
creditParseUrl, params, null, CreditParseInvokeResponse.class);
|
||||
|
||||
long elapsed = System.currentTimeMillis() - startTime;
|
||||
log.info("【征信解析】调用完成: statusCode={}, cost={}ms",
|
||||
response != null ? response.getStatusCode() : null, elapsed);
|
||||
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms",
|
||||
response != null ? response.getSuccess() : null,
|
||||
response != null ? response.getCode() : null,
|
||||
response != null && response.getData() != null && response.getData().getMappingOutputFields() != null
|
||||
? response.getData().getMappingOutputFields().getStatusCode() : null,
|
||||
elapsed);
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("【征信解析】调用失败: fileName={}, model={}, hType={}, error={}",
|
||||
file.getName(), model, hType, e.getMessage(), e);
|
||||
log.error("【征信解析】调用失败: model={}, remotePath={}, error={}",
|
||||
actualModel, remotePath, e.getMessage(), e);
|
||||
throw new LsfxApiException("征信解析调用失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String buildSerialNum() {
|
||||
return "CCDI_CREDIT_" + System.currentTimeMillis() + "_" + IdUtils.fastSimpleUUID();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
@@ -14,13 +14,6 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
@Tag(name = "征信解析接口测试", description = "用于测试征信解析接口")
|
||||
@Anonymous
|
||||
@@ -29,56 +22,27 @@ import java.nio.file.StandardCopyOption;
|
||||
public class CreditParseController {
|
||||
|
||||
private static final String DEFAULT_MODEL = "LXCUSTALL";
|
||||
private static final String DEFAULT_HTYPE = "PERSON";
|
||||
|
||||
@Resource
|
||||
private CreditParseClient creditParseClient;
|
||||
|
||||
@Operation(summary = "解析征信HTML", description = "上传征信HTML文件并调用外部解析服务")
|
||||
@Operation(summary = "解析征信HTML", description = "传入征信HTML远程地址并调用外部解析服务")
|
||||
@PostMapping("/parse")
|
||||
public AjaxResult parse(@Parameter(description = "征信HTML文件") @RequestParam("file") MultipartFile file,
|
||||
@Parameter(description = "解析模型,默认LXCUSTALL") @RequestParam(required = false) String model,
|
||||
@Parameter(description = "主体类型,默认PERSON") @RequestParam(required = false) String hType) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return AjaxResult.error("征信HTML文件不能为空");
|
||||
}
|
||||
|
||||
String originalFilename = file.getOriginalFilename();
|
||||
if (StringUtils.isBlank(originalFilename)) {
|
||||
return AjaxResult.error("文件名不能为空");
|
||||
}
|
||||
|
||||
String lowerCaseName = originalFilename.toLowerCase();
|
||||
if (!lowerCaseName.endsWith(".html") && !lowerCaseName.endsWith(".htm")) {
|
||||
return AjaxResult.error("仅支持 HTML 格式文件");
|
||||
public AjaxResult parse(@Parameter(description = "征信HTML远程访问地址") @RequestParam("remotePath") String remotePath,
|
||||
@Parameter(description = "模型编码,默认LXCUSTALL") @RequestParam(required = false) String model) {
|
||||
if (StringUtils.isBlank(remotePath)) {
|
||||
return AjaxResult.error("征信HTML远程地址不能为空");
|
||||
}
|
||||
|
||||
String actualModel = StringUtils.isBlank(model) ? DEFAULT_MODEL : model;
|
||||
String actualHType = StringUtils.isBlank(hType) ? DEFAULT_HTYPE : hType;
|
||||
|
||||
Path tempFile = null;
|
||||
try {
|
||||
String suffix = lowerCaseName.endsWith(".htm") ? ".htm" : ".html";
|
||||
tempFile = Files.createTempFile("credit_parse_", suffix);
|
||||
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
File convertedFile = tempFile.toFile();
|
||||
CreditParseResponse response = creditParseClient.parse(actualModel, actualHType, convertedFile);
|
||||
CreditParseInvokeResponse response = creditParseClient.parse(actualModel, remotePath);
|
||||
return AjaxResult.success(response);
|
||||
} catch (LsfxApiException e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
} catch (IOException e) {
|
||||
return AjaxResult.error("文件转换失败:" + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("征信解析失败:" + e.getMessage());
|
||||
} finally {
|
||||
if (tempFile != null) {
|
||||
try {
|
||||
Files.deleteIfExists(tempFile);
|
||||
} catch (IOException ignored) {
|
||||
// 忽略临时文件删除失败,避免影响主流程返回
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.ruoyi.lsfx.domain.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreditParseInvokeData {
|
||||
|
||||
private CreditParseResponse mappingOutputFields;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.ruoyi.lsfx.domain.response;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CreditParseInvokeResponse {
|
||||
|
||||
private Boolean success;
|
||||
|
||||
private Integer code;
|
||||
|
||||
private CreditParseInvokeData data;
|
||||
}
|
||||
@@ -206,6 +206,47 @@ public class HttpUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送POST请求(application/x-www-form-urlencoded格式,带请求头)
|
||||
* @param url 请求URL
|
||||
* @param params 表单参数
|
||||
* @param headers 请求头
|
||||
* @param responseType 响应类型
|
||||
* @return 响应对象
|
||||
*/
|
||||
public <T> T postUrlEncodedForm(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
|
||||
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<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
|
||||
|
||||
if (!response.getStatusCode().is2xxSuccessful()) {
|
||||
throw new LsfxApiException("API调用失败,HTTP状态码: " + response.getStatusCode());
|
||||
}
|
||||
|
||||
T 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
|
||||
|
||||
@@ -2,23 +2,28 @@ package com.ruoyi.lsfx.controller;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.lsfx.client.CreditParseClient;
|
||||
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
||||
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||
import com.ruoyi.lsfx.exception.LsfxApiException;
|
||||
import com.ruoyi.lsfx.util.HttpUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
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.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -31,31 +36,21 @@ class CreditParseControllerTest {
|
||||
private CreditParseController controller;
|
||||
|
||||
@Test
|
||||
void parse_shouldRejectEmptyFile() {
|
||||
AjaxResult result = controller.parse(null, null, null);
|
||||
void parse_shouldRejectBlankRemotePath() {
|
||||
AjaxResult result = controller.parse(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"));
|
||||
}
|
||||
void shouldUseDefaultModelWhenMissing() {
|
||||
CreditParseInvokeResponse response = new CreditParseInvokeResponse();
|
||||
response.setSuccess(true);
|
||||
response.setCode(1000);
|
||||
|
||||
@Test
|
||||
void shouldUseDefaultModelAndTypeWhenMissing() {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file", "credit.html", "text/html", "<html/>".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
CreditParseResponse response = new CreditParseResponse();
|
||||
response.setStatusCode("0");
|
||||
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
||||
when(client.parse(eq("LXCUSTALL"), eq(remotePath))).thenReturn(response);
|
||||
|
||||
when(client.parse(eq("LXCUSTALL"), eq("PERSON"), any(File.class))).thenReturn(response);
|
||||
|
||||
AjaxResult result = controller.parse(file, null, null);
|
||||
AjaxResult result = controller.parse(remotePath, null);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertSame(response, result.get("data"));
|
||||
@@ -63,14 +58,53 @@ class CreditParseControllerTest {
|
||||
|
||||
@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)))
|
||||
when(client.parse(anyString(), anyString()))
|
||||
.thenThrow(new LsfxApiException("超时"));
|
||||
|
||||
AjaxResult result = controller.parse(file, null, null);
|
||||
AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", null);
|
||||
|
||||
assertEquals(500, result.get("code"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
void creditParseClient_shouldPostUrlEncodedRemotePathParameters() {
|
||||
HttpUtil httpUtil = mock(HttpUtil.class);
|
||||
CreditParseClient parseClient = new CreditParseClient();
|
||||
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");
|
||||
|
||||
CreditParseInvokeResponse response = new CreditParseInvokeResponse();
|
||||
response.setSuccess(true);
|
||||
response.setCode(1000);
|
||||
when(httpUtil.postUrlEncodedForm(
|
||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
|
||||
isNull(),
|
||||
eq(CreditParseInvokeResponse.class)
|
||||
)).thenReturn(response);
|
||||
|
||||
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
||||
CreditParseInvokeResponse actual = parseClient.parse(remotePath);
|
||||
|
||||
assertSame(response, actual);
|
||||
ArgumentCaptor<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass((Class) Map.class);
|
||||
verify(httpUtil).postUrlEncodedForm(
|
||||
eq("http://tz/api/service/interface/invokeService/xfeature"),
|
||||
paramsCaptor.capture(),
|
||||
isNull(),
|
||||
eq(CreditParseInvokeResponse.class)
|
||||
);
|
||||
|
||||
Map<String, Object> params = paramsCaptor.getValue();
|
||||
assertNotNull(params.get("serialNum"));
|
||||
assertTrue(params.get("serialNum").toString().startsWith("CCDI_CREDIT_"));
|
||||
assertEquals("902000", params.get("orgCode"));
|
||||
assertEquals("1", params.get("runType"));
|
||||
assertEquals(remotePath, params.get("remotePath"));
|
||||
assertEquals("LXCUSTALL", params.get("model"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user