Files
ccdi/ccdi-lsfx/src/main/java/com/ruoyi/lsfx/util/HttpUtil.java

316 lines
13 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.ruoyi.lsfx.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.lsfx.domain.CallerContext;
import com.ruoyi.lsfx.exception.LsfxApiException;
import com.ruoyi.system.domain.SysApiLog;
import com.ruoyi.system.service.ISysApiLogService;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 业务外部HTTP请求工具统一保存完整调用日志。
*/
@Component
public class HttpUtil {
private static final Logger log = LoggerFactory.getLogger(HttpUtil.class);
private static final String CALL_SUCCESS = "0";
private static final String CALL_FAILED = "1";
@Resource
private RestTemplate restTemplate;
@Resource
private ObjectMapper objectMapper;
@Resource
private ISysApiLogService apiLogService;
public static org.springframework.core.io.Resource namedFileResource(File file, String filename) {
return new NamedFileSystemResource(file, filename);
}
private static class NamedFileSystemResource extends FileSystemResource {
private final String filename;
NamedFileSystemResource(File file, String filename) {
super(file);
this.filename = StringUtils.hasText(filename) ? filename : file.getName();
}
@Override
public String getFilename() {
return filename;
}
}
public <T> T get(CallerContext caller, String url, Map<String, Object> params,
Map<String, String> headers, Class<T> responseType) {
UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(url);
if (params != null) {
params.forEach((key, value) -> {
if (value != null) {
builder.queryParam(key, value);
}
});
}
String fullUrl = builder.toUriString();
HttpHeaders httpHeaders = createHeaders(headers);
return execute(caller, fullUrl, HttpMethod.GET, httpHeaders, params,
new HttpEntity<>(httpHeaders), responseType, "API返回数据为空", "GET请求异常");
}
public <T> T get(CallerContext caller, String url, Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
return execute(caller, url, HttpMethod.GET, httpHeaders, null,
new HttpEntity<>(httpHeaders), responseType, "API返回数据为空", "网络请求失败");
}
public <T> T postJson(CallerContext caller, String url, Object request,
Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
return execute(caller, url, HttpMethod.POST, httpHeaders, request,
new HttpEntity<>(request, httpHeaders), responseType, "API返回数据为空", "网络请求失败");
}
public <T> T postFormData(CallerContext caller, String url, Map<String, Object> params,
Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach(body::add);
}
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
new HttpEntity<>(body, httpHeaders), responseType, "API返回数据为空", "网络请求失败");
}
public <T> T postUrlEncodedForm(CallerContext caller, String url, Map<String, Object> params,
Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = toUrlEncodedBody(params);
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
new HttpEntity<>(body, httpHeaders), responseType, "API返回数据为空", "网络请求失败");
}
public String postUrlEncodedFormForString(CallerContext caller, String url,
Map<String, Object> params, Map<String, String> headers) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = toUrlEncodedBody(params);
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
new HttpEntity<>(body, httpHeaders), String.class, "API返回数据为空", "网络请求失败");
}
public <T> T uploadFile(CallerContext caller, String url, Map<String, Object> params,
Map<String, String> headers, Class<T> responseType) {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach((key, value) -> {
if (value instanceof File file) {
body.add(key, new FileSystemResource(file));
} else {
body.add(key, value);
}
});
}
return execute(caller, url, HttpMethod.POST, httpHeaders, params,
new HttpEntity<>(body, httpHeaders), responseType, "文件上传返回数据为空", "文件上传请求失败");
}
private <T> T execute(CallerContext caller, String url, HttpMethod method, HttpHeaders requestHeaders,
Object requestParams, HttpEntity<?> requestEntity, Class<T> responseType,
String emptyResponseMessage, String requestFailureMessage) {
if (caller == null) {
throw new IllegalArgumentException("接口调用用户不能为空");
}
long startTime = System.currentTimeMillis();
SysApiLog apiLog = createBaseApiLog(caller, url, method, startTime);
try {
captureRequest(apiLog, requestHeaders, requestParams);
} catch (Exception e) {
log.error("接口日志请求快照构建失败: method={}, url={}", method, url, e);
}
try {
ResponseEntity<String> response = restTemplate.exchange(url, method, requestEntity, String.class);
captureResponse(apiLog, response);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new LsfxApiException("API调用失败HTTP状态码: " + response.getStatusCode().value());
}
if (!StringUtils.hasText(response.getBody())) {
throw new LsfxApiException(emptyResponseMessage);
}
T result = parseResponse(response.getBody(), responseType);
apiLog.setCallStatus(CALL_SUCCESS);
return result;
} catch (RestClientResponseException e) {
apiLog.setResponseStatus(e.getStatusCode().value());
apiLog.setResponseHeaders(safeSerialize(e.getResponseHeaders()));
apiLog.setResponseBody(e.getResponseBodyAsString());
apiLog.setErrorMsg(formatException(e));
throw new LsfxApiException(requestFailureMessage + ": " + e.getMessage(), e);
} catch (LsfxApiException e) {
apiLog.setErrorMsg(formatException(e));
throw e;
} catch (RestClientException e) {
apiLog.setErrorMsg(formatException(e));
throw new LsfxApiException(requestFailureMessage + ": " + e.getMessage(), e);
} catch (Exception e) {
apiLog.setErrorMsg(formatException(e));
throw new LsfxApiException("响应解析失败: " + e.getMessage(), e);
} finally {
apiLog.setCostTime(System.currentTimeMillis() - startTime);
persistApiLog(apiLog);
}
}
private SysApiLog createBaseApiLog(CallerContext caller, String url, HttpMethod method, long startTime) {
SysApiLog apiLog = new SysApiLog();
apiLog.setCallerUserId(caller.userId());
apiLog.setCallerUsername(caller.username());
apiLog.setApiUrl(url);
apiLog.setHttpMethod(method.name());
apiLog.setCallStatus(CALL_FAILED);
apiLog.setCallTime(new Date(startTime));
return apiLog;
}
private void captureRequest(SysApiLog apiLog, HttpHeaders headers, Object params) {
apiLog.setContentType(headers.getContentType() == null ? null : headers.getContentType().toString());
apiLog.setRequestHeaders(safeSerialize(headers));
apiLog.setRequestParams(safeSerialize(normalizeValue(params)));
}
private void captureResponse(SysApiLog apiLog, ResponseEntity<String> response) {
apiLog.setResponseStatus(response.getStatusCode().value());
apiLog.setResponseHeaders(safeSerialize(response.getHeaders()));
apiLog.setResponseBody(response.getBody());
}
@SuppressWarnings("unchecked")
private <T> T parseResponse(String responseBody, Class<T> responseType) throws Exception {
if (String.class.equals(responseType)) {
return (T) responseBody;
}
return objectMapper.readValue(responseBody, responseType);
}
private MultiValueMap<String, String> toUrlEncodedBody(Map<String, Object> params) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach((key, value) -> {
if (value != null) {
body.add(key, value.toString());
}
});
}
return body;
}
private HttpHeaders createHeaders(Map<String, String> headers) {
HttpHeaders httpHeaders = new HttpHeaders();
if (headers != null) {
headers.forEach(httpHeaders::set);
}
return httpHeaders;
}
private Object normalizeValue(Object value) {
if (value == null) {
return null;
}
if (value instanceof File file) {
return fileMetadata(file.getName(), file.length());
}
if (value instanceof FileSystemResource resource) {
return fileMetadata(resource.getFilename(), resource.getFile().length());
}
if (value instanceof org.springframework.core.io.Resource resource) {
return fileMetadata(resource.getFilename(), null);
}
if (value instanceof Map<?, ?> map) {
Map<String, Object> normalized = new LinkedHashMap<>();
map.forEach((key, item) -> normalized.put(String.valueOf(key), normalizeValue(item)));
return normalized;
}
if (value instanceof Iterable<?> iterable) {
List<Object> normalized = new ArrayList<>();
iterable.forEach(item -> normalized.add(normalizeValue(item)));
return normalized;
}
if (value.getClass().isArray()) {
List<Object> normalized = new ArrayList<>();
for (int i = 0; i < Array.getLength(value); i++) {
normalized.add(normalizeValue(Array.get(value, i)));
}
return normalized;
}
return value;
}
private Map<String, Object> fileMetadata(String filename, Long size) {
Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("filename", filename);
metadata.put("size", size);
metadata.put("contentType", MediaTypeFactory.getMediaType(filename == null ? "" : filename)
.map(MediaType::toString).orElse(null));
return metadata;
}
private String safeSerialize(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (Exception e) {
log.error("接口日志序列化失败", e);
return String.valueOf(value);
}
}
private String formatException(Exception exception) {
StringWriter writer = new StringWriter();
exception.printStackTrace(new PrintWriter(writer));
return writer.toString();
}
private void persistApiLog(SysApiLog apiLog) {
try {
apiLogService.recordApiLog(apiLog);
} catch (Exception e) {
log.error("接口日志入库失败: method={}, url={}", apiLog.getHttpMethod(), apiLog.getApiUrl(), e);
}
}
}