fix(lsfx): 修复流水分析对接模块的代码质量问题

1. 修复配置问题
   - 替换app-secret占位符为正确的密钥dXj6eHRmPv

2. 添加异常处理
   - HttpUtil所有方法添加完整的异常处理
   - 统一使用LsfxApiException包装异常
   - 检查HTTP状态码和响应体

3. 添加日志记录
   - Client所有方法添加详细的日志记录
   - 记录请求参数、响应结果、耗时
   - 异常情况记录错误日志

4. 完善参数校验
   - 接口1:添加6个必填字段校验
   - 接口2:添加groupId和文件校验,限制文件大小10MB
   - 接口3:添加7个参数校验和日期范围校验
   - 接口4:添加groupId和inprogressList校验

5. 性能优化
   - RestTemplate使用Apache HttpClient连接池
   - 最大连接数100,每个路由最大20个连接
   - 支持连接复用,提升性能

6. 代码审查文档
   - 添加详细的代码审查报告
   - 记录发现的问题和改进建议

修改的文件:
- ccdi-lsfx/pom.xml
- ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.java
- ccdi-lsfx/src/main/java/com/ruoyi/lsfx/config/RestTemplateConfig.java
- ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/LsfxTestController.java
- ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java
- ruoyi-admin/src/main/resources/application-dev.yml
- doc/implementation/lsfx-code-review-20260302.md
This commit is contained in:
wkc
2026-03-03 09:35:27 +08:00
parent 921c15ffad
commit b022ec75b8
13 changed files with 3927 additions and 64 deletions

View File

@@ -26,6 +26,12 @@
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache HttpClient (用于连接池) -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>

View File

@@ -3,8 +3,10 @@ package com.ruoyi.lsfx.client;
import com.ruoyi.lsfx.constants.LsfxConstants;
import com.ruoyi.lsfx.domain.request.*;
import com.ruoyi.lsfx.domain.response.*;
import com.ruoyi.lsfx.exception.LsfxApiException;
import com.ruoyi.lsfx.util.HttpUtil;
import com.ruoyi.lsfx.util.MD5Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@@ -15,6 +17,7 @@ import java.util.Map;
/**
* 流水分析平台客户端
*/
@Slf4j
@Component
public class LsfxAnalysisClient {
@@ -52,67 +55,153 @@ public class LsfxAnalysisClient {
* 获取Token
*/
public GetTokenResponse getToken(GetTokenRequest request) {
String secretCode = MD5Util.generateSecretCode(
request.getProjectNo(),
request.getEntityName(),
appSecret
);
request.setAppSecretCode(secretCode);
request.setAppId(appId);
log.info("【流水分析】获取Token请求: projectNo={}, entityName={}", request.getProjectNo(), request.getEntityName());
long startTime = System.currentTimeMillis();
if (request.getAnalysisType() == null) {
request.setAnalysisType(LsfxConstants.ANALYSIS_TYPE);
}
if (request.getRole() == null) {
request.setRole(LsfxConstants.DEFAULT_ROLE);
}
try {
String secretCode = MD5Util.generateSecretCode(
request.getProjectNo(),
request.getEntityName(),
appSecret
);
request.setAppSecretCode(secretCode);
request.setAppId(appId);
String url = baseUrl + getTokenEndpoint;
return httpUtil.postJson(url, request, null, GetTokenResponse.class);
if (request.getAnalysisType() == null) {
request.setAnalysisType(LsfxConstants.ANALYSIS_TYPE);
}
if (request.getRole() == null) {
request.setRole(LsfxConstants.DEFAULT_ROLE);
}
String url = baseUrl + getTokenEndpoint;
GetTokenResponse response = httpUtil.postJson(url, request, null, GetTokenResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】获取Token成功: projectId={}, 耗时={}ms",
response.getData().getProjectId(), elapsed);
} else {
log.warn("【流水分析】获取Token响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】获取Token失败: projectNo={}, error={}", request.getProjectNo(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】获取Token未知异常: projectNo={}", request.getProjectNo(), e);
throw new LsfxApiException("获取Token失败: " + e.getMessage(), e);
}
}
/**
* 上传文件
*/
public UploadFileResponse uploadFile(Integer groupId, org.springframework.core.io.Resource file) {
String url = baseUrl + uploadFileEndpoint;
log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, file.getFilename());
long startTime = System.currentTimeMillis();
Map<String, Object> params = new HashMap<>();
params.put("groupId", groupId);
params.put("files", file);
try {
String url = baseUrl + uploadFileEndpoint;
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
Map<String, Object> params = new HashMap<>();
params.put("groupId", groupId);
params.put("files", file);
return httpUtil.uploadFile(url, params, headers, UploadFileResponse.class);
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
UploadFileResponse response = httpUtil.uploadFile(url, params, headers, UploadFileResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】上传文件成功: uploadStatus={}, 耗时={}ms",
response.getData().getUploadStatus(), elapsed);
} else {
log.warn("【流水分析】上传文件响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】上传文件失败: groupId={}, error={}", groupId, e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】上传文件未知异常: groupId={}", groupId, e);
throw new LsfxApiException("上传文件失败: " + e.getMessage(), e);
}
}
/**
* 拉取行内流水
*/
public FetchInnerFlowResponse fetchInnerFlow(FetchInnerFlowRequest request) {
String url = baseUrl + fetchInnerFlowEndpoint;
log.info("【流水分析】拉取行内流水请求: groupId={}, customerNo={}", request.getGroupId(), request.getCustomerNo());
long startTime = System.currentTimeMillis();
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
try {
String url = baseUrl + fetchInnerFlowEndpoint;
return httpUtil.postJson(url, request, headers, FetchInnerFlowResponse.class);
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
FetchInnerFlowResponse response = httpUtil.postJson(url, request, headers, FetchInnerFlowResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】拉取行内流水完成: code={}, message={}, 耗时={}ms",
response.getData().getCode(), response.getData().getMessage(), elapsed);
} else {
log.warn("【流水分析】拉取行内流水响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】拉取行内流水失败: groupId={}, error={}", request.getGroupId(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】拉取行内流水未知异常: groupId={}", request.getGroupId(), e);
throw new LsfxApiException("拉取行内流水失败: " + e.getMessage(), e);
}
}
/**
* 检查文件解析状态
*/
public CheckParseStatusResponse checkParseStatus(Integer groupId, String inprogressList) {
String url = baseUrl + checkParseStatusEndpoint;
log.info("【流水分析】检查文件解析状态: groupId={}, inprogressList={}", groupId, inprogressList);
long startTime = System.currentTimeMillis();
Map<String, Object> params = new HashMap<>();
params.put("groupId", groupId);
params.put("inprogressList", inprogressList);
try {
String url = baseUrl + checkParseStatusEndpoint;
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
Map<String, Object> params = new HashMap<>();
params.put("groupId", groupId);
params.put("inprogressList", inprogressList);
return httpUtil.postJson(url, params, headers, CheckParseStatusResponse.class);
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
CheckParseStatusResponse response = httpUtil.postJson(url, params, headers, CheckParseStatusResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】检查解析状态完成: parsing={}, pendingList.size={}, 耗时={}ms",
response.getData().getParsing(),
response.getData().getPendingList() != null ? response.getData().getPendingList().size() : 0,
elapsed);
} else {
log.warn("【流水分析】检查解析状态响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】检查解析状态失败: groupId={}, error={}", groupId, e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】检查解析状态未知异常: groupId={}", groupId, e);
throw new LsfxApiException("检查解析状态失败: " + e.getMessage(), e);
}
}
/**
@@ -123,11 +212,35 @@ public class LsfxAnalysisClient {
* @return 流水明细列表
*/
public GetBankStatementResponse getBankStatement(GetBankStatementRequest request) {
String url = baseUrl + getBankStatementEndpoint;
log.info("【流水分析】获取银行流水请求: groupId={}, logId={}, pageNow={}, pageSize={}",
request.getGroupId(), request.getLogId(), request.getPageNow(), request.getPageSize());
long startTime = System.currentTimeMillis();
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
try {
String url = baseUrl + getBankStatementEndpoint;
return httpUtil.postJson(url, request, headers, GetBankStatementResponse.class);
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
GetBankStatementResponse response = httpUtil.postJson(url, request, headers, GetBankStatementResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】获取银行流水成功: totalCount={}, 耗时={}ms",
response.getData().getTotalCount(), elapsed);
} else {
log.warn("【流水分析】获取银行流水响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】获取银行流水失败: groupId={}, logId={}, error={}",
request.getGroupId(), request.getLogId(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】获取银行流水未知异常: groupId={}, logId={}",
request.getGroupId(), request.getLogId(), e);
throw new LsfxApiException("获取银行流水失败: " + e.getMessage(), e);
}
}
}

View File

@@ -1,13 +1,17 @@
package com.ruoyi.lsfx.config;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置
* RestTemplate配置(使用连接池优化性能)
*/
@Configuration
public class RestTemplateConfig {
@@ -18,11 +22,29 @@ public class RestTemplateConfig {
@Value("${lsfx.api.read-timeout:60000}")
private int readTimeout;
@Value("${lsfx.api.pool.max-total:100}")
private int maxTotal;
@Value("${lsfx.api.pool.default-max-per-route:20}")
private int defaultMaxPerRoute;
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
// 创建连接池管理器
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(maxTotal); // 最大连接数
connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); // 每个路由的最大连接数
// 创建HttpClient并设置连接池
HttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
// 创建HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(connectionTimeout);
factory.setReadTimeout(readTimeout);
factory.setConnectionRequestTimeout(connectionTimeout);
return new RestTemplate(factory);
}
}

View File

@@ -1,6 +1,7 @@
package com.ruoyi.lsfx.controller;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
import com.ruoyi.lsfx.domain.request.*;
import com.ruoyi.lsfx.domain.response.*;
@@ -26,6 +27,26 @@ public class LsfxTestController {
@Operation(summary = "获取Token", description = "创建项目并获取访问Token")
@PostMapping("/getToken")
public AjaxResult getToken(@RequestBody GetTokenRequest request) {
// 参数校验
if (StringUtils.isBlank(request.getProjectNo())) {
return AjaxResult.error("参数不完整projectNo为必填");
}
if (StringUtils.isBlank(request.getEntityName())) {
return AjaxResult.error("参数不完整entityName为必填");
}
if (StringUtils.isBlank(request.getUserId())) {
return AjaxResult.error("参数不完整userId为必填");
}
if (StringUtils.isBlank(request.getUserName())) {
return AjaxResult.error("参数不完整userName为必填");
}
if (StringUtils.isBlank(request.getOrgCode())) {
return AjaxResult.error("参数不完整orgCode为必填");
}
if (StringUtils.isBlank(request.getDepartmentCode())) {
return AjaxResult.error("参数不完整departmentCode为必填");
}
GetTokenResponse response = lsfxAnalysisClient.getToken(request);
return AjaxResult.success(response);
}
@@ -36,6 +57,17 @@ public class LsfxTestController {
@Parameter(description = "项目ID") @RequestParam Integer groupId,
@Parameter(description = "流水文件") @RequestParam("file") MultipartFile file
) {
// 参数校验
if (groupId == null || groupId <= 0) {
return AjaxResult.error("参数不完整groupId为必填且大于0");
}
if (file == null || file.isEmpty()) {
return AjaxResult.error("参数不完整:文件不能为空");
}
if (file.getSize() > 10 * 1024 * 1024) { // 10MB限制
return AjaxResult.error("文件大小超过限制最大10MB");
}
org.springframework.core.io.Resource fileResource = file.getResource();
UploadFileResponse response = lsfxAnalysisClient.uploadFile(groupId, fileResource);
return AjaxResult.success(response);
@@ -44,6 +76,26 @@ public class LsfxTestController {
@Operation(summary = "拉取行内流水", description = "从数仓拉取行内流水数据")
@PostMapping("/fetchInnerFlow")
public AjaxResult fetchInnerFlow(@RequestBody FetchInnerFlowRequest request) {
// 参数校验
if (request.getGroupId() == null || request.getGroupId() <= 0) {
return AjaxResult.error("参数不完整groupId为必填且大于0");
}
if (StringUtils.isEmpty(request.getCustomerNo())) {
return AjaxResult.error("参数不完整customerNo为必填");
}
if (request.getRequestDateId() == null) {
return AjaxResult.error("参数不完整requestDateId为必填");
}
if (request.getDataStartDateId() == null) {
return AjaxResult.error("参数不完整dataStartDateId为必填");
}
if (request.getDataEndDateId() == null) {
return AjaxResult.error("参数不完整dataEndDateId为必填");
}
if (request.getDataStartDateId() > request.getDataEndDateId()) {
return AjaxResult.error("参数错误:开始日期不能大于结束日期");
}
FetchInnerFlowResponse response = lsfxAnalysisClient.fetchInnerFlow(request);
return AjaxResult.success(response);
}
@@ -54,6 +106,14 @@ public class LsfxTestController {
@Parameter(description = "项目ID") @RequestParam Integer groupId,
@Parameter(description = "文件ID列表") @RequestParam String inprogressList
) {
// 参数校验
if (groupId == null || groupId <= 0) {
return AjaxResult.error("参数不完整groupId为必填且大于0");
}
if (StringUtils.isEmpty(inprogressList)) {
return AjaxResult.error("参数不完整inprogressList为必填");
}
CheckParseStatusResponse response = lsfxAnalysisClient.checkParseStatus(groupId, inprogressList);
return AjaxResult.success(response);
}

View File

@@ -1,9 +1,11 @@
package com.ruoyi.lsfx.util;
import com.ruoyi.lsfx.exception.LsfxApiException;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import jakarta.annotation.Resource;
@@ -26,13 +28,27 @@ public class HttpUtil {
* @return 响应对象
*/
public <T> T get(String url, Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
HttpEntity<Void> requestEntity = new HttpEntity<>(httpHeaders);
try {
HttpHeaders httpHeaders = createHeaders(headers);
HttpEntity<Void> requestEntity = new HttpEntity<>(httpHeaders);
ResponseEntity<T> response = restTemplate.exchange(
url, HttpMethod.GET, requestEntity, responseType
);
return response.getBody();
ResponseEntity<T> response = restTemplate.exchange(
url, HttpMethod.GET, requestEntity, responseType
);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new LsfxApiException("API调用失败HTTP状态码: " + response.getStatusCode());
}
T body = response.getBody();
if (body == null) {
throw new LsfxApiException("API返回数据为空");
}
return body;
} catch (RestClientException e) {
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
}
}
/**
@@ -44,13 +60,27 @@ public class HttpUtil {
* @return 响应对象
*/
public <T> T postJson(String url, Object request, Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
try {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> requestEntity = new HttpEntity<>(request, httpHeaders);
HttpEntity<Object> requestEntity = new HttpEntity<>(request, httpHeaders);
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
return response.getBody();
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new LsfxApiException("API调用失败HTTP状态码: " + response.getStatusCode());
}
T body = response.getBody();
if (body == null) {
throw new LsfxApiException("API返回数据为空");
}
return body;
} catch (RestClientException e) {
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
}
}
/**
@@ -62,18 +92,32 @@ public class HttpUtil {
* @return 响应对象
*/
public <T> T uploadFile(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
try {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach(body::add);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach(body::add);
}
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, httpHeaders);
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new LsfxApiException("文件上传失败HTTP状态码: " + response.getStatusCode());
}
T responseBody = response.getBody();
if (responseBody == null) {
throw new LsfxApiException("文件上传返回数据为空");
}
return responseBody;
} catch (RestClientException e) {
throw new LsfxApiException("文件上传请求失败: " + e.getMessage(), e);
}
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, httpHeaders);
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
return response.getBody();
}
/**