mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-04-11 18:47:23 +00:00
5.0版本 发布 新增 cadviewer转换方法
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -33,13 +33,15 @@ public enum FileType {
|
||||
EPUB("epubFilePreviewImpl"),
|
||||
BPMN("bpmnFilePreviewImpl"),
|
||||
DCM("dcmFilePreviewImpl"),
|
||||
MSG("msgFilePreviewImpl"),
|
||||
DRAWIO("drawioFilePreviewImpl");
|
||||
|
||||
private static final String[] OFFICE_TYPES = {"docx", "wps", "doc", "docm", "xls", "xlsx", "csv" ,"xlsm", "ppt", "pptx", "vsd", "rtf", "odt", "wmf", "emf", "dps", "et", "ods", "ots", "tsv", "odp", "otp", "sxi", "ott", "vsdx", "fodt", "fods", "xltx","tga","psd","dotm","ett","xlt","xltm","wpt","dot","xlam","dotx","xla","pages", "eps"};
|
||||
private static final String[] PICTURE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "ico", "jfif", "webp"};
|
||||
private static final String[] PICTURE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "ico", "jfif", "webp", "heic", "avif"};
|
||||
private static final String[] ARCHIVE_TYPES = {"rar", "zip", "jar", "7-zip", "tar", "gzip", "7z"};
|
||||
private static final String[] ONLINE3D_TYPES = {"obj", "3ds", "stl", "ply", "off", "3dm", "fbx", "dae", "wrl", "3mf", "ifc","glb","o3dv","gltf","stp","bim","fcstd","step","iges","brep"};
|
||||
private static final String[] EML_TYPES = {"eml"};
|
||||
private static final String[] MSG_TYPES = {"msg"};
|
||||
private static final String[] XMIND_TYPES = {"xmind"};
|
||||
private static final String[] EPUB_TYPES = {"epub"};
|
||||
private static final String[] DCM_TYPES = {"dcm"};
|
||||
@@ -96,6 +98,9 @@ public enum FileType {
|
||||
for (String eml : EML_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(eml, FileType.EML);
|
||||
}
|
||||
for (String msg : MSG_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(msg, FileType.MSG);
|
||||
}
|
||||
for (String xmind : XMIND_TYPES) {
|
||||
FILE_TYPE_MAPPER.put(xmind, FileType.XMIND);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -78,7 +78,7 @@ public class CompressFileReader {
|
||||
|
||||
FileType type = FileType.typeFromUrl(filePathInsideArchive.toString());
|
||||
if (type.equals(FileType.PICTURE)) { //图片缓存到集合,为了特殊符号需要进行编码
|
||||
imgUrls.add(baseUrl + URLEncoder.encode(fileName + packagePath+"/"+ folderPath.relativize(filePathInsideArchive).toString().replace("\\", "/"), "UTF-8"));
|
||||
imgUrls.add(baseUrl + URLEncoder.encode(fileName + packagePath+"/"+ folderPath.relativize(filePathInsideArchive).toString().replace("\\", "/"), StandardCharsets.UTF_8).replaceAll("%2F", "/"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,8 +264,13 @@ public class FileHandlerService {
|
||||
.replaceAll("%3F", "?")
|
||||
.replaceAll("%26", "&")
|
||||
.replaceAll("%3D", "=");
|
||||
|
||||
}
|
||||
originFileName = KkFileUtils.htmlEscape(originFileName); //文件名处理
|
||||
if (!KkFileUtils.validateFileNameLength(originFileName)) {
|
||||
// 处理逻辑:抛出异常、记录日志、返回错误等
|
||||
throw new IllegalArgumentException("文件名超过系统限制");
|
||||
}
|
||||
boolean isHtmlView = suffix.equalsIgnoreCase("xls") || suffix.equalsIgnoreCase("xlsx") || suffix.equalsIgnoreCase("csv") || suffix.equalsIgnoreCase("xlsm") || suffix.equalsIgnoreCase("xlt") || suffix.equalsIgnoreCase("xltm") || suffix.equalsIgnoreCase("et") || suffix.equalsIgnoreCase("ett") || suffix.equalsIgnoreCase("xlam");
|
||||
String cacheFilePrefixName = null;
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,9 @@ public interface FilePreview {
|
||||
String NOT_SUPPORTED_FILE_PAGE = "fileNotSupported";
|
||||
String XLSX_FILE_PREVIEW_PAGE = "officeweb";
|
||||
String CSV_FILE_PREVIEW_PAGE = "csv";
|
||||
String MSG_FILE_PREVIEW_PAGE = "msg";
|
||||
String HEIC_FILE_PREVIEW_PAGE = "heic";
|
||||
String CADVIEWER_FILE_PREVIEW_PAGE = "cadviewer";
|
||||
String WAITING_FILE_PREVIEW_PAGE = "waiting";
|
||||
|
||||
String filePreviewHandle(String url, Model model, FileAttribute fileAttribute);
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* PDF转JPG服务 - JDK 21 高性能优化版本(使用虚拟线程和结构化并发)
|
||||
@@ -59,8 +58,7 @@ public class PdfToJpgService {
|
||||
// JDK 21: 使用虚拟线程调度器
|
||||
private final ScheduledExecutorService virtualCacheCleanupScheduler;
|
||||
|
||||
// 使用读写锁保护缓存操作
|
||||
private final ReentrantReadWriteLock cacheLock = new ReentrantReadWriteLock();
|
||||
|
||||
|
||||
// 加密PDF缓存记录类
|
||||
private static class EncryptedPdfCache {
|
||||
@@ -286,49 +284,6 @@ public class PdfToJpgService {
|
||||
logger.info("缓存清理任务已启动(每5分钟执行一次)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动性能监控
|
||||
*/
|
||||
private void startPerformanceMonitoring() {
|
||||
// 每10分钟记录一次性能统计
|
||||
virtualCacheCleanupScheduler.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
logPerformanceStatistics();
|
||||
} catch (Exception e) {
|
||||
logger.error("性能监控任务执行失败", e);
|
||||
}
|
||||
}, 5, 10, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录性能统计信息
|
||||
*/
|
||||
private void logPerformanceStatistics() {
|
||||
try {
|
||||
// 收集性能指标
|
||||
int runningTasksCount = runningTasks.size();
|
||||
int activeTasks = activeTaskCount.get();
|
||||
int totalCompleted = totalCompletedTasks.get();
|
||||
int cacheSize = encryptedPdfCacheMap.size();
|
||||
|
||||
// 计算内存使用
|
||||
long totalMemory = Runtime.getRuntime().totalMemory();
|
||||
long freeMemory = Runtime.getRuntime().freeMemory();
|
||||
long usedMemory = totalMemory - freeMemory;
|
||||
|
||||
logger.info(
|
||||
"PDF转换服务性能统计 - 运行中任务数: {}, 活跃任务数: {}, 累计完成任务数: {}, " +
|
||||
"内存缓存数量: {}, 内存使用: {} MB / {} MB ({}%), 虚拟线程: {}",
|
||||
runningTasksCount, activeTasks, totalCompleted, cacheSize,
|
||||
usedMemory / 1024 / 1024, totalMemory / 1024 / 1024,
|
||||
(usedMemory * 100 / totalMemory),
|
||||
Thread.currentThread().isVirtual() ? "是" : "否"
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("记录性能统计时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监控缓存统计信息
|
||||
@@ -527,84 +482,11 @@ public class PdfToJpgService {
|
||||
return imageUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* PDF转JPG - 异步版本(虚拟线程优化)
|
||||
|
||||
public CompletableFuture<List<String>> pdf2jpgAsync(String fileNameFilePath, String pdfFilePath,
|
||||
String pdfName, FileAttribute fileAttribute) {
|
||||
CompletableFuture<List<String>> future = new CompletableFuture<>();
|
||||
|
||||
// 尝试获取信号量,如果获取不到,则立即拒绝任务
|
||||
if (!concurrentTaskSemaphore.tryAcquire()) {
|
||||
future.completeExceptionally(new RejectedExecutionException("系统繁忙,请稍后再试"));
|
||||
return future;
|
||||
}
|
||||
|
||||
// JDK 21: 使用虚拟线程提交任务
|
||||
Future<?> taskFuture = virtualThreadExecutor.submit(() -> {
|
||||
try {
|
||||
List<String> result = pdf2jpg(fileNameFilePath, pdfFilePath, pdfName, fileAttribute);
|
||||
future.complete(result);
|
||||
} catch (Exception e) {
|
||||
future.completeExceptionally(e);
|
||||
} finally {
|
||||
// 必须在finally中释放信号量
|
||||
concurrentTaskSemaphore.release();
|
||||
runningTasks.remove(pdfName);
|
||||
}
|
||||
});
|
||||
|
||||
// 记录正在运行的任务
|
||||
runningTasks.put(pdfName, taskFuture);
|
||||
|
||||
// 设置超时检查(使用虚拟线程)
|
||||
scheduleVirtualTimeoutCheck(pdfName, future, taskFuture);
|
||||
|
||||
return future;
|
||||
}
|
||||
*/
|
||||
/**
|
||||
* 虚拟线程超时检查
|
||||
*/
|
||||
private void scheduleVirtualTimeoutCheck(String fileName, CompletableFuture<List<String>> taskFuture,
|
||||
Future<?> future) {
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
int timeout = calculateTimeoutByFileName();
|
||||
taskFuture.get(timeout, TimeUnit.SECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
handleConversionTimeout(fileName, taskFuture, future);
|
||||
} catch (Exception e) {
|
||||
// 正常完成或异常
|
||||
}
|
||||
}, virtualThreadExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理转换超时
|
||||
*/
|
||||
private void handleConversionTimeout(String fileName, CompletableFuture<List<String>> taskFuture,
|
||||
Future<?> future) {
|
||||
logger.error("PDF转换超时: {}", fileName);
|
||||
|
||||
// 取消正在运行的任务
|
||||
if (future != null && !future.isDone()) {
|
||||
boolean cancelled = future.cancel(true);
|
||||
logger.info("尝试取消PDF转换任务 {}: {}", fileName, cancelled ? "成功" : "失败");
|
||||
}
|
||||
|
||||
// 从运行任务列表中移除
|
||||
runningTasks.remove(fileName);
|
||||
|
||||
// 完成Future
|
||||
taskFuture.completeExceptionally(new TimeoutException("PDF转换超时: " + fileName));
|
||||
}
|
||||
|
||||
/**
|
||||
* PDF转JPG - 高性能主方法
|
||||
*/
|
||||
public List<String> pdf2jpg(String fileNameFilePath, String pdfFilePath,
|
||||
String pdfName, FileAttribute fileAttribute) throws Exception {
|
||||
FileAttribute fileAttribute) throws Exception {
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
|
||||
boolean usePasswordCache = fileAttribute.getUsePasswordCache();
|
||||
String filePassword = fileAttribute.getFilePassword();
|
||||
@@ -951,12 +833,6 @@ public class PdfToJpgService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据文件名计算超时时间
|
||||
*/
|
||||
private int calculateTimeoutByFileName() {
|
||||
return ConfigConstants.getPdfTimeoutMedium();
|
||||
}
|
||||
|
||||
/**
|
||||
* 按页码排序
|
||||
@@ -975,107 +851,5 @@ public class PdfToJpgService {
|
||||
return sortedImageUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制取消指定文件的转换任务
|
||||
*/
|
||||
public boolean cancelConversion(String fileName) {
|
||||
Future<?> future = runningTasks.get(fileName);
|
||||
if (future != null) {
|
||||
boolean cancelled = future.cancel(true);
|
||||
if (cancelled) {
|
||||
logger.info("成功取消PDF转换任务: {}", fileName);
|
||||
runningTasks.remove(fileName);
|
||||
}
|
||||
return cancelled;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取正在运行的任务数量
|
||||
*/
|
||||
public int getRunningTaskCount() {
|
||||
return runningTasks.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有正在运行的文件名
|
||||
*/
|
||||
public Set<String> getRunningTasks() {
|
||||
return new HashSet<>(runningTasks.keySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息(用于监控)
|
||||
*/
|
||||
public Map<String, Object> getCacheStatistics() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
cacheLock.readLock().lock();
|
||||
try {
|
||||
stats.put("cacheSize", encryptedPdfCacheMap.size());
|
||||
stats.put("runningTasks", runningTasks.size());
|
||||
stats.put("activeTasks", activeTaskCount.get());
|
||||
stats.put("totalCompleted", totalCompletedTasks.get());
|
||||
|
||||
// 计算缓存过期情况
|
||||
long expireTime = 10 * 60 * 1000L;
|
||||
int expiredCount = 0;
|
||||
for (EncryptedPdfCache cache : encryptedPdfCacheMap.values()) {
|
||||
if (cache.isExpired(expireTime)) {
|
||||
expiredCount++;
|
||||
}
|
||||
}
|
||||
stats.put("expiredCaches", expiredCount);
|
||||
|
||||
} finally {
|
||||
cacheLock.readLock().unlock();
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动清理所有过期缓存(供管理接口调用)
|
||||
*/
|
||||
public int cleanupAllExpiredCaches() {
|
||||
try {
|
||||
// 使用虚拟线程执行清理
|
||||
Future<Integer> future = virtualThreadExecutor.submit(() -> {
|
||||
long expireTime = 10 * 60 * 1000L;
|
||||
List<String> expiredKeys = new ArrayList<>();
|
||||
|
||||
cacheLock.readLock().lock();
|
||||
try {
|
||||
for (Map.Entry<String, EncryptedPdfCache> entry : encryptedPdfCacheMap.entrySet()) {
|
||||
if (entry.getValue().isExpired(expireTime)) {
|
||||
expiredKeys.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cacheLock.readLock().unlock();
|
||||
}
|
||||
|
||||
// 清理
|
||||
int cleaned = 0;
|
||||
for (String key : expiredKeys) {
|
||||
EncryptedPdfCache cache = encryptedPdfCacheMap.remove(key);
|
||||
if (cache != null) {
|
||||
try {
|
||||
deleteCacheFolder(cache.outputFolder());
|
||||
cleaned++;
|
||||
} catch (Exception e) {
|
||||
logger.warn("清理缓存文件失败: {}", cache.outputFolder(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
});
|
||||
|
||||
return future.get(30, TimeUnit.SECONDS);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("手动清理缓存失败", e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,7 @@ import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.CadToPdfService;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.FileConvertStatusManager;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.utils.*;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -78,12 +75,14 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
|
||||
String filePath = response.getContent();
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
if (StringUtils.hasText(outFilePath)) {
|
||||
try {
|
||||
// 启动异步转换,并添加回调处理
|
||||
startAsyncConversion(filePath, outFilePath, cacheName, fileAttribute);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
@@ -101,14 +100,33 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
*/
|
||||
private void startAsyncConversion(String filePath, String outFilePath,
|
||||
String cacheName, FileAttribute fileAttribute) {
|
||||
|
||||
//获取cad转换路径
|
||||
String cadConverterPath = ConfigConstants.getCadConverterPath();
|
||||
int conversionModule= ConfigConstants.getConversionModule();
|
||||
if(cadConverterPath.equals("false")){
|
||||
//默认aspose-cad
|
||||
conversionModule = 1;
|
||||
}
|
||||
CompletableFuture<Boolean> conversionFuture;
|
||||
// 启动异步转换
|
||||
CompletableFuture<Boolean> conversionFuture = cadtopdfservice.cadToPdfAsync(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cacheName,
|
||||
ConfigConstants.getCadPreviewType(),
|
||||
fileAttribute
|
||||
);
|
||||
if(conversionModule==2){
|
||||
conversionFuture = cadtopdfservice.cadViewerConvert(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cadConverterPath,
|
||||
ConfigConstants.getCadPreviewType(),
|
||||
cacheName
|
||||
);
|
||||
}else {
|
||||
conversionFuture = cadtopdfservice.cadToPdfAsync(
|
||||
filePath,
|
||||
outFilePath,
|
||||
cacheName,
|
||||
ConfigConstants.getCadPreviewType(),
|
||||
fileAttribute
|
||||
);
|
||||
}
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.whenCompleteAsync((success, throwable) -> {
|
||||
@@ -144,13 +162,18 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
FileAttribute fileAttribute) {
|
||||
cacheName = WebUtils.encodeFileName(cacheName);
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
|
||||
int conversionModule= ConfigConstants.getConversionModule();
|
||||
if ("tif".equalsIgnoreCase(cadPreviewType)) {
|
||||
model.addAttribute("currentUrl", cacheName);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
} else if ("svg".equalsIgnoreCase(cadPreviewType)) {
|
||||
model.addAttribute("currentUrl", cacheName);
|
||||
return SVG_FILE_PREVIEW_PAGE;
|
||||
if(conversionModule==2){
|
||||
return CADVIEWER_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return SVG_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (baseUrl != null && (OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType) ||
|
||||
|
||||
@@ -22,10 +22,8 @@ public class CodeFilePreviewImpl implements FilePreview {
|
||||
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
|
||||
}
|
||||
boolean isHtmlFile = suffix.equalsIgnoreCase("htm") || suffix.equalsIgnoreCase("html") || suffix.equalsIgnoreCase("shtml");
|
||||
model.addAttribute("isHtmlFile", isHtmlFile);
|
||||
return CODE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.keking.service.impl;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
@@ -10,6 +11,7 @@ import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -32,17 +34,26 @@ public class CommonPreviewImpl implements FilePreview {
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
String fileName = fileAttribute.getName(); //获取原始文件名
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(fileName) || !ConfigConstants.isCacheEnabled()) {
|
||||
if (url != null && !url.toLowerCase().startsWith("http")) {
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, null);
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, fileName);
|
||||
if (response.isFailure()) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, response.getMsg());
|
||||
} else {
|
||||
String file = fileHandlerService.getRelativePath(response.getContent());
|
||||
model.addAttribute("currentUrl", file);
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(fileName, file);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
model.addAttribute("currentUrl", url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
model.addAttribute("currentUrl", fileHandlerService.getConvertedFile(fileName));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,8 +97,10 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
try {
|
||||
// 启动异步转换,并添加回调处理
|
||||
startAsyncConversion(filePath, outFilePath, cacheName, fileAttribute);
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", "视频文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package cn.keking.service.impl;
|
||||
|
||||
import cn.keking.model.FileAttribute;
|
||||
import cn.keking.service.FilePreview;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
|
||||
/**
|
||||
* Dcm 文件处理
|
||||
*/
|
||||
@Service
|
||||
public class MsgFilePreviewImpl implements FilePreview {
|
||||
|
||||
private final CommonPreviewImpl commonPreview;
|
||||
|
||||
public MsgFilePreviewImpl(CommonPreviewImpl commonPreview) {
|
||||
this.commonPreview = commonPreview;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
commonPreview.filePreviewHandle(url,model,fileAttribute);
|
||||
return MSG_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,10 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncOfficeConversion(filePath, outFilePath, cacheName, fileAttribute, officePreviewType);
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
@@ -160,7 +162,7 @@ public class OfficeFilePreviewImpl implements FilePreview {
|
||||
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);
|
||||
imageUrls = pdftojpgservice.pdf2jpg(outFilePath, outFilePath, fileAttribute);
|
||||
}
|
||||
|
||||
// 缓存处理
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
@@ -37,16 +36,19 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final PdfToJpgService pdftojpgservice;
|
||||
private final OfficeFilePreviewImpl officefilepreviewimpl;
|
||||
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
public PdfFilePreviewImpl(FileHandlerService fileHandlerService,
|
||||
OtherFilePreviewImpl otherFilePreview,
|
||||
OfficeFilePreviewImpl officefilepreviewimpl,
|
||||
PdfToJpgService pdftojpgservice) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.pdftojpgservice = pdftojpgservice;
|
||||
this.officefilepreviewimpl = officefilepreviewimpl;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -56,35 +58,25 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache(); //是否启用强制更新命令
|
||||
String outFilePath = fileAttribute.getOutFilePath(); //生成的文件路径
|
||||
String originFilePath; //原始文件路径
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
String cacheName = pdfName+officePreviewType;
|
||||
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, "文件转换已超时,无法继续转换");
|
||||
}
|
||||
String statusResult = officefilepreviewimpl.checkAndHandleConvertStatus(model, pdfName, cacheName, fileAttribute);
|
||||
if (statusResult != null) {
|
||||
return statusResult;
|
||||
}
|
||||
|
||||
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);
|
||||
officePreviewType, fileAttribute);
|
||||
}
|
||||
// 当文件不存在时,就去下载
|
||||
ReturnResponse<String> response = DownloadUtils.downLoad(fileAttribute, pdfName);
|
||||
@@ -98,15 +90,17 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
if (checkIfPdfNeedsPassword(originFilePath, cacheName, pdfName)) {
|
||||
model.addAttribute("needFilePassword", true);
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("cacheName", cacheName);
|
||||
model.addAttribute("cacheName", pdfName);
|
||||
return EXEL_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncPdfConversion(originFilePath, outFilePath, cacheName, pdfName, fileAttribute);
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", pdfName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
@@ -116,7 +110,7 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
} else {
|
||||
// 如果已有缓存,直接渲染预览
|
||||
return renderPreview(model, cacheName, outFilePath,
|
||||
officePreviewType, pdfName, fileAttribute);
|
||||
officePreviewType, fileAttribute);
|
||||
}
|
||||
} else {
|
||||
// 处理普通PDF预览(非图片转换)
|
||||
@@ -175,13 +169,13 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
FileConvertStatusManager.updateProgress(cacheName, "正在启动PDF转换", 10);
|
||||
|
||||
List<String> imageUrls = pdftojpgservice.pdf2jpg(originFilePath, outFilePath,
|
||||
pdfName, fileAttribute);
|
||||
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));
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
FileConvertStatusManager.updateProgress(cacheName, "转换完成", 100);
|
||||
// 短暂延迟后清理状态
|
||||
@@ -214,17 +208,7 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
|
||||
// 添加转换完成后的回调
|
||||
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 {
|
||||
// 转换失败,保留源文件供排查问题
|
||||
if (imageUrls == null || imageUrls.isEmpty()) {
|
||||
logger.error("PDF转换失败,保留源文件: {}", originFilePath);
|
||||
if (throwable != null) {
|
||||
logger.error("转换失败原因: ", throwable);
|
||||
@@ -238,7 +222,7 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
*/
|
||||
private String renderPreview(Model model, String cacheName,
|
||||
String outFilePath, String officePreviewType,
|
||||
String pdfName, FileAttribute fileAttribute) {
|
||||
FileAttribute fileAttribute) {
|
||||
try {
|
||||
List<String> imageUrls;
|
||||
if(pdftojpgservice.hasEncryptedPdfCacheSimple(outFilePath)){
|
||||
@@ -251,7 +235,7 @@ public class PdfFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
|
||||
model.addAttribute("imgUrls", imageUrls);
|
||||
model.addAttribute("currentUrl", imageUrls.get(0));
|
||||
model.addAttribute("currentUrl", imageUrls.getFirst());
|
||||
|
||||
if (OfficeFilePreviewImpl.OFFICE_PREVIEW_TYPE_IMAGE.equals(officePreviewType)) {
|
||||
return OFFICE_PICTURE_FILE_PREVIEW_PAGE;
|
||||
|
||||
@@ -27,16 +27,28 @@ public class PictureFilePreviewImpl extends CommonPreviewImpl {
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
url= KkFileUtils.htmlEscape(url);
|
||||
String suffix = fileAttribute.getSuffix();
|
||||
List<String> imgUrls = new ArrayList<>();
|
||||
imgUrls.add(url);
|
||||
String compressFileKey = fileAttribute.getCompressFileKey();
|
||||
List<String> zipImgUrls = fileHandlerService.getImgCache(compressFileKey);
|
||||
if (!CollectionUtils.isEmpty(zipImgUrls)) {
|
||||
imgUrls.addAll(zipImgUrls);
|
||||
model.addAttribute("imgUrls", imgUrls);
|
||||
}else {
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
super.filePreviewHandle(url, model, fileAttribute);
|
||||
if ( url.toLowerCase().startsWith("file") || url.toLowerCase().startsWith("ftp")) {
|
||||
model.addAttribute("imgUrls", fileAttribute.getName());
|
||||
}else {
|
||||
model.addAttribute("imgUrls", url);
|
||||
}
|
||||
|
||||
}
|
||||
if(suffix.equalsIgnoreCase("heic")){
|
||||
return HEIC_FILE_PREVIEW_PAGE;
|
||||
}else {
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
// 不是http开头,浏览器不能直接访问,需下载到本地
|
||||
super.filePreviewHandle(url, model, fileAttribute);
|
||||
model.addAttribute("imgUrls", imgUrls);
|
||||
return PICTURE_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,8 +70,10 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
try {
|
||||
// 启动异步转换
|
||||
startAsyncTiffConversion(filePath, outFilePath, cacheName, fileName, fileAttribute, tifPreviewType, forceUpdatedCache);
|
||||
int refreshSchedule = ConfigConstants.getTime();
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("time", refreshSchedule);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -97,9 +97,10 @@ public class DownloadUtils {
|
||||
|
||||
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(), fileAttribute);
|
||||
WebUtils.applyBasicAuthHeaders(request.getHeaders(), finalUrlStr);
|
||||
String proxyAuthorization = fileAttribute.getKkProxyAuthorization();
|
||||
if(StringUtils.hasText(proxyAuthorization)){
|
||||
Map<String, String> proxyAuthorizationMap = mapper.readValue(
|
||||
|
||||
@@ -60,6 +60,18 @@ public class KkFileUtils {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public static boolean validateFileNameLength(String fileName) {
|
||||
if (fileName == null) {
|
||||
return false;
|
||||
}
|
||||
// 文件名长度限制:255个字符(不包含路径)
|
||||
int windowsMaxLength = 255;
|
||||
if (fileName.length() > windowsMaxLength) {
|
||||
System.err.println("文件名长度超过限制(255个字符)");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否是数字
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cn.keking.utils;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.model.FileAttribute;
|
||||
import io.mola.galimatias.GalimatiasParseException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -465,10 +464,8 @@ public class WebUtils {
|
||||
/**
|
||||
* 支持basic 下载方法
|
||||
*/
|
||||
public static void applyBasicAuthHeaders(HttpHeaders headers, FileAttribute fileAttribute) {
|
||||
String url = fileAttribute.getUrl();
|
||||
System.out.println(" T555.");
|
||||
System.out.println(url);
|
||||
public static void applyBasicAuthHeaders(HttpHeaders headers, String url) {
|
||||
|
||||
// 从配置文件读取User-Agent,如果没有配置则使用默认值
|
||||
String customUserAgent=ConfigConstants.getUserAgent();
|
||||
String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
|
||||
|
||||
@@ -197,9 +197,10 @@ public class OnlinePreviewController {
|
||||
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setRequestFactory(factory);
|
||||
String finalUrlPath = urlPath;
|
||||
RequestCallback requestCallback = request -> {
|
||||
request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));
|
||||
WebUtils.applyBasicAuthHeaders(request.getHeaders(), fileAttribute);
|
||||
WebUtils.applyBasicAuthHeaders(request.getHeaders(), finalUrlPath);
|
||||
String proxyAuthorization = fileAttribute.getKkProxyAuthorization();
|
||||
if(StringUtils.hasText(proxyAuthorization)){
|
||||
Map<String, String> proxyAuthorizationMap = mapper.readValue(
|
||||
|
||||
Reference in New Issue
Block a user