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

@@ -60,6 +60,9 @@ public class PdfFilePreviewImpl implements FilePreview {
String originFilePath; //原始文件路径
String cacheName = pdfName+officePreviewType;
String filePassword = fileAttribute.getFilePassword(); // 获取密码
if("demo.pdf".equals(pdfName)){
return otherFilePreview.notSupportedFile(model, fileAttribute, "不能使用该文件名,请更换其他文件名在进行转换");
}
// 查询转换状态
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, pdfName, cacheName, fileAttribute);
if (statusResult != null) {

View File

@@ -3,30 +3,11 @@ package cn.keking.utils;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
import cn.keking.model.ReturnResponse;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import io.mola.galimatias.GalimatiasParseException;
import org.apache.commons.io.FileUtils;
import org.apache.hc.client5.http.ConnectionKeepAliveStrategy;
import org.apache.hc.client5.http.config.ConnectionConfig;
import org.apache.hc.client5.http.config.RequestConfig;
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.core5.http.HttpResponse;
import org.apache.hc.core5.http.protocol.HttpContext;
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 java.io.File;
import java.io.FileNotFoundException;
@@ -34,10 +15,7 @@ import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static cn.keking.utils.KkFileUtils.*;
@@ -52,56 +30,6 @@ public class DownloadUtils {
private static final String URL_PARAM_FTP_PASSWORD = "ftp.password";
private static final String URL_PARAM_FTP_CONTROL_ENCODING = "ftp.control.encoding";
private static final String URL_PARAM_FTP_PORT = "ftp.control.port";
private static final ObjectMapper mapper = new ObjectMapper();
// 使用静态单例的HttpClient和RestTemplate实现连接复用
private static volatile CloseableHttpClient httpClient;
private static volatile RestTemplate restTemplate;
// 获取单例HttpClient线程安全
private static CloseableHttpClient getHttpClient() throws Exception {
if (httpClient == null) {
synchronized (DownloadUtils.class) {
if (httpClient == null) {
httpClient = createConfiguredHttpClient();
logger.info("HttpClient初始化完成已启用连接池和超时配置");
}
}
}
return httpClient;
}
// 获取单例RestTemplate
private static RestTemplate getRestTemplate() throws Exception {
if (restTemplate == null) {
synchronized (DownloadUtils.class) {
if (restTemplate == null) {
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(getHttpClient());
// 设置连接和读取超时(毫秒)
factory.setConnectTimeout(10000); // 10秒连接超时
factory.setReadTimeout(30000); // 30秒读取超时
factory.setConnectionRequestTimeout(5000); // 5秒获取连接超时
restTemplate = new RestTemplate(factory);
}
}
}
return restTemplate;
}
// 应用关闭时清理资源
public static void shutdown() {
if (httpClient != null) {
try {
httpClient.close();
logger.info("HttpClient已关闭");
} catch (IOException e) {
logger.warn("关闭HttpClient失败", e);
}
}
}
/**
* @param fileAttribute fileAttribute
@@ -149,79 +77,31 @@ public class DownloadUtils {
if (!fileAttribute.getSkipDownLoad()) {
if (isHttpUrl(url)) {
File realFile = new File(realPath);
// 使用单例的RestTemplate复用连接池
RestTemplate template = getRestTemplate();
CloseableHttpClient httpClient = HttpRequestUtils.createConfiguredHttpClient();
String finalUrlStr = urlStr;
RequestCallback requestCallback = request -> {
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
WebUtils.applyBasicAuthHeaders(request.getHeaders(), finalUrlStr);
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));
}
};
try {
final boolean[] hasError = {false};
String finalUrlStr1 = urlStr;
template.execute(url.toURI(), HttpMethod.GET, requestCallback, fileResponse -> {
try {
HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> {
// 获取响应头中的Content-Type
String contentType = WebUtils.headersType(fileResponse);
String contentType = responseWrapper.getContentType();
// 如果是Office/设计文件需要校验MIME类型
if ( WebUtils.isMimeCheckRequired(fileSuffix)) {
if (! WebUtils.isValidMimeType(contentType, fileSuffix)) {
if (WebUtils.isMimeCheckRequired(fileSuffix)) {
if (!WebUtils.isValidMimeType(contentType, fileSuffix)) {
logger.error("文件类型错误期望二进制文件但接收到文本类型url: {}, Content-Type: {}",
finalUrlStr1, contentType);
hasError[0] = true;
// 重要:关闭响应流,不读取后续数据
fileResponse.close();
return null;
finalUrlStr, contentType);
responseWrapper.setHasError(true);
return;
}
}
// 保存文件
FileUtils.copyToFile(fileResponse.getBody(), realFile);
} catch (Exception e) {
logger.error("处理文件响应时出错", e);
hasError[0] = true;
}
return null;
FileUtils.copyToFile(responseWrapper.getInputStream(), realFile);
});
// 如果下载过程中出现错误
if (hasError[0]) {
response.setCode(1);
response.setContent(null);
response.setMsg("文件类型校验失败");
return response;
}
} 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忽略功能或检查证书");
}
response.setCode(1);
response.setContent(null);
response.setMsg("下载失败:" + e.getMessage());
return response;
}
// 不再需要finally块中关闭HttpClient因为复用
} else if (isFtpUrl(url)) {
String ftpUsername = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_USERNAME);
String ftpPassword = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_PASSWORD);
String ftpControlEncoding = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_CONTROL_ENCODING);
String ftpport = WebUtils.getUrlParameterReg(realPath, URL_PARAM_FTP_PORT);
FtpUtils.download(fileAttribute.getUrl(),ftpport, realPath, ftpUsername, ftpPassword, ftpControlEncoding);
FtpUtils.download(fileAttribute.getUrl(), ftpport, realPath, ftpUsername, ftpPassword, ftpControlEncoding);
} else if (isFileUrl(url)) { // 添加对file协议的支持
handleFileProtocol(url, realPath);
} else {
@@ -233,7 +113,7 @@ public class DownloadUtils {
response.setMsg(fileName);
return response;
} catch (IOException | GalimatiasParseException e) {
logger.error("文件下载失败url{}", urlStr, e);
logger.error("文件下载失败url{}", urlStr);
response.setCode(1);
response.setContent(null);
if (e instanceof FileNotFoundException) {
@@ -243,81 +123,10 @@ public class DownloadUtils {
}
return response;
} catch (Exception e) {
logger.error("下载过程发生未知异常", e);
response.setCode(1);
response.setContent(null);
response.setMsg("下载失败:" + e.getMessage());
return response;
throw new RuntimeException(e);
}
}
/**
* 创建根据配置定制的HttpClient连接池版本
*/
private static CloseableHttpClient createConfiguredHttpClient() throws Exception {
// 使用新的Builder API创建连接池管理器
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
.setMaxConnTotal(100) // 最大连接数
.setMaxConnPerRoute(20) // 每个路由最大连接数
.setDefaultConnectionConfig(ConnectionConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(10)) // 连接超时10秒
.setSocketTimeout(Timeout.ofSeconds(30)) // Socket超时30秒
.setTimeToLive(TimeValue.ofMinutes(10)) // 连接存活时间10分钟
.build())
.build();
// 创建请求配置
RequestConfig requestConfig = RequestConfig.custom()
.setResponseTimeout(Timeout.ofSeconds(30)) // 响应超时30秒
.setConnectionRequestTimeout(Timeout.ofSeconds(5)) // 获取连接超时5秒
.build();
// 创建HttpClientBuilder
HttpClientBuilder builder = HttpClients.custom()
.setConnectionManager(connectionManager)
.setDefaultRequestConfig(requestConfig);
// 配置Keep-Alive策略
ConnectionKeepAliveStrategy keepAliveStrategy = (response, context) -> {
// 默认保持30秒
return TimeValue.ofSeconds(30);
};
builder.setKeepAliveStrategy(keepAliveStrategy);
// 配置SSL
if (ConfigConstants.isIgnoreSSL()) {
logger.debug("创建忽略SSL验证的HttpClient");
// 如果SslUtils支持HttpClient5则使用它来创建client
// 否则我们需要在这里配置忽略SSL
return createHttpClientWithConfig(builder);
} else {
logger.debug("创建标准HttpClient");
}
// 配置重定向
if (!ConfigConstants.isEnableRedirect()) {
logger.debug("禁用HttpClient重定向");
builder.disableRedirectHandling();
}
return builder.build();
}
/**
* 创建配置了忽略SSL的HttpClient
*/
private static CloseableHttpClient createHttpClientWithConfig(HttpClientBuilder builder) throws Exception {
// 如果SslUtils支持直接配置builder
// SslUtils.configureIgnoreSsl(builder);
// return builder.build();
// 否则使用SslUtils创建client
CloseableHttpClient sslIgnoredClient = SslUtils.createHttpClientIgnoreSsl();
logger.warn("SslUtils.createHttpClientIgnoreSsl()可能没有连接池配置建议修改SslUtils以支持builder配置");
return sslIgnoredClient;
}
// 处理file协议的文件下载
private static void handleFileProtocol(URL url, String targetPath) throws IOException {
File sourceFile = new File(url.getPath());

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;
}
}
}

View File

@@ -80,8 +80,9 @@ public class SslUtils {
/**
* 创建忽略SSL验证的SSLContext
* 修改为public访问权限
*/
private static SSLContext createIgnoreVerifySSL() throws Exception {
public static SSLContext createIgnoreVerifySSL() throws Exception {
// 使用TLSv1.2或TLSv1.3
SSLContext sc = SSLContext.getInstance("TLSv1.2");

View File

@@ -7,21 +7,12 @@ import cn.keking.service.FilePreview;
import cn.keking.service.FilePreviewFactory;
import cn.keking.service.cache.CacheService;
import cn.keking.service.impl.OtherFilePreviewImpl;
import cn.keking.utils.FtpUtils;
import cn.keking.utils.KkFileUtils;
import cn.keking.utils.SslUtils;
import cn.keking.utils.WebUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import cn.keking.utils.*;
import fr.opensagres.xdocreport.core.io.IOUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
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.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.ObjectUtils;
@@ -29,8 +20,6 @@ import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@@ -39,7 +28,6 @@ import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static cn.keking.service.FilePreview.PICTURE_FILE_PREVIEW_PAGE;
import static cn.keking.utils.KkFileUtils.isFtpUrl;
@@ -64,7 +52,6 @@ public class OnlinePreviewController {
private final CacheService cacheService;
private final FileHandlerService fileHandlerService;
private final OtherFilePreviewImpl otherFilePreview;
private static final ObjectMapper mapper = new ObjectMapper();
public OnlinePreviewController(FilePreviewFactory filePreviewFactory, FileHandlerService fileHandlerService, CacheService cacheService, OtherFilePreviewImpl otherFilePreview) {
this.previewFactory = filePreviewFactory;
@@ -189,50 +176,9 @@ public class OnlinePreviewController {
InputStream inputStream = null;
logger.info("读取跨域pdf文件url{}", urlPath);
if (!isFtpUrl(url)) {
// 根据配置创建HttpClient
CloseableHttpClient httpClient = createConfiguredHttpClient();
CloseableHttpClient httpClient = HttpRequestUtils.createConfiguredHttpClient();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(factory);
String finalUrlPath = urlPath;
RequestCallback requestCallback = request -> {
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
WebUtils.applyBasicAuthHeaders(request.getHeaders(), finalUrlPath);
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((headerKey, value) -> request.getHeaders().set(headerKey, value));
}
};
try {
restTemplate.execute(url.toURI(), HttpMethod.GET, requestCallback, fileResponse -> {
IOUtils.copy(fileResponse.getBody(), response.getOutputStream());
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忽略功能或检查证书");
}
logger.error("获取跨域文件失败", e);
} finally {
// 确保HttpClient被关闭
try {
httpClient.close();
} catch (IOException e) {
logger.warn("关闭HttpClient失败", e);
}
}
HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> IOUtils.copy(responseWrapper.getInputStream(), response.getOutputStream()));
} else {
try {
String filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
@@ -254,20 +200,6 @@ public class OnlinePreviewController {
}
}
/**
* 创建根据配置定制的HttpClient
*/
private CloseableHttpClient createConfiguredHttpClient() throws Exception {
org.apache.hc.client5.http.impl.classic.HttpClientBuilder builder = HttpClients.custom();
// 配置SSL和重定向
return SslUtils.configureHttpClientBuilder(
builder,
ConfigConstants.isIgnoreSSL(),
ConfigConstants.isEnableRedirect()
).build();
}
/**
* 通过api接口入队
*