Files
file-online-preview/server/src/main/java/cn/keking/utils/DownloadUtils.java
2026-01-22 11:28:25 +08:00

284 lines
12 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 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.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.util.StringUtils;
import org.springframework.web.client.RequestCallback;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.FileNotFoundException;
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 static cn.keking.utils.KkFileUtils.*;
/**
* @author yudian-it
*/
public class DownloadUtils {
private final static Logger logger = LoggerFactory.getLogger(DownloadUtils.class);
private static final String fileDir = ConfigConstants.getFileDir();
private static final String URL_PARAM_FTP_USERNAME = "ftp.username";
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 RestTemplate restTemplate = new RestTemplate();
private static final HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
private static final ObjectMapper mapper = new ObjectMapper();
/**
* @param fileAttribute fileAttribute
* @param fileName 文件名
* @return 本地文件绝对路径
*/
public static ReturnResponse<String> downLoad(FileAttribute fileAttribute, String fileName) {
String urlStr = null;
try {
urlStr = fileAttribute.getUrl();
} catch (Exception e) {
logger.error("处理URL异常:", e);
}
ReturnResponse<String> response = new ReturnResponse<>(0, "下载成功!!!", "");
String realPath = getRelFilePath(fileName, fileAttribute);
// 判断是否非法地址
if (KkFileUtils.isIllegalFileName(realPath)) {
response.setCode(1);
response.setContent(null);
response.setMsg("下载失败:文件名不合法!" + urlStr);
return response;
}
if (!KkFileUtils.isAllowedUpload(realPath)) {
response.setCode(1);
response.setContent(null);
response.setMsg("下载失败:不支持的类型!" + urlStr);
return response;
}
if (fileAttribute.isCompressFile()) { //压缩包文件 直接赋予路径 不予下载
response.setContent(fileDir + fileName);
response.setMsg(fileName);
return response;
}
// 如果文件是否已经存在、且不强制更新,则直接返回文件路径
if (KkFileUtils.isExist(realPath) && !fileAttribute.forceUpdatedCache()) {
response.setContent(realPath);
response.setMsg(fileName);
return response;
}
try {
URL url = WebUtils.normalizedURL(urlStr);
if (!fileAttribute.getSkipDownLoad()) {
if (isHttpUrl(url)) {
File realFile = new File(realPath);
// 创建配置好的HttpClient
CloseableHttpClient httpClient = createConfiguredHttpClient();
factory.setHttpClient(httpClient);
restTemplate.setRequestFactory(factory);
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 {
restTemplate.execute(url.toURI(), HttpMethod.GET, requestCallback, fileResponse -> {
FileUtils.copyToFile(fileResponse.getBody(), realFile);
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);
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);
} else if (isFileUrl(url)) { // 添加对file协议的支持
handleFileProtocol(url, realPath);
} else {
response.setCode(1);
response.setMsg("url不能识别url" + urlStr);
}
}
response.setContent(realPath);
response.setMsg(fileName);
return response;
} catch (IOException | GalimatiasParseException e) {
logger.error("文件下载失败url{}", urlStr);
response.setCode(1);
response.setContent(null);
if (e instanceof FileNotFoundException) {
response.setMsg("文件不存在!!!");
} else {
response.setMsg(e.getMessage());
}
return response;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 创建根据配置定制的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 {
File sourceFile = new File(url.getPath());
if (!sourceFile.exists()) {
throw new FileNotFoundException("本地文件不存在: " + url.getPath());
}
if (!sourceFile.isFile()) {
throw new IOException("路径不是文件: " + url.getPath());
}
File targetFile = new File(targetPath);
// 判断源文件和目标文件是否是同一个文件(防止自身复制覆盖)
if (isSameFile(sourceFile, targetFile)) {
// 如果是同一个文件,直接返回,不执行复制操作
logger.info("源文件和目标文件相同,跳过复制: {}", sourceFile.getAbsolutePath());
return;
}
// 确保目标目录存在
File parentDir = targetFile.getParentFile();
if (parentDir != null && !parentDir.exists()) {
parentDir.mkdirs();
}
// 复制文件
Files.copy(sourceFile.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
/**
* 判断两个文件是否是同一个文件
* 通过比较规范化路径来避免符号链接、相对路径等问题
*/
private static boolean isSameFile(File file1, File file2) {
try {
// 使用规范化路径比较,可以处理符号链接、相对路径等情况
String canonicalPath1 = file1.getCanonicalPath();
String canonicalPath2 = file2.getCanonicalPath();
// 如果是Windows系统忽略路径大小写
if (isWindows()) {
return canonicalPath1.equalsIgnoreCase(canonicalPath2);
}
return canonicalPath1.equals(canonicalPath2);
} catch (IOException e) {
// 如果获取规范化路径失败,使用绝对路径比较
logger.warn("无法获取文件的规范化路径,使用绝对路径比较: {}, {}", file1.getAbsolutePath(), file2.getAbsolutePath());
String absolutePath1 = file1.getAbsolutePath();
String absolutePath2 = file2.getAbsolutePath();
if (isWindows()) {
return absolutePath1.equalsIgnoreCase(absolutePath2);
}
return absolutePath1.equals(absolutePath2);
}
}
/**
* 获取真实文件绝对路径
*
* @param fileName 文件名
* @return 文件路径
*/
private static String getRelFilePath(String fileName, FileAttribute fileAttribute) {
String type = fileAttribute.getSuffix();
if (null == fileName) {
UUID uuid = UUID.randomUUID();
fileName = uuid + "." + type;
} else { // 文件后缀不一致时以type为准(针对simText【将类txt文件转为txt】)
fileName = fileName.replace(fileName.substring(fileName.lastIndexOf(".") + 1), type);
}
String realPath = fileDir + fileName;
File dirFile = new File(fileDir);
if (!dirFile.exists() && !dirFile.mkdirs()) {
logger.error("创建目录【{}】失败,可能是权限不够,请检查", fileDir);
}
return realPath;
}
}