mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-04-11 02:37:09 +00:00
284 lines
12 KiB
Java
284 lines
12 KiB
Java
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;
|
||
}
|
||
|
||
} |