mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-04-18 06:13:41 +00:00
新增 页码定位 美化前端 其他功能调整等
This commit is contained in:
@@ -55,7 +55,6 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
// 预览Type,参数传了就取参数的,没传取系统默认
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType() == null ?
|
||||
ConfigConstants.getOfficePreviewType() : fileAttribute.getOfficePreviewType();
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
String fileName = fileAttribute.getName();
|
||||
String cadPreviewType = ConfigConstants.getCadPreviewType();
|
||||
@@ -63,20 +62,9 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
|
||||
// 查询转换状态
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(fileName);
|
||||
if (status != null) {
|
||||
if (status.getStatus() == FileConvertStatusManager.Status.CONVERTING) {
|
||||
// 正在转换中,返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", status.getRealTimeMessage());
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.TIMEOUT) {
|
||||
// 超时状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换已超时,无法继续转换");
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.FAILED) {
|
||||
// 失败状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换失败,无法继续转换");
|
||||
}
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, fileName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
|
||||
// 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
|
||||
@@ -117,6 +105,7 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
CompletableFuture<Boolean> conversionFuture = cadtopdfservice.cadToPdfAsync(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cacheName,
|
||||
ConfigConstants.getCadPreviewType(),
|
||||
fileAttribute
|
||||
);
|
||||
@@ -166,11 +155,8 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
|
||||
if (baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return officefilepreviewimpl.getPreviewType(model, fileAttribute, officePreviewType,
|
||||
cacheName, outFilePath, fileHandlerService,
|
||||
OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
return officefilepreviewimpl.getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath);
|
||||
}
|
||||
|
||||
model.addAttribute("pdfUrl", cacheName);
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ public class CodeFilePreviewImpl implements FilePreview {
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
filePreviewHandle.filePreviewHandle(url, model, fileAttribute);
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
if(suffix.equalsIgnoreCase("htm") || suffix.equalsIgnoreCase("html") || suffix.equalsIgnoreCase("shtml") ){
|
||||
model.addAttribute("pdfUrl", url);
|
||||
return TXT_FILE_PREVIEW_PAGE; //直接输出html
|
||||
}
|
||||
return CODE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,23 +8,23 @@ import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.Mediatomp4Service;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author : kl
|
||||
* @authorboke : kailing.pub
|
||||
* @create : 2018-03-25 上午11:58
|
||||
* @description:
|
||||
* @description: 异步视频文件预览实现
|
||||
**/
|
||||
@Service
|
||||
public class MediaFilePreviewImpl implements FilePreview {
|
||||
@@ -32,10 +32,20 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MediaFilePreviewImpl.class);
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final Mediatomp4Service mediatomp4Service;
|
||||
private final OfficeFilePreviewImpl officefilepreviewimpl;
|
||||
|
||||
public MediaFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
public MediaFilePreviewImpl(FileHandlerService fileHandlerService,
|
||||
OtherFilePreviewImpl otherFilePreview,
|
||||
Mediatomp4Service mediatomp4Service,
|
||||
OfficeFilePreviewImpl officefilepreviewimpl) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.mediatomp4Service = mediatomp4Service;
|
||||
this.officefilepreviewimpl = officefilepreviewimpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,69 +56,61 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
FileType type = fileAttribute.getType();
|
||||
String[] mediaTypesConvert = FileType.MEDIA_CONVERT_TYPES; //获取支持的转换格式
|
||||
|
||||
// 检查是否是需要转换的视频格式
|
||||
boolean mediaTypes = false;
|
||||
String[] mediaTypesConvert = FileType.MEDIA_CONVERT_TYPES;
|
||||
for (String temp : mediaTypesConvert) {
|
||||
if (suffix.equalsIgnoreCase(temp)) {
|
||||
mediaTypes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 查询转换状态
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, fileName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
// 非HTTP协议或需要转换的文件
|
||||
if (!url.toLowerCase().startsWith("http") || checkNeedConvert(mediaTypes)) {
|
||||
// 检查缓存
|
||||
File outputFile = new File(outFilePath);
|
||||
if (outputFile.exists() && !forceUpdatedCache && ConfigConstants.isCacheEnabled()) {
|
||||
String relativePath = fileHandlerService.getRelativePath(outFilePath);
|
||||
if (fileHandlerService.listConvertedFiles().containsKey(cacheName)) {
|
||||
if (fileHandlerService.listConvertedMedias().containsKey(cacheName)) {
|
||||
model.addAttribute("mediaUrl", relativePath);
|
||||
logger.info("使用已缓存的视频文件: {}", cacheName);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
|
||||
String filePath = response.getContent();
|
||||
|
||||
try {
|
||||
if (mediaTypes) {
|
||||
// 检查文件大小限制
|
||||
if (isFileSizeExceeded(filePath)) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频文件大小超过" + ConfigConstants.getMediaConvertMaxSize() + "MB限制,禁止转换");
|
||||
}
|
||||
|
||||
// 使用改进的转换方法
|
||||
String convertedPath = convertVideoWithImprovedTimeout(filePath, outFilePath, fileAttribute);
|
||||
if (convertedPath != null) {
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(convertedPath));
|
||||
|
||||
// 缓存转换结果
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(convertedPath));
|
||||
}
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
} else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "视频转换失败,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 不需要转换的文件
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(outFilePath));
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
if (mediaTypes) {
|
||||
// 检查文件大小限制
|
||||
if (isFileSizeExceeded(filePath)) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频文件大小超过" + ConfigConstants.getMediaConvertMaxSize() + "MB限制,禁止转换");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理媒体文件失败: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频处理异常: " + getErrorMessage(e));
|
||||
try {
|
||||
// 启动异步转换,并添加回调处理
|
||||
startAsyncConversion(filePath, outFilePath, cacheName, fileAttribute);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "视频文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start video conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "视频转换异常,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 不需要转换的文件,直接返回
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(outFilePath));
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// HTTP协议的媒体文件,直接播放
|
||||
if (type.equals(FileType.MEDIA)) {
|
||||
model.addAttribute("mediaUrl", url);
|
||||
@@ -118,6 +120,45 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "系统还不支持该格式文件的在线预览");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步转换,并在转换完成后处理后续操作
|
||||
*/
|
||||
private void startAsyncConversion(String filePath, String outFilePath,
|
||||
String cacheName, FileAttribute fileAttribute) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<Boolean> conversionFuture = mediatomp4Service.convertToMp4Async(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cacheName,
|
||||
fileAttribute
|
||||
);
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.whenCompleteAsync((success, throwable) -> {
|
||||
if (success != null && success) {
|
||||
try {
|
||||
// 1. 是否保留源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
// 2. 加入视频缓存(只在转换成功后才添加)
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedMedias(cacheName,
|
||||
fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("视频转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
} else {
|
||||
// 转换失败,保留源文件供排查问题
|
||||
logger.error("视频转换失败,保留源文件: {}", filePath);
|
||||
if (throwable != null) {
|
||||
logger.error("视频转换失败原因: ", throwable);
|
||||
}
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件大小是否超过限制
|
||||
*/
|
||||
@@ -139,99 +180,14 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 改进的转换方法
|
||||
*/
|
||||
private String convertVideoWithImprovedTimeout(String filePath, String outFilePath,
|
||||
FileAttribute fileAttribute) {
|
||||
try {
|
||||
// 检查文件是否存在
|
||||
File outputFile = new File(outFilePath);
|
||||
if (outputFile.exists() && !fileAttribute.forceUpdatedCache()) {
|
||||
logger.info("输出文件已存在且非强制更新模式,跳过转换!");
|
||||
return outFilePath;
|
||||
}
|
||||
|
||||
// 使用改进的异步转换方法
|
||||
CompletableFuture<Boolean> future =
|
||||
Mediatomp4Service.convertToMp4Async(filePath, outFilePath, fileAttribute);
|
||||
|
||||
// 计算超时时间
|
||||
File inputFile = new File(filePath);
|
||||
long fileSizeMB = inputFile.length() / (1024 * 1024);
|
||||
int timeoutSeconds = calculateTimeout(fileSizeMB);
|
||||
|
||||
try {
|
||||
boolean result = future.get(timeoutSeconds, TimeUnit.SECONDS);
|
||||
if (result) {
|
||||
// 验证输出文件
|
||||
File convertedFile = new File(outFilePath);
|
||||
if (!convertedFile.exists() || convertedFile.length() == 0) {
|
||||
throw new IOException("转换完成但输出文件无效");
|
||||
}
|
||||
return outFilePath;
|
||||
} else {
|
||||
throw new Exception("转换返回失败状态");
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
// 超时后尝试获取任务ID并取消
|
||||
logger.error("视频转换超时: {}, 文件大小: {}MB, 超时: {}秒",
|
||||
filePath, fileSizeMB, timeoutSeconds);
|
||||
|
||||
throw new RuntimeException("视频转换超时,文件可能过大");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("视频转换异常: {}", filePath, e);
|
||||
throw new RuntimeException("视频转换失败: " + getErrorMessage(e), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算超时时间 - 从配置文件读取
|
||||
*/
|
||||
public int calculateTimeout(long fileSizeMB) {
|
||||
// 如果超时功能被禁用,返回一个非常大的值
|
||||
if (!ConfigConstants.isMediaTimeoutEnabled()) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
|
||||
// 根据文件大小从配置文件读取超时时间
|
||||
if (fileSizeMB < 10) return ConfigConstants.getMediaSmallFileTimeout(); // 小文件
|
||||
if (fileSizeMB < 50) return ConfigConstants.getMediaMediumFileTimeout(); // 中等文件
|
||||
if (fileSizeMB < 200) return ConfigConstants.getMediaLargeFileTimeout(); // 较大文件
|
||||
if (fileSizeMB < 500) return ConfigConstants.getMediaXLFileTimeout(); // 大文件
|
||||
if (fileSizeMB < 1024) return ConfigConstants.getMediaXXLFileTimeout(); // 超大文件
|
||||
return ConfigConstants.getMediaXXXLFileTimeout(); // 极大文件
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要转换
|
||||
*/
|
||||
private boolean checkNeedConvert(boolean mediaTypes) {
|
||||
// 1.检查开关是否开启
|
||||
// 检查转换开关是否开启
|
||||
if ("true".equals(ConfigConstants.getMediaConvertDisable())) {
|
||||
return mediaTypes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取友好的错误信息
|
||||
*/
|
||||
private String getErrorMessage(Exception e) {
|
||||
if (e instanceof CancellationException) {
|
||||
return "转换被取消";
|
||||
} else if (e instanceof TimeoutException) {
|
||||
return "转换超时";
|
||||
} else if (e.getMessage() != null) {
|
||||
// 截取主要错误信息
|
||||
String msg = e.getMessage();
|
||||
if (msg.length() > 100) {
|
||||
msg = msg.substring(0, 100) + "...";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
return "未知错误";
|
||||
}
|
||||
}
|
||||
@@ -8,19 +8,23 @@ import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.OfficeToPdfService;
|
||||
import cn.keking.service.PdfToJpgService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.OfficeUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.jodconverter.core.office.OfficeException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Created by kl on 2018/1/17.
|
||||
@@ -29,9 +33,12 @@ import java.util.List;
|
||||
@Service
|
||||
public class OfficeFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(OfficeFilePreviewImpl.class);
|
||||
public static final String OFFICE_PREVIEW_TYPE_IMAGE = "image";
|
||||
public static final String OFFICE_PREVIEW_TYPE_ALL_IMAGES = "allImages";
|
||||
private static final String OFFICE_PASSWORD_MSG = "password";
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OfficeToPdfService officeToPdfService;
|
||||
@@ -54,12 +61,16 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
String suffix = fileAttribute.getSuffix(); //获取文件后缀
|
||||
String fileName = fileAttribute.getName(); //获取文件原始名称
|
||||
String filePassword = fileAttribute.getFilePassword(); //获取密码
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean isHtmlView = fileAttribute.isHtmlView(); //xlsx 转换成html
|
||||
String cacheName = fileAttribute.getCacheName(); //转换后的文件名
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //转换后生成文件的路径
|
||||
|
||||
// 查询转换状态
|
||||
checkAndHandleConvertStatus(model, fileName, cacheName,fileAttribute);
|
||||
|
||||
if (!officePreviewType.equalsIgnoreCase("html")) {
|
||||
if (ConfigConstants.getOfficeTypeWeb() .equalsIgnoreCase("web")) {
|
||||
if (ConfigConstants.getOfficeTypeWeb().equalsIgnoreCase("web")) {
|
||||
if (suffix.equalsIgnoreCase("xlsx")) {
|
||||
model.addAttribute("pdfUrl", KkFileUtils.htmlEscape(url)); //特殊符号处理
|
||||
return XLSX_FILE_PREVIEW_PAGE;
|
||||
@@ -70,14 +81,190 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (forceUpdatedCache|| !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
// 下载远程文件到本地,如果文件在本地已存在不会重复下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
|
||||
// 图片预览模式(异步转换)
|
||||
if (!isHtmlView && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
boolean jiami = false;
|
||||
if (!ObjectUtils.isEmpty(filePassword)) {
|
||||
jiami = pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath);
|
||||
}
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
if (jiami) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath);
|
||||
}
|
||||
// 下载文件
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
|
||||
// 检查是否加密文件
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
if (isPwdProtectedOffice && !StringUtils.hasLength(filePassword)) {
|
||||
// 加密文件需要密码
|
||||
model.addAttribute("needFilePassword", true);
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("cacheName", cacheName);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncOfficeConversion(filePath, outFilePath, cacheName, fileAttribute, officePreviewType);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start Office conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换异常,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通Office转PDF预览
|
||||
return handleRegularOfficePreview(model, fileAttribute, fileName, forceUpdatedCache, cacheName, outFilePath,
|
||||
isHtmlView, userToken, filePassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步Office转换
|
||||
*/
|
||||
private void startAsyncOfficeConversion(String filePath, String outFilePath, String cacheName,
|
||||
FileAttribute fileAttribute,
|
||||
String officePreviewType) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<List<String>> conversionFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 更新状态
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动Office转换", 20);
|
||||
|
||||
// 转换Office到PDF
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换Office到jpg", 60);
|
||||
officeToPdfService.openOfficeToPDF(filePath, outFilePath, fileAttribute);
|
||||
|
||||
|
||||
if (fileAttribute.isHtmlView()) {
|
||||
// 对转换后的文件进行操作(改变编码方式)
|
||||
FileConvertStatusManager.updateProgress(cacheName, "处理HTML编码", 95);
|
||||
fileHandlerService.doActionConvertedFile(outFilePath);
|
||||
}
|
||||
|
||||
// 是否需要转换为图片
|
||||
List<String> imageUrls = null;
|
||||
if (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在转换PDF为图片", 90);
|
||||
imageUrls = pdftojpgservice.pdf2jpg(outFilePath, outFilePath, cacheName, fileAttribute);
|
||||
}
|
||||
|
||||
// 缓存处理
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
boolean userToken = fileAttribute.getUsePasswordCache();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
|
||||
if (ConfigConstants.isCacheEnabled() && (ObjectUtils.isEmpty(filePassword) || userToken || !isPwdProtectedOffice)) {
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
|
||||
FileConvertStatusManager.updateProgress(cacheName, "转换完成", 100);
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
|
||||
return imageUrls;
|
||||
} catch (OfficeException e) {
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath);
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
if (isPwdProtectedOffice && !OfficeUtils.isCompatible(filePath, filePassword)) {
|
||||
FileConvertStatusManager.markError(cacheName, "文件密码错误,请重新输入");
|
||||
} else {
|
||||
logger.error("Office转换执行失败: {}", cacheName, e);
|
||||
FileConvertStatusManager.markError(cacheName, "Office转换失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
logger.error("Office转换执行失败: {}", cacheName, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.thenAcceptAsync(imageUrls -> {
|
||||
try {
|
||||
// 这里假设imageUrls不为null且不为空表示转换成功
|
||||
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||
// 是否保留源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Office转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取预览类型(图片预览)
|
||||
*/
|
||||
String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType,
|
||||
String cacheName, String outFilePath) {
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
boolean isPPT = suffix.equalsIgnoreCase("ppt") || suffix.equalsIgnoreCase("pptx");
|
||||
List<String> imageUrls;
|
||||
|
||||
try {
|
||||
if (pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath)) {
|
||||
imageUrls = pdftojpgservice.getEncryptedPdfCache(outFilePath);
|
||||
} else {
|
||||
imageUrls = fileHandlerService.loadPdf2jpgCache(outFilePath);
|
||||
}
|
||||
|
||||
if (imageUrls == null || imageUrls.isEmpty()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "Office转换缓存异常,请联系管理员");
|
||||
}
|
||||
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.getFirst());
|
||||
|
||||
if (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType)) {
|
||||
// PPT 图片模式使用专用预览页面
|
||||
return (isPPT ? PPT_FILE_PREVIEW_PAGE : OFFICE_PICTURE_FILE_PREVIEW_PAGE);
|
||||
} else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("渲染Office预览页面失败: {}", cacheName, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "渲染预览页面异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通Office预览(转PDF)
|
||||
*/
|
||||
private String handleRegularOfficePreview(Model model, FileAttribute fileAttribute,
|
||||
String fileName, boolean forceUpdatedCache, String cacheName,
|
||||
String outFilePath, boolean isHtmlView, boolean userToken,
|
||||
String filePassword) {
|
||||
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
// 下载远程文件到本地,如果文件在本地已存在不会重复下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath); // 判断是否加密文件
|
||||
|
||||
boolean isPwdProtectedOffice = OfficeUtils.isPwdProtected(filePath); // 判断是否加密文件
|
||||
if (isPwdProtectedOffice && !StringUtils.hasLength(filePassword)) {
|
||||
// 加密文件需要密码
|
||||
model.addAttribute("needFilePassword", true);
|
||||
@@ -109,43 +296,33 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if (!isHtmlView && baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType))) {
|
||||
return getPreviewType(model, fileAttribute, officePreviewType, cacheName, outFilePath, fileHandlerService, OFFICE_PREVIEW_TYPE_IMAGE, otherFilePreview);
|
||||
}
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName)); //输出转义文件名 方便url识别
|
||||
return isHtmlView ? EXEL_FILE_PREVIEW_PAGE : PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
String getPreviewType(Model model, FileAttribute fileAttribute, String officePreviewType, String pdfName, String outFilePath, FileHandlerService fileHandlerService, String officePreviewTypeImage, OtherFilePreviewImpl otherFilePreview) {
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
boolean isPPT = suffix.equalsIgnoreCase("ppt") || suffix.equalsIgnoreCase("pptx");
|
||||
List<String> imageUrls = null;
|
||||
try {
|
||||
imageUrls = pdftojpgservice.pdf2jpg(outFilePath,outFilePath, pdfName, fileAttribute);
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(OFFICE_PASSWORD_MSG)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 异步方法
|
||||
*/
|
||||
public String checkAndHandleConvertStatus(Model model, String fileName, String cacheName, FileAttribute fileAttribute){
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
if (status != null) {
|
||||
if (status.getStatus() == FileConvertStatusManager.Status.CONVERTING) {
|
||||
// 正在转换中,返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", status.getRealTimeMessage());
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.TIMEOUT) {
|
||||
// 超时状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换已超时,无法继续转换");
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.FAILED) {
|
||||
// 失败状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换失败,无法继续转换");
|
||||
}
|
||||
}
|
||||
if (imageUrls == null || imageUrls.size() < 1) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "office转图片异常,请联系管理员");
|
||||
}
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.get(0));
|
||||
if (officePreviewTypeImage.equals(officePreviewType)) {
|
||||
// PPT 图片模式使用专用预览页面
|
||||
return (isPPT ? PPT_FILE_PREVIEW_PAGE : OFFICE_PICTURE_FILE_PREVIEW_PAGE);
|
||||
} else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -7,14 +7,23 @@ import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.PdfToJpgService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.apache.poi.EncryptedDocumentException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* Created by kl on 2018/1/17.
|
||||
@@ -23,80 +32,263 @@ import java.util.List;
|
||||
@Service
|
||||
public class PdfFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PdfFilePreviewImpl.class);
|
||||
private static final String PDF_PASSWORD_MSG = "password";
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final PdfToJpgService pdftojpgservice;
|
||||
private static final String PDF_PASSWORD_MSG = "password";
|
||||
public PdfFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview, PdfToJpgService pdftojpgservice) {
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
public PdfFilePreviewImpl(FileHandlerService fileHandlerService,
|
||||
OtherFilePreviewImpl otherFilePreview,
|
||||
PdfToJpgService pdftojpgservice) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.pdftojpgservice = pdftojpgservice;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String pdfName = fileAttribute.getName(); //获取原始文件名
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType(); //转换类型
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //生成的文件路径
|
||||
String originFilePath = fileAttribute.getOriginFilePath(); //原始文件路径
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) || OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
//当文件不存在时,就去下载
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
String originFilePath; //原始文件路径
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String filePassword = fileAttribute.getFilePassword(); // 获取密码
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
// 查询转换状态
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status != null) {
|
||||
if (status.getStatus() == FileConvertStatusManager.Status.CONVERTING) {
|
||||
// 正在转换中,返回等待页面
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", status.getRealTimeMessage());
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} else if (status.getStatus() == FileConvertStatusManager.Status.TIMEOUT) {
|
||||
// 超时状态,不允许重新转换
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "文件转换已超时,无法继续转换");
|
||||
}
|
||||
}
|
||||
boolean jiami=false;
|
||||
if(!ObjectUtils.isEmpty(filePassword)){
|
||||
jiami=pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath);
|
||||
}
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_ALL_IMAGES.equals(officePreviewType)) {
|
||||
|
||||
// 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
if(jiami){
|
||||
return renderPreview(model, cacheName, outFilePath,
|
||||
officePreviewType, pdfName, fileAttribute);
|
||||
}
|
||||
// 当文件不存在时,就去下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
originFilePath = response.getContent();
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(originFilePath));
|
||||
// 检查文件是否需要密码,但不启动转换
|
||||
if (filePassword == null || filePassword.trim().isEmpty()) {
|
||||
// 没有提供密码,先检查文件是否需要密码
|
||||
if (checkIfPdfNeedsPassword(originFilePath, cacheName, pdfName)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("cacheName", cacheName);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncPdfConversion(originFilePath, outFilePath, cacheName, pdfName, fileAttribute);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start PDF conversion: {}", originFilePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "PDF转换异常,请联系管理员");
|
||||
}
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
return renderPreview(model, cacheName, outFilePath,
|
||||
officePreviewType, pdfName, fileAttribute);
|
||||
}
|
||||
List<String> imageUrls;
|
||||
try {
|
||||
imageUrls = pdftojpgservice.pdf2jpg(originFilePath,outFilePath, pdfName, fileAttribute);
|
||||
} else {
|
||||
// 处理普通PDF预览(非图片转换)
|
||||
return handleRegularPdfPreview(url, model, fileAttribute, pdfName, forceUpdatedCache, outFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查PDF文件是否需要密码(不进行实际转换)
|
||||
*/
|
||||
private boolean checkIfPdfNeedsPassword(String originFilePath, String cacheName, String pdfName) {
|
||||
try {
|
||||
// 尝试用空密码加载PDF,检查是否需要密码
|
||||
File pdfFile = new File(originFilePath);
|
||||
if (!pdfFile.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用try-with-resources确保资源释放
|
||||
try (org.apache.pdfbox.pdmodel.PDDocument tempDoc = org.apache.pdfbox.Loader.loadPDF(pdfFile, "")) {
|
||||
// 如果能加载成功,说明不需要密码
|
||||
int pageCount = tempDoc.getNumberOfPages();
|
||||
logger.info("PDF文件不需要密码,总页数: {},文件: {}", pageCount, originFilePath);
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
logger.info("PDF文件需要密码: {}", originFilePath);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "pdf转图片异常,请联系管理员");
|
||||
logger.warn("PDF文件检查异常: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
if (imageUrls == null || imageUrls.size() < 1) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "pdf转图片异常,请联系管理员");
|
||||
} catch (Exception e) {
|
||||
logger.error("检查PDF密码状态失败: {}", originFilePath, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步PDF转换
|
||||
*/
|
||||
private void startAsyncPdfConversion(String originFilePath, String outFilePath,
|
||||
String cacheName, String pdfName,
|
||||
FileAttribute fileAttribute) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<List<String>> conversionFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 更新状态
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动PDF转换", 10);
|
||||
|
||||
List<String> imageUrls = pdftojpgservice.pdf2jpg(originFilePath, outFilePath,
|
||||
pdfName, fileAttribute);
|
||||
|
||||
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||
boolean usePasswordCache = fileAttribute.getUsePasswordCache();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
if (ConfigConstants.isCacheEnabled() && (ObjectUtils.isEmpty(filePassword) || usePasswordCache)) {
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
FileConvertStatusManager.updateProgress(cacheName, "转换完成", 100);
|
||||
// 短暂延迟后清理状态
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return imageUrls;
|
||||
} else {
|
||||
FileConvertStatusManager.markError(cacheName, "PDF转换失败,未生成图片");
|
||||
return null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
|
||||
for (Throwable throwable : throwableArray) {
|
||||
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
|
||||
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
|
||||
// 标记为需要密码的状态
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.error("PDF转换执行失败: {}", cacheName, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.whenCompleteAsync((imageUrls, throwable) -> {
|
||||
if (imageUrls != null && !imageUrls.isEmpty()) {
|
||||
try {
|
||||
// 是否保留PDF源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(originFilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("PDF转换后续处理失败: {}", originFilePath, e);
|
||||
}
|
||||
} else {
|
||||
// 转换失败,保留源文件供排查问题
|
||||
logger.error("PDF转换失败,保留源文件: {}", originFilePath);
|
||||
if (throwable != null) {
|
||||
logger.error("转换失败原因: ", throwable);
|
||||
}
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染预览页面
|
||||
*/
|
||||
private String renderPreview(Model model, String cacheName,
|
||||
String outFilePath, String officePreviewType,
|
||||
String pdfName, FileAttribute fileAttribute) {
|
||||
try {
|
||||
List<String> imageUrls;
|
||||
if(pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath)){
|
||||
imageUrls = pdftojpgservice.getEncryptedPdfCache(outFilePath);
|
||||
}else {
|
||||
imageUrls = fileHandlerService.loadPdf2jpgCache(outFilePath);
|
||||
}
|
||||
if (imageUrls == null || imageUrls.isEmpty()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "PDF转换缓存异常,请联系管理员");
|
||||
}
|
||||
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.get(0));
|
||||
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType)) {
|
||||
return OFFICE_PICTURE_FILE_PREVIEW_PAGE;
|
||||
} else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
} else {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (!fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
model.addAttribute("pdfUrl", fileHandlerService.getRelativePath(response.getContent()));
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(pdfName));
|
||||
} catch (Exception e) {
|
||||
logger.error("渲染PDF预览页面失败: {}", cacheName, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "渲染预览页面异常,请联系管理员");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通PDF预览(非图片转换)
|
||||
*/
|
||||
private String handleRegularPdfPreview(String url, Model model, FileAttribute fileAttribute,
|
||||
String pdfName, boolean forceUpdatedCache,
|
||||
String outFilePath) {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(pdfName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
model.addAttribute("pdfUrl", fileHandlerService.getRelativePath(response.getContent()));
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(pdfName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", url);
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(pdfName));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("pdfUrl", url);
|
||||
}
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class SimTextFilePreviewImpl implements FilePreview {
|
||||
return null;
|
||||
}
|
||||
if (!file.exists() || file.length() == 0) {
|
||||
return "";
|
||||
return "KK提醒您:文件不存在或者已经被删除了!";
|
||||
} else {
|
||||
String charset = EncodingDetects.getJavaEncode(filePath);
|
||||
if ("ASCII".equals(charset)) {
|
||||
|
||||
@@ -7,12 +7,18 @@ import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.TifToPdfService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* tiff 图片文件处理
|
||||
@@ -22,21 +28,37 @@ import java.util.List;
|
||||
@Service
|
||||
public class TiffFilePreviewImpl implements FilePreview {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TiffFilePreviewImpl.class);
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final TifToPdfService tiftoservice;
|
||||
public TiffFilePreviewImpl(FileHandlerService fileHandlerService,OtherFilePreviewImpl otherFilePreview,TifToPdfService tiftoservice) {
|
||||
private final OfficeFilePreviewImpl officefilepreviewimpl;
|
||||
|
||||
public TiffFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview, TifToPdfService tiftoservice, OfficeFilePreviewImpl officefilepreviewimpl) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.tiftoservice = tiftoservice;
|
||||
this.officefilepreviewimpl = officefilepreviewimpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
String fileName = fileAttribute.getName();
|
||||
String tifPreviewType = ConfigConstants.getTifPreviewType();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String outFilePath = fileAttribute.getOutFilePath();
|
||||
boolean forceUpdatedCache=fileAttribute.forceUpdatedCache();
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
|
||||
// 查询转换状态
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, fileName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
|
||||
if ("jpg".equalsIgnoreCase(tifPreviewType) || "pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
@@ -44,66 +66,111 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncTiffConversion(filePath, outFilePath, cacheName, fileName, fileAttribute, tifPreviewType, forceUpdatedCache);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to start TIF conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转换异常,请联系系统管理员!");
|
||||
}
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
try {
|
||||
tiftoservice.convertTif2Pdf(filePath, outFilePath,forceUpdatedCache);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
model.addAttribute("currentUrl", url);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转pdf异常,请联系系统管理员!" );
|
||||
}
|
||||
}
|
||||
//是否保留TIFF源文件
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName));
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
// 将tif转换为jpg,返回转换后的文件路径、文件名的list
|
||||
List<String> listPic2Jpg;
|
||||
try {
|
||||
listPic2Jpg = tiftoservice.convertTif2Jpg(filePath, outFilePath,forceUpdatedCache);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
model.addAttribute("currentUrl", url);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转JPG异常,请联系系统管理员!" );
|
||||
}
|
||||
} else if ("jpg".equalsIgnoreCase(tifPreviewType)) {
|
||||
List<String> imgCache = fileHandlerService.getImgCache(cacheName);
|
||||
if (imgCache == null || imgCache.isEmpty()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "TIF转换缓存异常,请联系系统管理员!");
|
||||
}
|
||||
//是否保留源文件,转换失败保留源文件,转换成功删除源文件
|
||||
if(!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.putImgCache(cacheName, listPic2Jpg);
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
model.addAttribute("imgUrls", listPic2Jpg);
|
||||
model.addAttribute("currentUrl", listPic2Jpg.get(0));
|
||||
model.addAttribute("imgUrls", imgCache);
|
||||
model.addAttribute("currentUrl", imgCache.getFirst());
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("pdfUrl", WebUtils.encodeFileName(cacheName));
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
else if ("jpg".equalsIgnoreCase(tifPreviewType)) {
|
||||
model.addAttribute("imgUrls", fileHandlerService.getImgCache(cacheName));
|
||||
model.addAttribute("currentUrl", fileHandlerService.getImgCache(cacheName).get(0));
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// 处理普通TIF预览(不进行转换)
|
||||
return handleRegularTiffPreview(url, model, fileAttribute, fileName, forceUpdatedCache, outFilePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步TIF转换
|
||||
*/
|
||||
private void startAsyncTiffConversion(String filePath, String outFilePath, String cacheName,
|
||||
String fileName, FileAttribute fileAttribute,
|
||||
String tifPreviewType, boolean forceUpdatedCache) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<Void> conversionFuture = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 更新状态
|
||||
FileConvertStatusManager.startConvert(cacheName);
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动TIF转换", 10);
|
||||
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
tiftoservice.convertTif2Pdf(filePath, outFilePath,fileName,cacheName, forceUpdatedCache);
|
||||
|
||||
// 转换成功,更新缓存
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
List<String> listPic2Jpg = tiftoservice.convertTif2Jpg(filePath, outFilePath,fileName,cacheName, forceUpdatedCache);
|
||||
// 转换成功,更新缓存
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.putImgCache(cacheName, listPic2Jpg);
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
}
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
// 检查是否为Bad endianness tag异常
|
||||
if (e.getMessage() != null && e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)")) {
|
||||
// 特殊处理:对于这种异常,我们不标记为转换失败,而是记录日志
|
||||
logger.warn("TIF文件格式异常(Bad endianness tag),将尝试直接预览: {}", filePath);
|
||||
FileConvertStatusManager.convertSuccess(cacheName);
|
||||
return null;
|
||||
} else {
|
||||
logger.error("TIF转换执行失败: {}", cacheName, e);
|
||||
|
||||
// 检查是否已经标记为超时
|
||||
FileConvertStatusManager.ConvertStatus status = FileConvertStatusManager.getConvertStatus(cacheName);
|
||||
if (status == null || status.getStatus() != FileConvertStatusManager.Status.TIMEOUT) {
|
||||
FileConvertStatusManager.markError(cacheName, "转换失败: " + e.getMessage());
|
||||
}
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.thenRunAsync(() -> {
|
||||
try {
|
||||
// 是否保留源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("TIF转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
}, callbackExecutor).exceptionally(throwable -> {
|
||||
// 转换失败,记录日志但不删除源文件
|
||||
logger.error("TIF转换失败,保留源文件供排查: {}", filePath, throwable);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理普通TIF预览(不进行转换)
|
||||
*/
|
||||
private String handleRegularTiffPreview(String url, Model model, FileAttribute fileAttribute,
|
||||
String fileName, boolean forceUpdatedCache, String outFilePath) {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(fileName) || !ConfigConstants.isCacheEnabled()) {
|
||||
@@ -117,12 +184,11 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
fileHandlerService.addConvertedFile(fileName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("currentUrl", WebUtils.encodeFileName(fileName));
|
||||
model.addAttribute("currentUrl", WebUtils.encodeFileName(fileName));
|
||||
}
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
model.addAttribute("currentUrl", url);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user