mirror of
https://gitee.com/kekingcn/file-online-preview.git
synced 2026-11-11 22:00:44 +00:00
优化多线程转换方法 添加异步转换提示
This commit is contained in:
@@ -34,6 +34,7 @@ public interface FilePreview {
|
||||
String NOT_SUPPORTED_FILE_PAGE = "fileNotSupported";
|
||||
String XLSX_FILE_PREVIEW_PAGE = "officeweb";
|
||||
String CSV_FILE_PREVIEW_PAGE = "csv";
|
||||
String WAITING_FILE_PREVIEW_PAGE = "waiting";
|
||||
|
||||
String filePreviewHandle(String url, Model model, FileAttribute fileAttribute);
|
||||
}
|
||||
|
||||
621
server/src/main/java/cn/keking/service/Mediatomp4Service.java
Normal file
621
server/src/main/java/cn/keking/service/Mediatomp4Service.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,8 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -28,6 +30,7 @@ public class OfficeToPdfService {
|
||||
|
||||
|
||||
public static void converterFile(File inputFile, String outputFilePath_end, FileAttribute fileAttribute) throws OfficeException {
|
||||
Instant startTime = Instant.now();
|
||||
File outputFile = new File(outputFilePath_end);
|
||||
// 假如目标路径不存在,则新建该路径
|
||||
if (!outputFile.getParentFile().exists() && !outputFile.getParentFile().mkdirs()) {
|
||||
@@ -65,7 +68,42 @@ public class OfficeToPdfService {
|
||||
} else {
|
||||
builder = LocalConverter.builder().storeProperties(customProperties);
|
||||
}
|
||||
builder.build().convert(inputFile).to(outputFile).execute();
|
||||
|
||||
try {
|
||||
builder.build().convert(inputFile).to(outputFile).execute();
|
||||
|
||||
// 计算转换耗时
|
||||
Instant endTime = Instant.now();
|
||||
Duration duration = Duration.between(startTime, endTime);
|
||||
|
||||
// 格式化显示耗时(支持不同时间单位)
|
||||
String durationFormatted;
|
||||
if (duration.toMinutes() > 0) {
|
||||
durationFormatted = String.format("%d分%d秒",
|
||||
duration.toMinutes(),
|
||||
duration.toSecondsPart());
|
||||
} else if (duration.toSeconds() > 0) {
|
||||
durationFormatted = String.format("%d.%03d秒",
|
||||
duration.toSeconds(),
|
||||
duration.toMillisPart());
|
||||
} else {
|
||||
durationFormatted = String.format("%d毫秒", duration.toMillis());
|
||||
}
|
||||
|
||||
logger.info("文件转换成功:{} -> {},耗时:{}",
|
||||
inputFile.getName(),
|
||||
outputFile.getName(),
|
||||
durationFormatted);
|
||||
|
||||
} catch (OfficeException e) {
|
||||
Instant endTime = Instant.now();
|
||||
Duration duration = Duration.between(startTime, endTime);
|
||||
logger.error("文件转换失败:{},已耗时:{}毫秒,错误信息:{}",
|
||||
inputFile.getName(),
|
||||
duration.toMillis(),
|
||||
e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -97,4 +135,4 @@ public class OfficeToPdfService {
|
||||
return inputFilePath.substring(inputFilePath.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
472
server/src/main/java/cn/keking/service/TifToPdfService.java
Normal file
472
server/src/main/java/cn/keking/service/TifToPdfService.java
Normal file
@@ -0,0 +1,472 @@
|
||||
package cn.keking.service;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.apache.commons.imaging.Imaging;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* TIF文件转换服务 - 虚拟线程版本 (JDK 21+)
|
||||
*/
|
||||
@Component
|
||||
public class TifToPdfService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(TifToPdfService.class);
|
||||
private static final String FILE_DIR = ConfigConstants.getFileDir();
|
||||
|
||||
// 虚拟线程执行器
|
||||
private ExecutorService virtualThreadExecutor;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
try {
|
||||
// 创建虚拟线程执行器
|
||||
this.virtualThreadExecutor = Executors.newVirtualThreadPerTaskExecutor();
|
||||
logger.info("TIF转换虚拟线程执行器初始化完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("虚拟线程执行器初始化失败", e);
|
||||
// 降级为固定线程池
|
||||
this.virtualThreadExecutor = Executors.newFixedThreadPool(
|
||||
Math.min(getMaxConcurrentConversions(), Runtime.getRuntime().availableProcessors() * 2)
|
||||
);
|
||||
logger.warn("使用固定线程池作为降级方案");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TIF转JPG - 虚拟线程版本
|
||||
*/
|
||||
public List<String> convertTif2Jpg(String strInputFile, String strOutputFile,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
String fileName = new File(strInputFile).getName();
|
||||
Instant startTime = Instant.now();
|
||||
|
||||
try {
|
||||
List<String> result = performTifToJpgConversionVirtual(
|
||||
strInputFile, strOutputFile, forceUpdatedCache
|
||||
);
|
||||
|
||||
Duration elapsedTime = Duration.between(startTime, Instant.now());
|
||||
boolean success = result != null && !result.isEmpty();
|
||||
|
||||
logger.info("TIF转换{} - 文件: {}, 耗时: {}ms, 页数: {}",
|
||||
success ? "成功" : "失败", fileName, elapsedTime.toMillis(),
|
||||
result != null ? result.size() : 0);
|
||||
|
||||
return result != null ? result : Collections.emptyList();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("TIF转JPG失败: {}, 耗时: {}ms", fileName,
|
||||
Duration.between(startTime, Instant.now()).toMillis(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟线程执行TIF转JPG转换
|
||||
*/
|
||||
private List<String> performTifToJpgConversionVirtual(String strInputFile, String strOutputFile,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
Instant totalStart = Instant.now();
|
||||
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
String outputDirPath = strOutputFile.substring(0, strOutputFile.lastIndexOf('.'));
|
||||
|
||||
File tiffFile = new File(strInputFile);
|
||||
if (!tiffFile.exists()) {
|
||||
throw new FileNotFoundException("文件不存在: " + strInputFile);
|
||||
}
|
||||
|
||||
File outputDir = new File(outputDirPath);
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
throw new IOException("创建目录失败: " + outputDirPath);
|
||||
}
|
||||
|
||||
// 加载所有图片
|
||||
List<BufferedImage> images;
|
||||
try {
|
||||
images = Imaging.getAllBufferedImages(tiffFile);
|
||||
// logger.info("TIF文件加载完成,共{}页,文件: {}",images.size(), strInputFile);
|
||||
} catch (IOException e) {
|
||||
handleImagingException(e, strInputFile);
|
||||
throw e;
|
||||
}
|
||||
|
||||
int pageCount = images.size();
|
||||
if (pageCount == 0) {
|
||||
logger.warn("TIF文件没有可转换的页面: {}", strInputFile);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> result = convertPagesVirtualThreads(images, outputDirPath, baseUrl, forceUpdatedCache);
|
||||
|
||||
Duration totalTime = Duration.between(totalStart, Instant.now());
|
||||
logger.info("TIF转换PNG完成,总页数: {}, 总耗时: {}ms", pageCount, totalTime.toMillis());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用虚拟线程并行转换页面
|
||||
*/
|
||||
private List<String> convertPagesVirtualThreads(List<BufferedImage> images, String outputDirPath,
|
||||
String baseUrl, boolean forceUpdatedCache) {
|
||||
int pageCount = images.size();
|
||||
AtomicInteger successCount = new AtomicInteger(0);
|
||||
AtomicInteger skipCount = new AtomicInteger(0);
|
||||
AtomicInteger errorCount = new AtomicInteger(0);
|
||||
|
||||
// 用于收集结果的并发列表
|
||||
List<String> imageUrls = Collections.synchronizedList(new ArrayList<>(pageCount));
|
||||
|
||||
Instant startTime = Instant.now();
|
||||
|
||||
try {
|
||||
// 使用虚拟线程并行处理所有页面
|
||||
List<CompletableFuture<Void>> futures = IntStream.range(0, pageCount)
|
||||
.mapToObj(pageIndex -> CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
BufferedImage image = images.get(pageIndex);
|
||||
|
||||
// 使用PNG格式,质量更好
|
||||
String fileName = outputDirPath + File.separator + pageIndex + ".png";
|
||||
File outputFile = new File(fileName);
|
||||
|
||||
if (forceUpdatedCache || !outputFile.exists()) {
|
||||
// 创建目录
|
||||
File parentDir = outputFile.getParentFile();
|
||||
if (!parentDir.exists()) {
|
||||
parentDir.mkdirs();
|
||||
}
|
||||
|
||||
boolean writeSuccess = ImageIO.write(image, "png", outputFile);
|
||||
if (!writeSuccess) {
|
||||
throw new IOException("无法写入PNG格式");
|
||||
}
|
||||
successCount.incrementAndGet();
|
||||
} else {
|
||||
skipCount.incrementAndGet();
|
||||
}
|
||||
|
||||
// 构建URL
|
||||
String relativePath = fileName.replace(FILE_DIR, "");
|
||||
String url = baseUrl + WebUtils.encodeFileName(relativePath);
|
||||
imageUrls.add(url);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("转换页 {} 失败: {}", pageIndex, e.getMessage());
|
||||
errorCount.incrementAndGet();
|
||||
}
|
||||
}, virtualThreadExecutor))
|
||||
.toList();
|
||||
|
||||
// 等待所有任务完成
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.completeOnTimeout(null, getConversionTimeout(), TimeUnit.SECONDS)
|
||||
.join();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("虚拟线程并行转换异常", e);
|
||||
}
|
||||
|
||||
Duration elapsedTime = Duration.between(startTime, Instant.now());
|
||||
|
||||
logger.info("TIF虚拟线程转换统计: 成功={}, 跳过={}, 失败={}, 总页数={}, 耗时={}ms",successCount.get(), skipCount.get(), errorCount.get(), pageCount,
|
||||
elapsedTime.toMillis());
|
||||
|
||||
// 按页码排序
|
||||
return imageUrls.stream()
|
||||
.sorted(Comparator.comparing(url -> {
|
||||
// 从URL中提取页码进行排序
|
||||
String fileName = url.substring(url.lastIndexOf('/') + 1);
|
||||
return Integer.parseInt(fileName.substring(0, fileName.lastIndexOf('.')));
|
||||
}))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* TIF转PDF - 虚拟线程版本
|
||||
*/
|
||||
public void convertTif2Pdf(String strJpgFile, String strPdfFile,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
String fileName = new File(strJpgFile).getName();
|
||||
Instant startTime = Instant.now();
|
||||
|
||||
try {
|
||||
File pdfFile = new File(strPdfFile);
|
||||
|
||||
// 检查缓存
|
||||
if (!forceUpdatedCache && pdfFile.exists()) {
|
||||
logger.info("PDF文件已存在,跳过转换: {}", strPdfFile);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean result = performTifToPdfConversionVirtual(strJpgFile, strPdfFile);
|
||||
Duration elapsedTime = Duration.between(startTime, Instant.now());
|
||||
|
||||
logger.info("TIF转PDF{} - 文件: {}, 耗时: {}ms",
|
||||
result ? "成功" : "失败", fileName, elapsedTime.toMillis());
|
||||
|
||||
if (!result) {
|
||||
throw new Exception("TIF转PDF失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("TIF转PDF失败: {}, 耗时: {}ms", fileName,
|
||||
Duration.between(startTime, Instant.now()).toMillis(), e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟线程执行TIF转PDF转换(保持顺序)
|
||||
*/
|
||||
private boolean performTifToPdfConversionVirtual(String strJpgFile, String strPdfFile) throws Exception {
|
||||
Instant totalStart = Instant.now();
|
||||
|
||||
File tiffFile = new File(strJpgFile);
|
||||
|
||||
try (PDDocument document = new PDDocument()) {
|
||||
// 直接使用Imaging获取所有图像
|
||||
List<BufferedImage> images = Imaging.getAllBufferedImages(tiffFile);
|
||||
|
||||
if (images.isEmpty()) {
|
||||
logger.warn("TIFF文件没有可转换的页面: {}", strJpgFile);
|
||||
return false;
|
||||
}
|
||||
|
||||
int pageCount = images.size();
|
||||
AtomicInteger processedCount = new AtomicInteger(0);
|
||||
AtomicInteger errorCount = new AtomicInteger(0);
|
||||
|
||||
// 创建页面处理结果的列表
|
||||
List<CompletableFuture<ProcessedPageResult>> futures = new ArrayList<>(pageCount);
|
||||
|
||||
// 为每个页面创建处理任务
|
||||
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
|
||||
final int currentPageIndex = pageIndex;
|
||||
BufferedImage originalImage = images.get(pageIndex);
|
||||
|
||||
CompletableFuture<ProcessedPageResult> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
// 处理图像(耗时的操作)
|
||||
BufferedImage processedImage = processImageVirtualOptimized(originalImage);
|
||||
|
||||
// 返回处理结果,包含页码和处理后的图像
|
||||
return new ProcessedPageResult(currentPageIndex, processedImage);
|
||||
} catch (Exception e) {
|
||||
logger.error("异步处理页 {} 失败", currentPageIndex + 1, e);
|
||||
errorCount.incrementAndGet();
|
||||
return null;
|
||||
}
|
||||
}, virtualThreadExecutor);
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
// 等待所有任务完成
|
||||
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
|
||||
futures.toArray(new CompletableFuture[0])
|
||||
);
|
||||
|
||||
// 设置超时
|
||||
allFutures.completeOnTimeout(null, getConversionTimeout(), TimeUnit.SECONDS).join();
|
||||
|
||||
// 按顺序收集处理结果
|
||||
List<ProcessedPageResult> results = new ArrayList<>(pageCount);
|
||||
for (CompletableFuture<ProcessedPageResult> future : futures) {
|
||||
ProcessedPageResult result = future.get();
|
||||
if (result != null) {
|
||||
results.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 按页码排序(确保顺序)
|
||||
results.sort(Comparator.comparingInt(ProcessedPageResult::pageIndex));
|
||||
|
||||
// 按顺序添加到PDF文档
|
||||
for (ProcessedPageResult result : results) {
|
||||
try {
|
||||
// 创建页面
|
||||
PDPage page = new PDPage(PDRectangle.A4);
|
||||
document.addPage(page);
|
||||
|
||||
// 转换为PDImageXObject
|
||||
PDImageXObject pdImage = LosslessFactory.createFromImage(document, result.processedImage());
|
||||
|
||||
// 计算位置并绘制图像
|
||||
float[] position = calculateImagePositionOptimized(page, pdImage);
|
||||
try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) {
|
||||
contentStream.drawImage(pdImage, position[0], position[1], position[2], position[3]);
|
||||
}
|
||||
|
||||
// 释放资源
|
||||
result.processedImage().flush();
|
||||
processedCount.incrementAndGet();
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("添加页 {} 到PDF失败", result.pageIndex() + 1, e);
|
||||
errorCount.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
// 保存PDF
|
||||
document.save(strPdfFile);
|
||||
|
||||
// Duration totalTime = Duration.between(totalStart, Instant.now());
|
||||
//logger.info("PDF异步转换完成: {}, 总页数: {}, 成功: {}, 失败: {}, 总耗时: {}ms", strPdfFile, pageCount, processedCount.get(), errorCount.get(), totalTime.toMillis());
|
||||
|
||||
return processedCount.get() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面处理结果类
|
||||
*/
|
||||
private record ProcessedPageResult(int pageIndex, BufferedImage processedImage) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化的图像处理方法
|
||||
*/
|
||||
private BufferedImage processImageVirtualOptimized(BufferedImage original) {
|
||||
int targetDPI = 150;
|
||||
float a4WidthInch = 8.27f;
|
||||
float a4HeightInch = 11.69f;
|
||||
|
||||
int maxWidth = (int) (a4WidthInch * targetDPI);
|
||||
int maxHeight = (int) (a4HeightInch * targetDPI);
|
||||
|
||||
if (original.getWidth() <= maxWidth && original.getHeight() <= maxHeight) {
|
||||
return original;
|
||||
}
|
||||
|
||||
double scaleX = (double) maxWidth / original.getWidth();
|
||||
double scaleY = (double) maxHeight / original.getHeight();
|
||||
double scale = Math.min(scaleX, scaleY);
|
||||
|
||||
int newWidth = (int) (original.getWidth() * scale);
|
||||
int newHeight = (int) (original.getHeight() * scale);
|
||||
|
||||
BufferedImage resized = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g = resized.createGraphics();
|
||||
|
||||
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
||||
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
|
||||
g.drawImage(original, 0, 0, newWidth, newHeight, null);
|
||||
g.dispose();
|
||||
|
||||
return resized;
|
||||
}
|
||||
|
||||
/**
|
||||
* 优化的位置计算方法
|
||||
*/
|
||||
private float[] calculateImagePositionOptimized(PDPage page, PDImageXObject pdImage) {
|
||||
float margin = 5;
|
||||
float pageWidth = page.getMediaBox().getWidth() - 2 * margin;
|
||||
float pageHeight = page.getMediaBox().getHeight() - 2 * margin;
|
||||
|
||||
float imageWidth = pdImage.getWidth();
|
||||
float imageHeight = pdImage.getHeight();
|
||||
|
||||
float scale = Math.min(pageWidth / imageWidth, pageHeight / imageHeight);
|
||||
float scaledWidth = imageWidth * scale;
|
||||
float scaledHeight = imageHeight * scale;
|
||||
|
||||
float x = (pageWidth - scaledWidth) / 2 + margin;
|
||||
float y = (pageHeight - scaledHeight) / 2 + margin;
|
||||
|
||||
return new float[]{x, y, scaledWidth, scaledHeight};
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理
|
||||
*/
|
||||
private void handleImagingException(IOException e, String filePath) {
|
||||
String message = e.getMessage();
|
||||
if (message != null && message.contains("Only sequential, baseline JPEGs are supported at the moment")) {
|
||||
logger.warn("不支持的非基线JPEG格式,文件:{}", filePath, e);
|
||||
} else {
|
||||
logger.error("TIF转JPG异常,文件路径:{}", filePath, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转换超时时间
|
||||
*/
|
||||
private long getConversionTimeout() {
|
||||
try {
|
||||
String timeoutStr = ConfigConstants.getTifTimeout();
|
||||
if (timeoutStr != null && !timeoutStr.trim().isEmpty()) {
|
||||
long timeout = Long.parseLong(timeoutStr);
|
||||
if (timeout > 0) {
|
||||
return Math.min(timeout, 600L);
|
||||
}
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("解析TIF转换超时时间失败,使用默认值300秒", e);
|
||||
}
|
||||
return 300L;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最大并发数
|
||||
*/
|
||||
private int getMaxConcurrentConversions() {
|
||||
try {
|
||||
int maxConcurrent = ConfigConstants.getTifThread();
|
||||
if (maxConcurrent > 0) {
|
||||
return Math.min(maxConcurrent, 50);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取并发数配置失败,使用默认值", e);
|
||||
}
|
||||
return 4;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
logger.info("开始关闭TIF转换服务...");
|
||||
|
||||
if (virtualThreadExecutor != null && !virtualThreadExecutor.isShutdown()) {
|
||||
try {
|
||||
virtualThreadExecutor.shutdown();
|
||||
if (!virtualThreadExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
|
||||
virtualThreadExecutor.shutdownNow();
|
||||
}
|
||||
logger.info("虚拟线程执行器已关闭");
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
virtualThreadExecutor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("TIF转换服务已关闭");
|
||||
}
|
||||
}
|
||||
@@ -1,426 +0,0 @@
|
||||
package cn.keking.service;
|
||||
|
||||
import cn.keking.config.ConfigConstants;
|
||||
import cn.keking.utils.WebUtils;
|
||||
import cn.keking.web.filter.BaseUrlFilter;
|
||||
import com.itextpdf.text.Document;
|
||||
import com.itextpdf.text.DocumentException;
|
||||
import com.itextpdf.text.Image;
|
||||
import com.itextpdf.text.io.FileChannelRandomAccessSource;
|
||||
import com.itextpdf.text.pdf.PdfWriter;
|
||||
import com.itextpdf.text.pdf.RandomAccessFileOrArray;
|
||||
import com.itextpdf.text.pdf.codec.TiffImage;
|
||||
import org.apache.commons.imaging.Imaging;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class TifToService {
|
||||
|
||||
private static final int FIT_WIDTH = 500;
|
||||
private static final int FIT_HEIGHT = 900;
|
||||
private static final Logger logger = LoggerFactory.getLogger(TifToService.class);
|
||||
private static final String FILE_DIR = ConfigConstants.getFileDir();
|
||||
// 用于文档同步的锁对象
|
||||
private final Object documentLock = new Object();
|
||||
// 专用线程池用于TIF转换
|
||||
private static ExecutorService tifConversionPool;
|
||||
|
||||
static {
|
||||
initThreadPool();
|
||||
}
|
||||
|
||||
private static void initThreadPool() {
|
||||
int corePoolSize = getOptimalThreadCount();
|
||||
int maxPoolSize = corePoolSize * 2;
|
||||
long keepAliveTime = 60L;
|
||||
|
||||
tifConversionPool = new ThreadPoolExecutor(
|
||||
corePoolSize,
|
||||
maxPoolSize,
|
||||
keepAliveTime,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<>(100),
|
||||
new ThreadFactory() {
|
||||
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
||||
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setName("tif-convert-thread-" + threadNumber.getAndIncrement());
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
}
|
||||
},
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
|
||||
logger.info("TIF转换线程池初始化完成,核心线程数: {}, 最大线程数: {}", corePoolSize, maxPoolSize);
|
||||
}
|
||||
|
||||
private static int getOptimalThreadCount() {
|
||||
int cpuCores = Runtime.getRuntime().availableProcessors();
|
||||
// 对于I/O密集型任务,可以设置更多的线程
|
||||
return Math.min(cpuCores * 2, 16);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 RandomAccessFileOrArray 实例(修复已弃用的构造函数)
|
||||
*/
|
||||
private RandomAccessFileOrArray createRandomAccessFileOrArray(File file) throws IOException {
|
||||
RandomAccessFile aFile = new RandomAccessFile(file, "r");
|
||||
FileChannel inChannel = aFile.getChannel();
|
||||
FileChannelRandomAccessSource fcra = new FileChannelRandomAccessSource(inChannel);
|
||||
return new RandomAccessFileOrArray(fcra);
|
||||
}
|
||||
|
||||
/**
|
||||
* TIF转JPG - 支持多线程并行处理
|
||||
*/
|
||||
public List<String> convertTif2Jpg(String strInputFile, String strOutputFile,
|
||||
boolean forceUpdatedCache) throws Exception {
|
||||
return convertTif2Jpg(strInputFile, strOutputFile, forceUpdatedCache, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* TIF转JPG - 可选择是否启用并行处理
|
||||
* @param parallelProcessing 是否启用并行处理
|
||||
*/
|
||||
public List<String> convertTif2Jpg(String strInputFile, String strOutputFile,
|
||||
boolean forceUpdatedCache, boolean parallelProcessing) throws Exception {
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
String outputDirPath = strOutputFile.substring(0, strOutputFile.lastIndexOf('.'));
|
||||
|
||||
File tiffFile = new File(strInputFile);
|
||||
if (!tiffFile.exists()) {
|
||||
logger.error("找不到文件【{}】", strInputFile);
|
||||
throw new FileNotFoundException("文件不存在: " + strInputFile);
|
||||
}
|
||||
|
||||
File outputDir = new File(outputDirPath);
|
||||
if (!outputDir.exists() && !outputDir.mkdirs()) {
|
||||
throw new IOException("创建目录失败: " + outputDirPath);
|
||||
}
|
||||
|
||||
// 加载所有图片
|
||||
List<BufferedImage> images;
|
||||
try {
|
||||
images = Imaging.getAllBufferedImages(tiffFile);
|
||||
logger.info("TIF文件加载完成,共{}页,文件: {}", images.size(), strInputFile);
|
||||
} catch (IOException e) {
|
||||
handleImagingException(e, strInputFile);
|
||||
throw e;
|
||||
}
|
||||
|
||||
int pageCount = images.size();
|
||||
|
||||
// 根据页面数量决定是否使用并行处理
|
||||
boolean useParallel = parallelProcessing && pageCount > 5;
|
||||
|
||||
if (useParallel) {
|
||||
return convertParallel(images, outputDirPath, baseUrl, forceUpdatedCache);
|
||||
} else {
|
||||
return convertSequentially(images, outputDirPath, baseUrl, forceUpdatedCache);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 并行转换
|
||||
*/
|
||||
private List<String> convertParallel(List<BufferedImage> images, String outputDirPath,
|
||||
String baseUrl, boolean forceUpdatedCache) {
|
||||
int pageCount = images.size();
|
||||
List<CompletableFuture<String>> futures = new ArrayList<>(pageCount);
|
||||
AtomicInteger successCount = new AtomicInteger(0);
|
||||
AtomicInteger skipCount = new AtomicInteger(0);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
// 提交所有页面转换任务
|
||||
for (int i = 0; i < pageCount; i++) {
|
||||
final int pageIndex = i;
|
||||
BufferedImage image = images.get(i);
|
||||
|
||||
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
String fileName = outputDirPath + File.separator + pageIndex + ".jpg";
|
||||
File outputFile = new File(fileName);
|
||||
|
||||
// 检查是否需要转换
|
||||
if (forceUpdatedCache || !outputFile.exists()) {
|
||||
// 使用PNG格式保持更好的质量,如果需要JPG可以调整
|
||||
boolean success = ImageIO.write(image, "png", outputFile);
|
||||
if (!success) {
|
||||
logger.error("无法写入图片格式,页号: {}", pageIndex);
|
||||
return null;
|
||||
}
|
||||
logger.debug("并行转换图片页 {} 完成", pageIndex);
|
||||
successCount.incrementAndGet();
|
||||
} else {
|
||||
logger.debug("使用缓存图片页 {}", pageIndex);
|
||||
skipCount.incrementAndGet();
|
||||
}
|
||||
|
||||
// 构建URL
|
||||
String relativePath = fileName.replace(FILE_DIR, "");
|
||||
return baseUrl + WebUtils.encodeFileName(relativePath);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("并行转换页 {} 失败: {}", pageIndex, e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}, tifConversionPool);
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
|
||||
// 等待所有任务完成并收集结果
|
||||
List<String> imageUrls = futures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
long elapsedTime = System.currentTimeMillis() - startTime;
|
||||
|
||||
logger.info("TIF并行转换完成: 成功={}, 跳过={}, 总页数={}, 耗时={}ms",
|
||||
successCount.get(), skipCount.get(), pageCount, elapsedTime);
|
||||
|
||||
return imageUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* 串行转换
|
||||
*/
|
||||
private List<String> convertSequentially(List<BufferedImage> images, String outputDirPath,
|
||||
String baseUrl, boolean forceUpdatedCache) throws Exception {
|
||||
List<String> imageUrls = new ArrayList<>(images.size());
|
||||
|
||||
for (int i = 0; i < images.size(); i++) {
|
||||
String fileName = outputDirPath + File.separator + i + ".jpg";
|
||||
File outputFile = new File(fileName);
|
||||
|
||||
try {
|
||||
if (forceUpdatedCache || !outputFile.exists()) {
|
||||
BufferedImage image = images.get(i);
|
||||
boolean success = ImageIO.write(image, "png", outputFile);
|
||||
if (!success) {
|
||||
throw new IOException("无法写入JPG格式图片: " + fileName);
|
||||
}
|
||||
logger.debug("转换图片页 {} 完成", i);
|
||||
} else {
|
||||
logger.debug("使用缓存图片页 {}", i);
|
||||
}
|
||||
|
||||
String relativePath = fileName.replace(FILE_DIR, "");
|
||||
String url = baseUrl + WebUtils.encodeFileName(relativePath);
|
||||
imageUrls.add(url);
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error("转换页 {} 失败: {}", i, e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return imageUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JPG图片转换为PDF - 优化版本
|
||||
*/
|
||||
public void convertTif2Pdf(String strJpgFile, String strPdfFile) throws Exception {
|
||||
convertJpg2Pdf(strJpgFile, strPdfFile, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将JPG图片转换为PDF - 支持并行处理图片加载
|
||||
*/
|
||||
public void convertJpg2Pdf(String strJpgFile, String strPdfFile, boolean parallelLoad) throws Exception {
|
||||
Document document = new Document();
|
||||
FileOutputStream outputStream = null;
|
||||
RandomAccessFileOrArray rafa = null;
|
||||
|
||||
try {
|
||||
File tiffFile = new File(strJpgFile);
|
||||
|
||||
// 修复:使用非弃用的方式创建 RandomAccessFileOrArray
|
||||
rafa = createRandomAccessFileOrArray(tiffFile);
|
||||
int pages = TiffImage.getNumberOfPages(rafa);
|
||||
logger.info("开始转换TIFF到PDF,总页数: {}", pages);
|
||||
|
||||
outputStream = new FileOutputStream(strPdfFile);
|
||||
PdfWriter.getInstance(document, outputStream);
|
||||
document.open();
|
||||
|
||||
// 修改为传入File对象而不是RandomAccessFileOrArray
|
||||
if (parallelLoad && pages > 10) {
|
||||
convertPagesParallel(document, tiffFile, pages);
|
||||
} else {
|
||||
convertPagesSequentially(document, tiffFile, pages);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
handlePdfConversionException(e, strPdfFile);
|
||||
throw e;
|
||||
} finally {
|
||||
// 修复:传入 rafa 以正确关闭资源
|
||||
closeResources(document, rafa, outputStream);
|
||||
}
|
||||
|
||||
logger.info("PDF转换完成: {}", strPdfFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 串行处理页面 - 修复版
|
||||
*/
|
||||
private void convertPagesSequentially(Document document, File tiffFile, int pages) throws IOException, DocumentException {
|
||||
RandomAccessFileOrArray rafa = null;
|
||||
try {
|
||||
// 修复:使用非弃用的方式创建 RandomAccessFileOrArray
|
||||
rafa = createRandomAccessFileOrArray(tiffFile);
|
||||
for (int i = 1; i <= pages; i++) {
|
||||
Image image = TiffImage.getTiffImage(rafa, i);
|
||||
image.scaleToFit(FIT_WIDTH, FIT_HEIGHT);
|
||||
document.add(image);
|
||||
|
||||
if (i % 10 == 0) {
|
||||
logger.debug("已处理 {} 页", i);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (rafa != null) {
|
||||
try {
|
||||
rafa.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭RandomAccessFileOrArray失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 并行加载并添加图片到PDF - 修复版
|
||||
*/
|
||||
private void convertPagesParallel(Document document, File tiffFile, int pages) {
|
||||
List<CompletableFuture<Image>> futures = new ArrayList<>();
|
||||
|
||||
// 提交所有页面加载任务
|
||||
for (int i = 1; i <= pages; i++) {
|
||||
final int pageNum = i;
|
||||
CompletableFuture<Image> future = CompletableFuture.supplyAsync(() -> {
|
||||
RandomAccessFileOrArray localRafa = null;
|
||||
try {
|
||||
// 为每个线程创建独立的RandomAccessFileOrArray
|
||||
// 修复:使用非弃用的方式创建 RandomAccessFileOrArray
|
||||
localRafa = createRandomAccessFileOrArray(tiffFile);
|
||||
|
||||
Image image = TiffImage.getTiffImage(localRafa, pageNum);
|
||||
image.scaleToFit(FIT_WIDTH, FIT_HEIGHT);
|
||||
logger.debug("并行加载TIFF页 {}", pageNum);
|
||||
return image;
|
||||
} catch (Exception e) {
|
||||
logger.error("加载TIFF页 {} 失败", pageNum, e);
|
||||
return null;
|
||||
} finally {
|
||||
if (localRafa != null) {
|
||||
try {
|
||||
localRafa.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭RandomAccessFileOrArray失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, tifConversionPool);
|
||||
|
||||
futures.add(future);
|
||||
}
|
||||
// 按顺序添加到文档(保持页面顺序)
|
||||
for (int i = 0; i < futures.size(); i++) {
|
||||
try {
|
||||
Image image = futures.get(i).get(30, TimeUnit.SECONDS);
|
||||
if (image != null) {
|
||||
// 使用专门的锁对象而不是同步document参数
|
||||
synchronized (documentLock) {
|
||||
document.add(image);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("添加页 {} 到PDF失败", i + 1, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异常处理
|
||||
*/
|
||||
private void handleImagingException(IOException e, String filePath) {
|
||||
if (!e.getMessage().contains("Only sequential, baseline JPEGs are supported at the moment")) {
|
||||
logger.error("TIF转JPG异常,文件路径:{}", filePath, e);
|
||||
} else {
|
||||
logger.warn("不支持的非基线JPEG格式,文件:{}", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
private void handlePdfConversionException(IOException e, String filePath) {
|
||||
if (!e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)")) {
|
||||
logger.error("TIF转PDF异常,文件路径:{}", filePath, e);
|
||||
} else {
|
||||
logger.warn("TIFF文件字节顺序标记错误,文件:{}", filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 资源关闭
|
||||
*/
|
||||
private void closeResources(Document document, RandomAccessFileOrArray rafa, FileOutputStream outputStream) {
|
||||
try {
|
||||
if (document != null && document.isOpen()) {
|
||||
document.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭Document失败", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (rafa != null) {
|
||||
rafa.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭RandomAccessFileOrArray失败", e);
|
||||
}
|
||||
|
||||
try {
|
||||
if (outputStream != null) {
|
||||
outputStream.close();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭FileOutputStream失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优雅关闭
|
||||
*/
|
||||
public static void shutdown() {
|
||||
if (tifConversionPool != null && !tifConversionPool.isShutdown()) {
|
||||
tifConversionPool.shutdown();
|
||||
try {
|
||||
if (!tifConversionPool.awaitTermination(30, TimeUnit.SECONDS)) {
|
||||
tifConversionPool.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
tifConversionPool.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ 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.web.filter.BaseUrlFilter;
|
||||
@@ -16,6 +17,9 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author chenjh
|
||||
@@ -33,7 +37,13 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final OfficeFilePreviewImpl officefilepreviewimpl;
|
||||
|
||||
public CadFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview, CadToPdfService cadtopdfservice,OfficeFilePreviewImpl officefilepreviewimpl ) {
|
||||
// 用于处理回调的线程池
|
||||
private static final ExecutorService callbackExecutor = Executors.newFixedThreadPool(3);
|
||||
|
||||
public CadFilePreviewImpl(FileHandlerService fileHandlerService,
|
||||
OtherFilePreviewImpl otherFilePreview,
|
||||
CadToPdfService cadtopdfservice,
|
||||
OfficeFilePreviewImpl officefilepreviewimpl) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.cadtopdfservice = cadtopdfservice;
|
||||
@@ -43,41 +53,109 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
@Override
|
||||
public String filePreviewHandle(String url, Model model, FileAttribute fileAttribute) {
|
||||
// 预览Type,参数传了就取参数的,没传取系统默认
|
||||
String officePreviewType = fileAttribute.getOfficePreviewType() == null ? ConfigConstants.getOfficePreviewType() : fileAttribute.getOfficePreviewType();
|
||||
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();
|
||||
String cacheName = fileAttribute.getCacheName();
|
||||
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, "文件转换失败,无法继续转换");
|
||||
}
|
||||
}
|
||||
|
||||
// 判断之前是否已转换过,如果转换过,直接返回,否则执行转换
|
||||
if (forceUpdatedCache || !fileHandlerService.listConvertedFiles().containsKey(cacheName) || !ConfigConstants.isCacheEnabled()) {
|
||||
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 imageUrls = false;
|
||||
if (StringUtils.hasText(outFilePath)) {
|
||||
try {
|
||||
imageUrls = cadtopdfservice.cadToPdf(filePath, outFilePath, cadPreviewType, fileAttribute);
|
||||
// 启动异步转换,并添加回调处理
|
||||
startAsyncConversion(filePath, outFilePath, cacheName, fileAttribute);
|
||||
// 返回等待页面
|
||||
model.addAttribute("fileName", fileName);
|
||||
model.addAttribute("message", "文件正在转换中,请稍候...");
|
||||
return WAITING_FILE_PREVIEW_PAGE;
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to convert CAD file: {}", filePath, e);
|
||||
}
|
||||
if (!imageUrls) {
|
||||
logger.error("Failed to start CAD conversion: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "CAD转换异常,请联系管理员");
|
||||
}
|
||||
//是否保留CAD源文件
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
}
|
||||
}
|
||||
cacheName= WebUtils.encodeFileName(cacheName);
|
||||
// 如果已有缓存,直接渲染预览
|
||||
return renderPreview(model, cacheName, outFilePath, officePreviewType, cadPreviewType, fileAttribute);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动异步转换,并在转换完成后处理后续操作
|
||||
*/
|
||||
private void startAsyncConversion(String filePath, String outFilePath,
|
||||
String cacheName, FileAttribute fileAttribute) {
|
||||
// 启动异步转换
|
||||
CompletableFuture<Boolean> conversionFuture = cadtopdfservice.cadToPdfAsync(
|
||||
filePath,
|
||||
outFilePath,
|
||||
ConfigConstants.getCadPreviewType(),
|
||||
fileAttribute
|
||||
);
|
||||
|
||||
// 添加转换完成后的回调
|
||||
conversionFuture.whenCompleteAsync((success, throwable) -> {
|
||||
if (success != null && success) {
|
||||
try {
|
||||
// 1. 是否保留CAD源文件(只在转换成功后才删除)
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
// 2. 加入缓存(只在转换成功后才添加)
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
fileHandlerService.addConvertedFile(cacheName,
|
||||
fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("CAD转换后续处理失败: {}", filePath, e);
|
||||
}
|
||||
} else {
|
||||
// 转换失败,保留源文件供排查问题
|
||||
logger.error("CAD转换失败,保留源文件: {}", filePath);
|
||||
if (throwable != null) {
|
||||
logger.error("转换失败原因: ", throwable);
|
||||
}
|
||||
}
|
||||
}, callbackExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染预览页面
|
||||
*/
|
||||
private String renderPreview(Model model, String cacheName, String outFilePath,
|
||||
String officePreviewType, String cadPreviewType,
|
||||
FileAttribute fileAttribute) {
|
||||
cacheName = WebUtils.encodeFileName(cacheName);
|
||||
String baseUrl = BaseUrlFilter.getBaseUrl();
|
||||
|
||||
if ("tif".equalsIgnoreCase(cadPreviewType)) {
|
||||
model.addAttribute("currentUrl", cacheName);
|
||||
return TIFF_FILE_PREVIEW_PAGE;
|
||||
@@ -85,10 +163,15 @@ public class CadFilePreviewImpl implements FilePreview {
|
||||
model.addAttribute("currentUrl", cacheName);
|
||||
return SVG_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
model.addAttribute("pdfUrl", cacheName);
|
||||
return PDF_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,23 +6,19 @@ import cn.keking.model.FileType;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.Mediatomp4Service;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import org.bytedeco.ffmpeg.global.avcodec;
|
||||
import org.bytedeco.javacv.FFmpegFrameGrabber;
|
||||
import org.bytedeco.javacv.FFmpegFrameRecorder;
|
||||
import org.bytedeco.javacv.Frame;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* @author : kl
|
||||
@@ -36,14 +32,6 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
private static final Logger logger = LoggerFactory.getLogger(MediaFilePreviewImpl.class);
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private static final String mp4 = "mp4";
|
||||
|
||||
// 添加线程池管理视频转换任务
|
||||
private static final ExecutorService videoConversionExecutor =
|
||||
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
|
||||
|
||||
// 添加转换任务缓存,避免重复转换
|
||||
private static final Map<String, Future<String>> conversionTasks = new HashMap<>();
|
||||
|
||||
public MediaFilePreviewImpl(FileHandlerService fileHandlerService, OtherFilePreviewImpl otherFilePreview) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
@@ -61,235 +49,189 @@ public class MediaFilePreviewImpl implements FilePreview {
|
||||
String[] mediaTypesConvert = FileType.MEDIA_CONVERT_TYPES; //获取支持的转换格式
|
||||
boolean mediaTypes = false;
|
||||
for (String temp : mediaTypesConvert) {
|
||||
if (suffix.equals(temp)) {
|
||||
if (suffix.equalsIgnoreCase(temp)) {
|
||||
mediaTypes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!url.toLowerCase().startsWith("http") || checkNeedConvert(mediaTypes)) { //不是http协议的 // 开启转换方式并是支持转换格式的
|
||||
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());
|
||||
// 非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)) {
|
||||
model.addAttribute("mediaUrl", relativePath);
|
||||
logger.info("使用已缓存的视频文件: {}", cacheName);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
String filePath = response.getContent();
|
||||
String convertedUrl = null;
|
||||
try {
|
||||
if (mediaTypes) {
|
||||
// 检查是否已有正在进行的转换任务
|
||||
Future<String> conversionTask = conversionTasks.get(cacheName);
|
||||
if (conversionTask != null && !conversionTask.isDone()) {
|
||||
// 等待现有转换任务完成
|
||||
convertedUrl = conversionTask.get();
|
||||
} else {
|
||||
// 提交新的转换任务
|
||||
conversionTask = videoConversionExecutor.submit(() -> {
|
||||
return convertToMp4(filePath, outFilePath, fileAttribute);
|
||||
});
|
||||
conversionTasks.put(cacheName, conversionTask);
|
||||
convertedUrl = conversionTask.get();
|
||||
}
|
||||
} else {
|
||||
convertedUrl = outFilePath; //其他协议的 不需要转换方式的文件 直接输出
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to convert media file: {}", filePath, e);
|
||||
// 清理失败的任务
|
||||
conversionTasks.remove(cacheName);
|
||||
}
|
||||
if (convertedUrl == null) {
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "视频转换异常,请联系管理员");
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
fileHandlerService.addConvertedFile(cacheName, fileHandlerService.getRelativePath(outFilePath));
|
||||
}
|
||||
// 转换完成后清理任务缓存
|
||||
conversionTasks.remove(cacheName);
|
||||
model.addAttribute("mediaUrl", fileHandlerService.getRelativePath(outFilePath));
|
||||
} else {
|
||||
model.addAttribute("mediaUrl", fileHandlerService.listConvertedFiles().get(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;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("处理媒体文件失败: {}", filePath, e);
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute,
|
||||
"视频处理异常: " + getErrorMessage(e));
|
||||
}
|
||||
}
|
||||
if (type.equals(FileType.MEDIA)) { // 支持输出 只限默认格式
|
||||
|
||||
// HTTP协议的媒体文件,直接播放
|
||||
if (type.equals(FileType.MEDIA)) {
|
||||
model.addAttribute("mediaUrl", url);
|
||||
return MEDIA_FILE_PREVIEW_PAGE;
|
||||
}
|
||||
|
||||
return otherFilePreview.notSupportedFile(model, fileAttribute, "系统还不支持该格式文件的在线预览");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查视频文件转换是否已开启,以及当前文件是否需要转换
|
||||
*
|
||||
* @return
|
||||
* 检查文件大小是否超过限制
|
||||
*/
|
||||
private boolean isFileSizeExceeded(String filePath) {
|
||||
try {
|
||||
File inputFile = new File(filePath);
|
||||
if (inputFile.exists()) {
|
||||
long fileSizeMB = inputFile.length() / (1024 * 1024);
|
||||
int maxSizeMB = ConfigConstants.getMediaConvertMaxSize();
|
||||
|
||||
if (fileSizeMB > maxSizeMB) {
|
||||
logger.warn("视频文件大小超过限制: {}MB > {}MB", fileSizeMB, maxSizeMB);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("检查文件大小时出错: {}", filePath, e);
|
||||
}
|
||||
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.检查开关是否开启
|
||||
// 1.检查开关是否开启
|
||||
if ("true".equals(ConfigConstants.getMediaConvertDisable())) {
|
||||
return mediaTypes;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static String convertToMp4(String filePath, String outFilePath, FileAttribute fileAttribute) throws Exception {
|
||||
FFmpegFrameGrabber frameGrabber = null;
|
||||
FFmpegFrameRecorder recorder = null;
|
||||
try {
|
||||
File desFile = new File(outFilePath);
|
||||
//判断一下防止重复转换
|
||||
if (desFile.exists()) {
|
||||
return outFilePath;
|
||||
}
|
||||
|
||||
if (fileAttribute.isCompressFile()) { //判断 是压缩包的创建新的目录
|
||||
int index = outFilePath.lastIndexOf("/"); //截取最后一个斜杠的前面的内容
|
||||
String folder = outFilePath.substring(0, index);
|
||||
File path = new File(folder);
|
||||
//目录不存在 创建新的目录
|
||||
if (!path.exists()) {
|
||||
path.mkdirs();
|
||||
}
|
||||
}
|
||||
|
||||
frameGrabber = FFmpegFrameGrabber.createDefault(filePath);
|
||||
frameGrabber.start();
|
||||
|
||||
// 优化:使用更快的编码预设
|
||||
recorder = new FFmpegFrameRecorder(outFilePath,
|
||||
frameGrabber.getImageWidth(),
|
||||
frameGrabber.getImageHeight(),
|
||||
frameGrabber.getAudioChannels());
|
||||
|
||||
// 设置快速编码参数
|
||||
recorder.setFormat(mp4);
|
||||
recorder.setFrameRate(frameGrabber.getFrameRate());
|
||||
recorder.setSampleRate(frameGrabber.getSampleRate());
|
||||
|
||||
// 视频编码属性配置 - 使用快速编码预设
|
||||
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
|
||||
recorder.setVideoOption("preset", "veryfast"); // 添加快速编码预设
|
||||
recorder.setVideoOption("tune", "zerolatency"); // 降低延迟
|
||||
recorder.setVideoBitrate(frameGrabber.getVideoBitrate());
|
||||
recorder.setAspectRatio(frameGrabber.getAspectRatio());
|
||||
|
||||
// 音频编码设置
|
||||
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
|
||||
recorder.setAudioBitrate(frameGrabber.getAudioBitrate());
|
||||
recorder.setAudioChannels(frameGrabber.getAudioChannels());
|
||||
|
||||
// 优化:当源文件已经是h264/aac编码时,尝试直接复制流
|
||||
if (isCompatibleCodec(frameGrabber)) {
|
||||
recorder.setVideoOption("c:v", "copy");
|
||||
recorder.setAudioOption("c:a", "copy");
|
||||
}
|
||||
|
||||
recorder.start();
|
||||
|
||||
// 批量处理帧,提高处理效率
|
||||
Frame capturedFrame;
|
||||
int batchSize = 100; // 批量处理的帧数
|
||||
int frameCount = 0;
|
||||
Frame[] frameBatch = new Frame[batchSize];
|
||||
|
||||
while (true) {
|
||||
capturedFrame = frameGrabber.grabFrame();
|
||||
if (capturedFrame == null) {
|
||||
break;
|
||||
}
|
||||
|
||||
frameBatch[frameCount % batchSize] = capturedFrame;
|
||||
frameCount++;
|
||||
|
||||
// 批量记录帧
|
||||
if (frameCount % batchSize == 0 || capturedFrame == null) {
|
||||
for (int i = 0; i < Math.min(batchSize, frameCount); i++) {
|
||||
if (frameBatch[i] != null) {
|
||||
recorder.record(frameBatch[i]);
|
||||
frameBatch[i] = null; // 释放引用
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 记录剩余的帧
|
||||
for (int i = 0; i < frameBatch.length; i++) {
|
||||
if (frameBatch[i] != null) {
|
||||
recorder.record(frameBatch[i]);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("视频转码完成: {} -> {}", filePath, outFilePath);
|
||||
return outFilePath;
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Failed to convert video file to mp4: {}", filePath, e);
|
||||
// 删除可能已创建的失败文件
|
||||
try {
|
||||
File failedFile = new File(outFilePath);
|
||||
if (failedFile.exists()) {
|
||||
failedFile.delete();
|
||||
}
|
||||
} catch (SecurityException ex) {
|
||||
logger.warn("无法删除失败的转换文件: {}", outFilePath, ex);
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
// 确保资源被正确释放
|
||||
if (recorder != null) {
|
||||
try {
|
||||
recorder.stop();
|
||||
recorder.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭recorder时发生异常", e);
|
||||
}
|
||||
}
|
||||
if (frameGrabber != null) {
|
||||
try {
|
||||
frameGrabber.stop();
|
||||
frameGrabber.close();
|
||||
} catch (Exception e) {
|
||||
logger.warn("关闭frameGrabber时发生异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
// 强制垃圾回收,释放FFmpeg相关资源
|
||||
System.gc();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查源文件是否已经是兼容的编码格式(H264/AAC)
|
||||
* 获取友好的错误信息
|
||||
*/
|
||||
private static boolean isCompatibleCodec(FFmpegFrameGrabber grabber) {
|
||||
try {
|
||||
String videoCodec = grabber.getVideoCodecName();
|
||||
String audioCodec = grabber.getAudioCodecName();
|
||||
|
||||
boolean videoCompatible = videoCodec != null &&
|
||||
(videoCodec.toLowerCase().contains("h264") ||
|
||||
videoCodec.toLowerCase().contains("h.264"));
|
||||
|
||||
boolean audioCompatible = audioCodec != null &&
|
||||
(audioCodec.toLowerCase().contains("aac") ||
|
||||
audioCodec.toLowerCase().contains("mp3"));
|
||||
|
||||
return videoCompatible && audioCompatible;
|
||||
} catch (Exception e) {
|
||||
logger.debug("无法获取编解码器信息", e);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有转换任务(应用关闭时调用)
|
||||
*/
|
||||
public static void shutdown() {
|
||||
if (!CollectionUtils.isEmpty(conversionTasks)) {
|
||||
conversionTasks.values().forEach(task -> task.cancel(true));
|
||||
conversionTasks.clear();
|
||||
}
|
||||
videoConversionExecutor.shutdownNow();
|
||||
return "未知错误";
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import cn.keking.model.FileAttribute;
|
||||
import cn.keking.model.ReturnResponse;
|
||||
import cn.keking.service.FileHandlerService;
|
||||
import cn.keking.service.FilePreview;
|
||||
import cn.keking.service.TifToService;
|
||||
import cn.keking.service.TifToPdfService;
|
||||
import cn.keking.utils.DownloadUtils;
|
||||
import cn.keking.utils.KkFileUtils;
|
||||
import cn.keking.utils.WebUtils;
|
||||
@@ -16,7 +16,6 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* tiff 图片文件处理
|
||||
*
|
||||
* @author kl (http://kailing.pub)
|
||||
* @since 2021/2/8
|
||||
*/
|
||||
@@ -25,8 +24,8 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
|
||||
private final FileHandlerService fileHandlerService;
|
||||
private final OtherFilePreviewImpl otherFilePreview;
|
||||
private final TifToService tiftoservice;
|
||||
public TiffFilePreviewImpl(FileHandlerService fileHandlerService,OtherFilePreviewImpl otherFilePreview,TifToService tiftoservice) {
|
||||
private final TifToPdfService tiftoservice;
|
||||
public TiffFilePreviewImpl(FileHandlerService fileHandlerService,OtherFilePreviewImpl otherFilePreview,TifToPdfService tiftoservice) {
|
||||
this.fileHandlerService = fileHandlerService;
|
||||
this.otherFilePreview = otherFilePreview;
|
||||
this.tiftoservice = tiftoservice;
|
||||
@@ -47,7 +46,7 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
String filePath = response.getContent();
|
||||
if ("pdf".equalsIgnoreCase(tifPreviewType)) {
|
||||
try {
|
||||
tiftoservice.convertTif2Pdf(filePath, outFilePath);
|
||||
tiftoservice.convertTif2Pdf(filePath, outFilePath,forceUpdatedCache);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage().contains("Bad endianness tag (not 0x4949 or 0x4d4d)") ) {
|
||||
model.addAttribute("imgUrls", url);
|
||||
@@ -59,7 +58,7 @@ public class TiffFilePreviewImpl implements FilePreview {
|
||||
}
|
||||
//是否保留TIFF源文件
|
||||
if (!fileAttribute.isCompressFile() && ConfigConstants.getDeleteSourceFile()) {
|
||||
// KkFileUtils.deleteFileByPath(filePath);
|
||||
KkFileUtils.deleteFileByPath(filePath);
|
||||
}
|
||||
if (ConfigConstants.isCacheEnabled()) {
|
||||
// 加入缓存
|
||||
|
||||
Reference in New Issue
Block a user