mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-04-15 21:03:42 +00:00
优化多线程转换方法 添加异步转换提示
This commit is contained in:
@@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.type.TypeFactory;
|
||||
import io.mola.galimatias.GalimatiasParseException;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
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;
|
||||
@@ -41,7 +42,7 @@ public class DownloadUtils {
|
||||
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 RestTemplate restTemplate = new RestTemplate();
|
||||
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
||||
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
||||
private static final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
|
||||
@@ -51,12 +52,12 @@ public class DownloadUtils {
|
||||
* @return 本地文件绝对路径
|
||||
*/
|
||||
public static ReturnResponse<String> downLoad(FileAttribute fileAttribute, String fileName) {
|
||||
// 忽略ssl证书
|
||||
|
||||
String urlStr = null;
|
||||
try {
|
||||
urlStr = fileAttribute.getUrl().replaceAll("\\+", "%20").replaceAll(" ", "%20");
|
||||
} catch (Exception e) {
|
||||
logger.error("忽略SSL证书异常:", e);
|
||||
logger.error("处理URL异常:", e);
|
||||
}
|
||||
ReturnResponse<String> response = new ReturnResponse<>(0, "下载成功!!!", "");
|
||||
String realPath = getRelFilePath(fileName, fileAttribute);
|
||||
@@ -90,7 +91,10 @@ public class DownloadUtils {
|
||||
if (!fileAttribute.getSkipDownLoad()) {
|
||||
if (isHttpUrl(url)) {
|
||||
File realFile = new File(realPath);
|
||||
CloseableHttpClient httpClient = SslUtils.createHttpClientIgnoreSsl();
|
||||
|
||||
// 创建配置好的HttpClient
|
||||
CloseableHttpClient httpClient = createConfiguredHttpClient();
|
||||
|
||||
factory.setHttpClient(httpClient);
|
||||
restTemplate.setRequestFactory(factory);
|
||||
RequestCallback requestCallback = request -> {
|
||||
@@ -111,10 +115,25 @@ public class DownloadUtils {
|
||||
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忽略功能或检查证书");
|
||||
}
|
||||
response.setCode(1);
|
||||
response.setContent(null);
|
||||
response.setMsg("下载失败:" + e);
|
||||
return response;
|
||||
} finally {
|
||||
// 确保HttpClient被关闭
|
||||
try {
|
||||
httpClient.close();
|
||||
} catch (IOException e) {
|
||||
logger.warn("关闭HttpClient失败", e);
|
||||
}
|
||||
}
|
||||
} else if (isFtpUrl(url)) {
|
||||
String ftpUsername = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_USERNAME);
|
||||
@@ -147,6 +166,38 @@ public class DownloadUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建根据配置定制的HttpClient
|
||||
*/
|
||||
private static CloseableHttpClient createConfiguredHttpClient() throws Exception {
|
||||
org.apache.hc.client5.http.impl.classic.HttpClientBuilder builder = HttpClients.custom();
|
||||
|
||||
// 配置SSL
|
||||
if (ConfigConstants.isIgnoreSSL()) {
|
||||
logger.debug("创建忽略SSL验证的HttpClient");
|
||||
// 如果SslUtils有创建builder的方法就更好了,这里假设我们直接使用SslUtils
|
||||
// 或者我们可以创建一个新的方法来返回配置了忽略SSL的builder
|
||||
return createHttpClientWithConfig();
|
||||
} else {
|
||||
logger.debug("创建标准HttpClient");
|
||||
}
|
||||
|
||||
// 配置重定向
|
||||
if (!ConfigConstants.isEnableRedirect()) {
|
||||
logger.debug("禁用HttpClient重定向");
|
||||
builder.disableRedirectHandling();
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置了忽略SSL的HttpClient
|
||||
*/
|
||||
private static CloseableHttpClient createHttpClientWithConfig() throws Exception {
|
||||
return SslUtils.createHttpClientIgnoreSsl();
|
||||
}
|
||||
|
||||
|
||||
// 处理file协议的文件下载
|
||||
private static void handleFileProtocol(URL url, String targetPath) throws IOException {
|
||||
@@ -229,4 +280,4 @@ public class DownloadUtils {
|
||||
return realPath;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* 文件转换状态管理器(增强版)
|
||||
* 支持实时状态跟踪和状态锁定机制
|
||||
*/
|
||||
public class FileConvertStatusManager {
|
||||
|
||||
// 存储转换状态:key=文件名,value=转换状态对象
|
||||
private static final ConcurrentMap<String, ConvertState> STATUS_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// 记录最终状态(超时或异常),防止重复转换
|
||||
private static final ConcurrentMap<String, Status> FINAL_STATUS_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 开始转换,创建初始状态
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
public static void startConvert(String fileName) {
|
||||
STATUS_MAP.putIfAbsent(fileName, new ConvertState(Status.CONVERTING, "等待转换", 0));
|
||||
// 清除可能存在的最终状态,因为要开始新的转换
|
||||
FINAL_STATUS_MAP.remove(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新转换进度
|
||||
* @param fileName 文件名
|
||||
* @param message 状态消息
|
||||
* @param progress 进度百分比(0-100)
|
||||
*/
|
||||
public static void updateProgress(String fileName, String message, int progress) {
|
||||
STATUS_MAP.computeIfPresent(fileName, (key, state) -> {
|
||||
state.update(message, progress);
|
||||
logger.debug("更新转换进度: {} -> {} ({}%)", fileName, message, progress);
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记转换超时 - 记录为最终状态
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
public static void markTimeout(String fileName) {
|
||||
STATUS_MAP.put(fileName, new ConvertState(Status.TIMEOUT, "转换超时,请重试", 0));
|
||||
// 记录为最终状态
|
||||
FINAL_STATUS_MAP.put(fileName, Status.TIMEOUT);
|
||||
logger.warn("标记文件转换超时: {}", fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记转换失败 - 记录为最终状态
|
||||
* @param fileName 文件名
|
||||
* @param errorMessage 错误信息
|
||||
*/
|
||||
public static void markError(String fileName, String errorMessage) {
|
||||
STATUS_MAP.put(fileName, new ConvertState(Status.FAILED, errorMessage, 0));
|
||||
// 记录为最终状态
|
||||
FINAL_STATUS_MAP.put(fileName, Status.FAILED);
|
||||
logger.warn("标记文件转换失败: {}, 错误: {}", fileName, errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询文件转换状态
|
||||
* @param fileName 文件名
|
||||
* @return 转换状态对象,如果不存在返回null
|
||||
*/
|
||||
public static ConvertStatus getConvertStatus(String fileName) {
|
||||
// 先检查是否有最终状态
|
||||
Status finalStatus = FINAL_STATUS_MAP.get(fileName);
|
||||
if ((finalStatus == Status.TIMEOUT || finalStatus == Status.FAILED)) {
|
||||
ConvertState state = STATUS_MAP.get(fileName);
|
||||
if (state == null) {
|
||||
// 如果STATUS_MAP中没有,创建一个最终状态
|
||||
if (finalStatus == Status.TIMEOUT) {
|
||||
return new ConvertStatus(Status.TIMEOUT, "转换超时,请重试", 0, 0);
|
||||
} else {
|
||||
return new ConvertStatus(Status.FAILED, "转换失败", 0, 0);
|
||||
}
|
||||
}
|
||||
// 返回最终状态
|
||||
return new ConvertStatus(state.status, state.message, state.progress, 0);
|
||||
}
|
||||
|
||||
ConvertState state = STATUS_MAP.get(fileName);
|
||||
if (state == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 如果是转换中状态,计算已等待时间
|
||||
long waitingSeconds = 0;
|
||||
if (state.status == Status.CONVERTING) {
|
||||
waitingSeconds = (System.currentTimeMillis() - state.startTime) / 1000;
|
||||
}
|
||||
|
||||
return new ConvertStatus(
|
||||
state.status,
|
||||
state.message,
|
||||
state.progress,
|
||||
waitingSeconds
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换成功
|
||||
* @param fileName 文件名
|
||||
*/
|
||||
public static void convertSuccess(String fileName) {
|
||||
STATUS_MAP.remove(fileName);
|
||||
// 清除最终状态,允许重新转换
|
||||
FINAL_STATUS_MAP.remove(fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理状态(强制重置,允许重新转换)
|
||||
* @param fileName 文件名
|
||||
* @return true: 清理成功; false: 清理失败
|
||||
*/
|
||||
public static boolean clearStatus(String fileName) {
|
||||
boolean removed1 = STATUS_MAP.remove(fileName) != null;
|
||||
boolean removed2 = FINAL_STATUS_MAP.remove(fileName) != null;
|
||||
logger.info("清理文件状态: {}, STATUS_MAP: {}, FINAL_STATUS_MAP: {}",
|
||||
fileName, removed1, removed2);
|
||||
return removed1 || removed2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 清理过期状态(长时间未清理的状态)
|
||||
* @param expireHours 过期时间(小时)
|
||||
* @return 清理的数量
|
||||
*/
|
||||
public static int cleanupExpiredStatus(int expireHours) {
|
||||
long expireMillis = expireHours * 3600 * 1000L;
|
||||
long currentTime = System.currentTimeMillis();
|
||||
|
||||
// 清理STATUS_MAP中的过期状态
|
||||
int count1 = (int) STATUS_MAP.entrySet().stream()
|
||||
.filter(entry -> {
|
||||
ConvertState state = entry.getValue();
|
||||
if (state.status == Status.CONVERTING) {
|
||||
return false; // 转换中的不清理
|
||||
}
|
||||
long elapsed = currentTime - state.startTime;
|
||||
return elapsed > expireMillis;
|
||||
})
|
||||
.count();
|
||||
|
||||
// 清理FINAL_STATUS_MAP中的过期状态
|
||||
// 注意:FINAL_STATUS_MAP没有时间戳,无法基于时间清理
|
||||
// 如果需要清理,可以设置一个独立的过期机制
|
||||
|
||||
logger.info("清理了 {} 个过期的转换状态", count1);
|
||||
return count1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换状态枚举
|
||||
*/
|
||||
public enum Status {
|
||||
CONVERTING("转换中"),
|
||||
FAILED("转换失败"),
|
||||
TIMEOUT("转换超时"),
|
||||
QUEUED("排队中");
|
||||
|
||||
private final String description;
|
||||
|
||||
Status(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部状态存储类
|
||||
*/
|
||||
private static class ConvertState {
|
||||
private final Status status;
|
||||
private String message;
|
||||
private int progress; // 0-100
|
||||
private final long startTime;
|
||||
|
||||
public ConvertState(Status status, String message, int progress) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.progress = Math.max(0, Math.min(100, progress));
|
||||
this.startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void update(String message, int progress) {
|
||||
this.message = message;
|
||||
this.progress = Math.max(0, Math.min(100, progress));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外暴露的转换状态封装类
|
||||
*/
|
||||
public static class ConvertStatus {
|
||||
private final Status status;
|
||||
private final String message;
|
||||
private final int progress;
|
||||
private final long waitingSeconds;
|
||||
private final long timestamp;
|
||||
|
||||
public ConvertStatus(Status status, String message, int progress, long waitingSeconds) {
|
||||
this.status = status;
|
||||
this.message = message;
|
||||
this.progress = progress;
|
||||
this.waitingSeconds = waitingSeconds;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
// 获取实时状态信息
|
||||
public String getRealTimeMessage() {
|
||||
if (status == Status.CONVERTING) {
|
||||
if (progress > 0) {
|
||||
return String.format("%s: %s (进度: %d%%,已等待 %d 秒)",
|
||||
status.getDescription(), message, progress, waitingSeconds);
|
||||
} else {
|
||||
return String.format("%s: %s,已等待 %d 秒",
|
||||
status.getDescription(), message, waitingSeconds);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
// Getters
|
||||
public Status getStatus() { return status; }
|
||||
public String getMessage() { return message; }
|
||||
public int getProgress() { return progress; }
|
||||
|
||||
public long getTimestamp() { return timestamp; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConvertStatus{" +
|
||||
"status=" + status +
|
||||
", message='" + message + '\'' +
|
||||
", progress=" + progress +
|
||||
", waitingSeconds=" + waitingSeconds +
|
||||
", timestamp=" + timestamp +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
// 日志记录器
|
||||
private static final org.slf4j.Logger logger =
|
||||
org.slf4j.LoggerFactory.getLogger(FileConvertStatusManager.class);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
import org.apache.hc.client5.http.config.RequestConfig;
|
||||
import org.apache.hc.client5.http.impl.DefaultRedirectStrategy;
|
||||
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;
|
||||
@@ -11,7 +11,8 @@ import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
|
||||
import org.apache.hc.core5.http.io.SocketConfig;
|
||||
import org.apache.hc.core5.util.Timeout;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/**
|
||||
@@ -23,40 +24,58 @@ public class SslUtils {
|
||||
* 创建忽略SSL验证的HttpClient(适用于HttpClient 5.6)
|
||||
*/
|
||||
public static CloseableHttpClient createHttpClientIgnoreSsl() throws Exception {
|
||||
// 创建自定义的SSL上下文
|
||||
SSLContext sslContext = createIgnoreVerifySSL();
|
||||
return configureHttpClientBuilder(HttpClients.custom(), true, true).build();
|
||||
}
|
||||
|
||||
// 使用SSLConnectionSocketFactoryBuilder构建SSL连接工厂
|
||||
DefaultClientTlsStrategy tlsStrategy = new DefaultClientTlsStrategy(
|
||||
sslContext, NoopHostnameVerifier.INSTANCE);
|
||||
/**
|
||||
* 配置HttpClientBuilder,支持SSL和重定向配置
|
||||
* @param builder HttpClientBuilder
|
||||
* @param ignoreSSL 是否忽略SSL验证
|
||||
* @param enableRedirect 是否启用重定向
|
||||
* @return 配置好的HttpClientBuilder
|
||||
*/
|
||||
public static HttpClientBuilder configureHttpClientBuilder(HttpClientBuilder builder,
|
||||
boolean ignoreSSL,
|
||||
boolean enableRedirect) throws Exception {
|
||||
// 配置SSL
|
||||
if (ignoreSSL) {
|
||||
// 创建自定义的SSL上下文
|
||||
SSLContext sslContext = createIgnoreVerifySSL();
|
||||
|
||||
// 使用新的PoolingHttpClientConnectionManagerBuilder构建连接管理器
|
||||
// 使用连接管理器构建器
|
||||
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(tlsStrategy)
|
||||
.setDefaultSocketConfig(SocketConfig.custom()
|
||||
.setSoTimeout(Timeout.ofSeconds(10))
|
||||
.build())
|
||||
.build();
|
||||
// 使用SSLConnectionSocketFactoryBuilder构建SSL连接工厂
|
||||
DefaultClientTlsStrategy tlsStrategy = new DefaultClientTlsStrategy(
|
||||
sslContext, NoopHostnameVerifier.INSTANCE);
|
||||
|
||||
// 配置连接池参数
|
||||
connectionManager.setMaxTotal(200);
|
||||
connectionManager.setDefaultMaxPerRoute(20);
|
||||
// 使用连接管理器构建器
|
||||
PoolingHttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create()
|
||||
.setTlsSocketStrategy(tlsStrategy)
|
||||
.setDefaultSocketConfig(SocketConfig.custom()
|
||||
.setSoTimeout(Timeout.ofSeconds(10))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// 配置连接池参数
|
||||
connectionManager.setMaxTotal(200);
|
||||
connectionManager.setDefaultMaxPerRoute(20);
|
||||
|
||||
builder.setConnectionManager(connectionManager);
|
||||
}
|
||||
|
||||
// 配置请求参数
|
||||
RequestConfig requestConfig = RequestConfig.custom()
|
||||
.setConnectionRequestTimeout(Timeout.ofSeconds(10))
|
||||
.setResponseTimeout(Timeout.ofSeconds(72))
|
||||
.setConnectionRequestTimeout(Timeout.ofSeconds(2))
|
||||
.setRedirectsEnabled(true)
|
||||
.setConnectTimeout(Timeout.ofSeconds(2))
|
||||
.setRedirectsEnabled(enableRedirect)
|
||||
.setMaxRedirects(5)
|
||||
.build();
|
||||
builder.setDefaultRequestConfig(requestConfig);
|
||||
|
||||
return HttpClients.custom()
|
||||
.setConnectionManager(connectionManager)
|
||||
.setDefaultRequestConfig(requestConfig)
|
||||
.setRedirectStrategy(DefaultRedirectStrategy.INSTANCE)
|
||||
.build();
|
||||
if (!enableRedirect) {
|
||||
builder.disableRedirectHandling();
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +103,7 @@ public class SslUtils {
|
||||
}
|
||||
};
|
||||
|
||||
sc.init(null, new TrustManager[]{trustManager}, new java.security.SecureRandom());
|
||||
sc.init(null, new javax.net.ssl.TrustManager[]{trustManager}, new java.security.SecureRandom());
|
||||
return sc;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user