mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-05-13 18:27:04 +00:00
优化:区分 HTTP 错误,返回明确原因 (#752)
* 优化:区分 HTTP 错误,返回明确原因 * 修复 缓存关闭共享的 client,会导致后续请求失败 * 修复 缓存关闭共享的 client,会导致后续请求失败 --------- Co-authored-by: 高雄 <admin@cxcp.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import org.apache.commons.io.FileUtils;
|
|||||||
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
@@ -46,9 +47,8 @@ public class DownloadUtils {
|
|||||||
}
|
}
|
||||||
ReturnResponse<String> response = new ReturnResponse<>(0, "下载成功!!!", "");
|
ReturnResponse<String> response = new ReturnResponse<>(0, "下载成功!!!", "");
|
||||||
String realPath = getRelFilePath(fileName, fileAttribute);
|
String realPath = getRelFilePath(fileName, fileAttribute);
|
||||||
// 获取文件后缀用于校验
|
|
||||||
final String fileSuffix = fileAttribute.getSuffix();
|
final String fileSuffix = fileAttribute.getSuffix();
|
||||||
// 判断是否非法地址
|
|
||||||
if (KkFileUtils.isIllegalFileName(realPath)) {
|
if (KkFileUtils.isIllegalFileName(realPath)) {
|
||||||
response.setCode(1);
|
response.setCode(1);
|
||||||
response.setContent(null);
|
response.setContent(null);
|
||||||
@@ -61,17 +61,17 @@ public class DownloadUtils {
|
|||||||
response.setMsg("下载失败:不支持的类型!" + urlStr);
|
response.setMsg("下载失败:不支持的类型!" + urlStr);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
if (fileAttribute.isCompressFile()) { //压缩包文件 直接赋予路径 不予下载
|
if (fileAttribute.isCompressFile()) {
|
||||||
response.setContent(fileDir + fileName);
|
response.setContent(fileDir + fileName);
|
||||||
response.setMsg(fileName);
|
response.setMsg(fileName);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
// 如果文件是否已经存在、且不强制更新,则直接返回文件路径
|
|
||||||
if (KkFileUtils.isExist(realPath) && !fileAttribute.forceUpdatedCache()) {
|
if (KkFileUtils.isExist(realPath) && !fileAttribute.forceUpdatedCache()) {
|
||||||
response.setContent(realPath);
|
response.setContent(realPath);
|
||||||
response.setMsg(fileName);
|
response.setMsg(fileName);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
URL url = WebUtils.normalizedURL(urlStr);
|
URL url = WebUtils.normalizedURL(urlStr);
|
||||||
if (!fileAttribute.getSkipDownLoad()) {
|
if (!fileAttribute.getSkipDownLoad()) {
|
||||||
@@ -79,39 +79,59 @@ public class DownloadUtils {
|
|||||||
File realFile = new File(realPath);
|
File realFile = new File(realPath);
|
||||||
CloseableHttpClient httpClient = HttpRequestUtils.createConfiguredHttpClient();
|
CloseableHttpClient httpClient = HttpRequestUtils.createConfiguredHttpClient();
|
||||||
String finalUrlStr = urlStr;
|
String finalUrlStr = urlStr;
|
||||||
HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> {
|
|
||||||
// 获取响应头中的Content-Type
|
|
||||||
String contentType = responseWrapper.getContentType();
|
|
||||||
|
|
||||||
// 如果是Office/设计文件,需要校验MIME类型
|
final boolean[] hasMimeError = {false};
|
||||||
|
final String[] mimeErrorMessage = {null};
|
||||||
|
|
||||||
|
HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> {
|
||||||
|
String contentType = responseWrapper.getContentType();
|
||||||
if (WebUtils.isMimeCheckRequired(fileSuffix)) {
|
if (WebUtils.isMimeCheckRequired(fileSuffix)) {
|
||||||
if (!WebUtils.isValidMimeType(contentType, fileSuffix)) {
|
if (!WebUtils.isValidMimeType(contentType, fileSuffix)) {
|
||||||
logger.error("文件类型错误,期望二进制文件但接收到文本类型,url: {}, Content-Type: {}",
|
logger.error("文件类型错误,期望二进制文件但接收到文本类型,url: {}, Content-Type: {}",
|
||||||
finalUrlStr, contentType);
|
finalUrlStr, contentType);
|
||||||
responseWrapper.setHasError(true);
|
hasMimeError[0] = true;
|
||||||
|
mimeErrorMessage[0] = "期望二进制文件但接收到文本类型,Content-Type: " + contentType;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存文件
|
|
||||||
FileUtils.copyToFile(responseWrapper.getInputStream(), realFile);
|
FileUtils.copyToFile(responseWrapper.getInputStream(), realFile);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hasMimeError[0]) {
|
||||||
|
response.setCode(1);
|
||||||
|
response.setContent(null);
|
||||||
|
response.setMsg(mimeErrorMessage[0]);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
} else if (isFtpUrl(url)) {
|
} else if (isFtpUrl(url)) {
|
||||||
String ftpUsername = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_USERNAME);
|
String ftpUsername = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_USERNAME);
|
||||||
String ftpPassword = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_PASSWORD);
|
String ftpPassword = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_PASSWORD);
|
||||||
String ftpControlEncoding = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_CONTROL_ENCODING);
|
String ftpControlEncoding = WebUtils.getUrlParameterReg(fileAttribute.getUrl(), URL_PARAM_FTP_CONTROL_ENCODING);
|
||||||
String ftpport = WebUtils.getUrlParameterReg(realPath, URL_PARAM_FTP_PORT);
|
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协议的支持
|
} else if (isFileUrl(url)) {
|
||||||
handleFileProtocol(url, realPath);
|
handleFileProtocol(url, realPath);
|
||||||
} else {
|
} else {
|
||||||
response.setCode(1);
|
response.setCode(1);
|
||||||
response.setMsg("url不能识别url" + urlStr);
|
response.setMsg("url不能识别url" + urlStr);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.setContent(realPath);
|
response.setContent(realPath);
|
||||||
response.setMsg(fileName);
|
response.setMsg(fileName);
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
|
} catch (HttpClientErrorException e) {
|
||||||
|
logger.error("HTTP请求失败,状态码:{},url:{}", e.getStatusCode(), urlStr);
|
||||||
|
response.setCode(1);
|
||||||
|
response.setContent(null);
|
||||||
|
if (e.getStatusCode().is4xxClientError()) {
|
||||||
|
response.setMsg("文件不存在或无法访问 (HTTP " + e.getStatusCode() + ")");
|
||||||
|
} else {
|
||||||
|
response.setMsg("下载失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return response;
|
||||||
} catch (IOException | GalimatiasParseException e) {
|
} catch (IOException | GalimatiasParseException e) {
|
||||||
logger.error("文件下载失败,url:{}", urlStr);
|
logger.error("文件下载失败,url:{}", urlStr);
|
||||||
response.setCode(1);
|
response.setCode(1);
|
||||||
@@ -123,7 +143,11 @@ public class DownloadUtils {
|
|||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
logger.error("下载文件时发生未知异常,url:{}", urlStr, e);
|
||||||
|
response.setCode(1);
|
||||||
|
response.setContent(null);
|
||||||
|
response.setMsg("下载失败: " + e.getMessage());
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package cn.keking.utils;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import jakarta.annotation.PreDestroy;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class HttpClientLifecycle {
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() {
|
||||||
|
System.out.println("Spring 容器关闭,释放 HTTP 连接池资源...");
|
||||||
|
HttpRequestUtils.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,8 @@ import org.springframework.web.bind.annotation.ResponseBody;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.web.client.HttpClientErrorException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@@ -152,34 +154,71 @@ public class OnlinePreviewController {
|
|||||||
// 1. 验证接口是否开启
|
// 1. 验证接口是否开启
|
||||||
if (!ConfigConstants.getGetCorsFile()) {
|
if (!ConfigConstants.getGetCorsFile()) {
|
||||||
logger.info("接口关闭,禁止访问!,url:{}", urlPath);
|
logger.info("接口关闭,禁止访问!,url:{}", urlPath);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "接口已关闭");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
//2. 验证访问权限
|
// 2. 验证访问权限
|
||||||
if (WebUtils.validateKey(key)) {
|
if (WebUtils.validateKey(key)) {
|
||||||
logger.info("访问不合法:访问密码不正确!,url:{}", urlPath);
|
logger.info("访问不合法:访问密码不正确!,url:{}", urlPath);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "访问密码不正确");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
URL url;
|
URL url;
|
||||||
try {
|
try {
|
||||||
urlPath = WebUtils.decodeUrl(urlPath, encryption);
|
urlPath = WebUtils.decodeUrl(urlPath, encryption);
|
||||||
url = WebUtils.normalizedURL(urlPath);
|
url = WebUtils.normalizedURL(urlPath);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.error(String.format(BASE64_DECODE_ERROR_MSG, urlPath),ex);
|
logger.error(String.format(BASE64_DECODE_ERROR_MSG, urlPath), ex);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "URL 解析失败");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert urlPath != null;
|
assert urlPath != null;
|
||||||
if (!isHttpUrl(url) && !isFtpUrl(url)) {
|
if (!isHttpUrl(url) && !isFtpUrl(url)) {
|
||||||
logger.info("读取跨域文件异常,可能存在非法访问,urlPath:{}", urlPath);
|
logger.info("读取跨域文件异常,可能存在非法访问,urlPath:{}", urlPath);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_FORBIDDEN, "不支持的协议");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(urlPath, req);
|
|
||||||
InputStream inputStream = null;
|
|
||||||
logger.info("读取跨域文件url:{}", urlPath);
|
|
||||||
if (!isFtpUrl(url)) {
|
|
||||||
CloseableHttpClient httpClient = HttpRequestUtils.createConfiguredHttpClient();
|
|
||||||
|
|
||||||
|
FileAttribute fileAttribute = fileHandlerService.getFileAttribute(urlPath, req);
|
||||||
|
logger.info("读取跨域文件url:{}", urlPath);
|
||||||
|
|
||||||
|
if (!isFtpUrl(url)) {
|
||||||
|
// HTTP/HTTPS 处理(修复:不关闭共享的 CloseableHttpClient)
|
||||||
|
CloseableHttpClient httpClient = HttpRequestUtils.createConfiguredHttpClient();
|
||||||
|
try {
|
||||||
HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> IOUtils.copy(responseWrapper.getInputStream(), response.getOutputStream()));
|
HttpRequestUtils.executeHttpRequest(url, httpClient, fileAttribute, responseWrapper -> IOUtils.copy(responseWrapper.getInputStream(), response.getOutputStream()));
|
||||||
|
} catch (HttpClientErrorException e) {
|
||||||
|
// 捕获 HTTP 4xx 错误(如 404)
|
||||||
|
logger.error("HTTP 请求失败,状态码:{},url:{}", e.getStatusCode(), urlPath);
|
||||||
|
try {
|
||||||
|
if (e.getStatusCode().is4xxClientError()) {
|
||||||
|
response.sendError(e.getStatusCode().value(), "文件不存在或无法访问");
|
||||||
} else {
|
} else {
|
||||||
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "下载文件时发生错误");
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 捕获其他异常(如连接超时、IO 异常等)
|
||||||
|
logger.error("读取跨域文件异常,url:{}", urlPath, e);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "读取文件失败: " + e.getMessage());
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// FTP 处理
|
||||||
|
InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
String filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
String filename = urlPath.substring(urlPath.lastIndexOf('/') + 1);
|
||||||
String contentType = WebUtils.getContentTypeByFilename(filename);
|
String contentType = WebUtils.getContentTypeByFilename(filename);
|
||||||
@@ -190,10 +229,23 @@ public class OnlinePreviewController {
|
|||||||
String ftpPassword = WebUtils.getUrlParameterReg(urlPath, URL_PARAM_FTP_PASSWORD);
|
String ftpPassword = WebUtils.getUrlParameterReg(urlPath, URL_PARAM_FTP_PASSWORD);
|
||||||
String ftpControlEncoding = WebUtils.getUrlParameterReg(urlPath, URL_PARAM_FTP_CONTROL_ENCODING);
|
String ftpControlEncoding = WebUtils.getUrlParameterReg(urlPath, URL_PARAM_FTP_CONTROL_ENCODING);
|
||||||
String support = WebUtils.getUrlParameterReg(urlPath, URL_PARAM_FTP_PORT);
|
String support = WebUtils.getUrlParameterReg(urlPath, URL_PARAM_FTP_PORT);
|
||||||
inputStream= FtpUtils.preview(urlPath,support, urlPath, ftpUsername, ftpPassword, ftpControlEncoding);
|
inputStream = FtpUtils.preview(urlPath, support, urlPath, ftpUsername, ftpPassword, ftpControlEncoding);
|
||||||
IOUtils.copy(inputStream, response.getOutputStream());
|
IOUtils.copy(inputStream, response.getOutputStream());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.error("读取跨域文件异常,url:{}", urlPath);
|
logger.error("读取跨域文件异常,url:{}", urlPath, e);
|
||||||
|
try {
|
||||||
|
// 根据异常信息判断是否为文件不存在
|
||||||
|
if (e.getMessage() != null && (e.getMessage().contains("550") || e.getMessage().contains("File not found"))) {
|
||||||
|
response.sendError(HttpServletResponse.SC_NOT_FOUND, "FTP 文件不存在");
|
||||||
|
} else {
|
||||||
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "FTP 读取失败");
|
||||||
|
}
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("FTP 预览发生未知异常,url:{}", urlPath, e);
|
||||||
|
try {
|
||||||
|
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "FTP 服务异常");
|
||||||
|
} catch (IOException ignored) {}
|
||||||
} finally {
|
} finally {
|
||||||
IOUtils.closeQuietly(inputStream);
|
IOUtils.closeQuietly(inputStream);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user