diff --git a/server/src/main/java/cn/keking/service/impl/PdfFilePreviewImpl.java b/server/src/main/java/cn/keking/service/impl/PdfFilePreviewImpl.java index d410c83f..fc2b13de 100644 --- a/server/src/main/java/cn/keking/service/impl/PdfFilePreviewImpl.java +++ b/server/src/main/java/cn/keking/service/impl/PdfFilePreviewImpl.java @@ -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) { diff --git a/server/src/main/java/cn/keking/utils/DownloadUtils.java b/server/src/main/java/cn/keking/utils/DownloadUtils.java index 020d9b19..42ea2f18 100644 --- a/server/src/main/java/cn/keking/utils/DownloadUtils.java +++ b/server/src/main/java/cn/keking/utils/DownloadUtils.java @@ -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 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 { - // 获取响应头中的Content-Type - String contentType = WebUtils.headersType(fileResponse); + HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> { + // 获取响应头中的Content-Type + String contentType = responseWrapper.getContentType(); - // 如果是Office/设计文件,需要校验MIME类型 - if ( WebUtils.isMimeCheckRequired(fileSuffix)) { - if (! WebUtils.isValidMimeType(contentType, fileSuffix)) { - logger.error("文件类型错误,期望二进制文件但接收到文本类型,url: {}, Content-Type: {}", - finalUrlStr1, contentType); - hasError[0] = true; - // 重要:关闭响应流,不读取后续数据 - fileResponse.close(); - return null; - } - } - - // 保存文件 - FileUtils.copyToFile(fileResponse.getBody(), realFile); - } catch (Exception e) { - logger.error("处理文件响应时出错", e); - hasError[0] = true; + // 如果是Office/设计文件,需要校验MIME类型 + if (WebUtils.isMimeCheckRequired(fileSuffix)) { + if (!WebUtils.isValidMimeType(contentType, fileSuffix)) { + logger.error("文件类型错误,期望二进制文件但接收到文本类型,url: {}, Content-Type: {}", + finalUrlStr, contentType); + responseWrapper.setHasError(true); + return; } - return null; - }); + } - // 如果下载过程中出现错误 - 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,因为复用 + // 保存文件 + FileUtils.copyToFile(responseWrapper.getInputStream(), realFile); + }); } 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()); diff --git a/server/src/main/java/cn/keking/utils/HttpRequestUtils.java b/server/src/main/java/cn/keking/utils/HttpRequestUtils.java new file mode 100644 index 00000000..67d91a41 --- /dev/null +++ b/server/src/main/java/cn/keking/utils/HttpRequestUtils.java @@ -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 httpClientCache = new ConcurrentHashMap<>(); + + // 用于缓存不同配置的RestTemplate实例 + private static final Map 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 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; + } + } +} \ No newline at end of file diff --git a/server/src/main/java/cn/keking/utils/SslUtils.java b/server/src/main/java/cn/keking/utils/SslUtils.java index 824eadc4..67e80e9d 100644 --- a/server/src/main/java/cn/keking/utils/SslUtils.java +++ b/server/src/main/java/cn/keking/utils/SslUtils.java @@ -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"); diff --git a/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java b/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java index 3ba13107..bd3d8e45 100644 --- a/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java +++ b/server/src/main/java/cn/keking/web/controller/OnlinePreviewController.java @@ -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 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接口入队 *