5.0版本 发布 重构下载方法 修复pdf 占用demo文件夹

This commit is contained in:
高雄
2026-01-22 17:17:37 +08:00
parent 1e04822532
commit b08f4657e5
5 changed files with 381 additions and 281 deletions

View File

@@ -0,0 +1,355 @@
package cn.keking.utils;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
import org.apache.hc.core5.util.TimeValue;
import org.apache.hc.core5.util.Timeout;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.SSLContext;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* HTTP请求工具类统一处理HTTP请求逻辑
* 优化版本:支持连接复用,减少开销
*/
public class HttpRequestUtils {
private static final Logger logger = LoggerFactory.getLogger(HttpRequestUtils.class);
private static final ObjectMapper mapper = new ObjectMapper();
// 连接池管理器(静态变量,全局共享)
private static volatile PoolingHttpClientConnectionManager connectionManager;
// 用于缓存不同配置的HttpClient实例
private static final Map<String, CloseableHttpClient> httpClientCache = new ConcurrentHashMap<>();
// 用于缓存不同配置的RestTemplate实例
private static final Map<String, RestTemplate> restTemplateCache = new ConcurrentHashMap<>();
// 默认连接池配置
private static final int DEFAULT_MAX_TOTAL = 200; // 最大连接数
private static final int DEFAULT_MAX_PER_ROUTE = 50; // 每个路由最大连接数
/**
* 初始化连接池管理器(懒加载)
*/
private static PoolingHttpClientConnectionManager getConnectionManager() throws Exception {
if (connectionManager == null) {
synchronized (HttpRequestUtils.class) {
if (connectionManager == null) {
// 创建连接池管理器
PoolingHttpClientConnectionManagerBuilder builder = PoolingHttpClientConnectionManagerBuilder.create();
// 如果配置忽略SSL使用自定义TLS策略
if (ConfigConstants.isIgnoreSSL()) {
SSLContext sslContext = SslUtils.createIgnoreVerifySSL();
DefaultClientTlsStrategy tlsStrategy = new DefaultClientTlsStrategy(
sslContext, NoopHostnameVerifier.INSTANCE);
builder.setTlsSocketStrategy(tlsStrategy);
}
// 设置连接池参数
builder.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX)
.setConnPoolPolicy(PoolReusePolicy.LIFO)
.setMaxConnTotal(DEFAULT_MAX_TOTAL)
.setMaxConnPerRoute(DEFAULT_MAX_PER_ROUTE);
// 设置Socket配置
SocketConfig socketConfig = SocketConfig.custom()
.setTcpNoDelay(true)
.setSoKeepAlive(true)
.setSoReuseAddress(true)
.setSoTimeout(Timeout.ofSeconds(30))
.build();
builder.setDefaultSocketConfig(socketConfig);
// 设置连接配置
ConnectionConfig connectionConfig = ConnectionConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(10))
.setSocketTimeout(Timeout.ofSeconds(30))
.setTimeToLive(TimeValue.ofMinutes(5))
.build();
builder.setDefaultConnectionConfig(connectionConfig);
connectionManager = builder.build();
// 启动空闲连接清理线程
startIdleConnectionMonitor();
logger.info("HTTP连接池管理器初始化完成最大连接数{},每个路由最大连接数:{}",
DEFAULT_MAX_TOTAL, DEFAULT_MAX_PER_ROUTE);
}
}
}
return connectionManager;
}
/**
* 启动空闲连接监控线程
*/
private static void startIdleConnectionMonitor() {
Thread monitorThread = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
synchronized (HttpRequestUtils.class) {
Thread.sleep(30000); // 每30秒检查一次
if (connectionManager != null) {
// 关闭过期的连接
connectionManager.closeExpired();
// 关闭空闲超过30秒的连接
connectionManager.closeIdle(TimeValue.ofSeconds(30));
// 可选:打印连接池状态
if (logger.isDebugEnabled()) {
logger.debug("连接池状态:最大连接数={}, 每个路由最大连接数={}",
connectionManager.getMaxTotal(),
connectionManager.getDefaultMaxPerRoute());
}
}
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
logger.info("连接池监控线程被中断");
} catch (Exception e) {
logger.error("连接池监控异常", e);
}
});
monitorThread.setDaemon(true);
monitorThread.setName("http-connection-monitor");
monitorThread.start();
}
/**
* 创建根据配置定制的HttpClient支持复用
*/
public static CloseableHttpClient createConfiguredHttpClient() throws Exception {
String cacheKey = buildHttpClientCacheKey();
// 尝试从缓存获取
CloseableHttpClient cachedClient = httpClientCache.get(cacheKey);
if (cachedClient != null) {
// HttpClient 5.x 没有 isClosed() 方法,我们需要通过其他方式判断
// 暂时假设缓存的客户端都是可用的,如果有问题会在使用时报错
return cachedClient;
}
// 创建新的HttpClient
synchronized (httpClientCache) {
// 双重检查
cachedClient = httpClientCache.get(cacheKey);
if (cachedClient != null) {
return cachedClient;
}
// 构建HttpClientBuilder
HttpClientBuilder httpClientBuilder = HttpClients.custom()
.setConnectionManager(getConnectionManager())
.setConnectionManagerShared(true); // 共享连接管理器
// 使用SslUtils配置HttpClientBuilder
CloseableHttpClient httpClient = SslUtils.configureHttpClientBuilder(
httpClientBuilder,
ConfigConstants.isIgnoreSSL(),
ConfigConstants.isEnableRedirect()
).build();
// 缓存HttpClient
httpClientCache.put(cacheKey, httpClient);
logger.debug("创建并缓存新的HttpClient实例缓存键{}", cacheKey);
return httpClient;
}
}
/**
* 构建HttpClient缓存键
*/
private static String buildHttpClientCacheKey() {
return String.format("ignoreSSL_%s_enableRedirect_%s",
ConfigConstants.isIgnoreSSL(),
ConfigConstants.isEnableRedirect());
}
/**
* 获取缓存的RestTemplate减少对象创建
*/
private static RestTemplate getCachedRestTemplate(CloseableHttpClient httpClient) {
String cacheKey = "restTemplate_" + System.identityHashCode(httpClient);
return restTemplateCache.computeIfAbsent(cacheKey, key -> {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
// 设置连接超时和读取超时
factory.setConnectTimeout(30000);
factory.setReadTimeout(30000);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(factory);
logger.debug("创建并缓存新的RestTemplate实例缓存键{}", cacheKey);
return restTemplate;
});
}
/**
* 执行HTTP请求使用连接池
*/
public static void executeHttpRequest(java.net.URL url, CloseableHttpClient httpClient,
FileAttribute fileAttribute, FileResponseHandler handler) throws Exception {
// 获取缓存的RestTemplate
RestTemplate restTemplate = getCachedRestTemplate(httpClient);
String finalUrlStr = url.toString();
RequestCallback requestCallback = createRequestCallback(finalUrlStr, fileAttribute);
try {
restTemplate.execute(url.toURI(), HttpMethod.GET, requestCallback, response -> {
FileResponseWrapper wrapper = new FileResponseWrapper();
wrapper.setInputStream(response.getBody());
wrapper.setContentType(WebUtils.headersType(response));
try {
handler.handleResponse(wrapper);
} catch (Exception e) {
logger.error("处理文件响应时出错", e);
throw new RuntimeException(e);
}
return null;
});
} catch (Exception e) {
// 如果是SSL证书错误给出建议
if (e.getMessage() != null &&
(e.getMessage().contains("SSL") ||
e.getMessage().contains("证书") ||
e.getMessage().contains("certificate")) &&
!ConfigConstants.isIgnoreSSL()) {
logger.warn("SSL证书验证失败建议启用SSL忽略功能或检查证书");
}
throw e;
}
// 注意不再关闭HttpClient由连接池管理
}
/**
* 创建请求回调
*/
private static RequestCallback createRequestCallback(String finalUrlStr, FileAttribute fileAttribute) {
return request -> {
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
WebUtils.applyBasicAuthHeaders(request.getHeaders(), finalUrlStr);
// 添加Keep-Alive头
request.getHeaders().set("Connection", "keep-alive");
request.getHeaders().set("Keep-Alive", "timeout=60");
String proxyAuthorization = fileAttribute.getKkProxyAuthorization();
if (StringUtils.hasText(proxyAuthorization)) {
Map<String, String> proxyAuthorizationMap = mapper.readValue(
proxyAuthorization,
TypeFactory.defaultInstance().constructMapType(Map.class, String.class, String.class)
);
proxyAuthorizationMap.forEach((key, value) -> request.getHeaders().set(key, value));
}
};
}
/**
* 清理资源(在应用关闭时调用)
*/
public static void shutdown() {
logger.info("开始清理HTTP连接池资源...");
// 关闭所有缓存的HttpClient
httpClientCache.values().forEach(client -> {
try {
client.close();
} catch (Exception e) {
logger.warn("关闭HttpClient失败", e);
}
});
httpClientCache.clear();
// 关闭连接池管理器
if (connectionManager != null) {
try {
connectionManager.close();
logger.info("HTTP连接池管理器已关闭");
} catch (Exception e) {
logger.warn("关闭连接池管理器失败", e);
}
connectionManager = null;
}
// 清空RestTemplate缓存
restTemplateCache.clear();
logger.info("HTTP连接池资源清理完成");
}
/**
* 文件响应处理器接口
*/
public interface FileResponseHandler {
void handleResponse(FileResponseWrapper responseWrapper) throws Exception;
}
/**
* 文件响应包装器
*/
public static class FileResponseWrapper {
private InputStream inputStream;
private String contentType;
private boolean hasError;
public InputStream getInputStream() {
return inputStream;
}
public void setInputStream(InputStream inputStream) {
this.inputStream = inputStream;
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public boolean isHasError() {
return hasError;
}
public void setHasError(boolean hasError) {
this.hasError = hasError;
}
}
}