tif 优化多线程转换方法

This commit is contained in:
高雄
2026-01-09 17:36:11 +08:00
parent 9a3ec88390
commit c96b7ebd1e
3 changed files with 1328 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,399 @@
package cn.keking.service;
import cn.keking.config.ConfigConstants;
import cn.keking.model.FileAttribute;
import cn.keking.service.cache.NotResourceCache;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.apache.pdfbox.tools.imageio.ImageIOUtil;
import org.apache.poi.EncryptedDocumentException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author yudian-it
*/
@Component
public class PdfToJpgService {
private final FileHandlerService fileHandlerService;
// PDF转换专用线程池
private ExecutorService pdfConversionPool;
private ThreadPoolExecutor pdfThreadPoolExecutor;
private static final Logger logger = LoggerFactory.getLogger(PdfToJpgService.class);
private static final String PDF_PASSWORD_MSG = "password";
private static final String PDF2JPG_IMAGE_FORMAT = ".jpg";
// 最大并行页数阈值
private static final int MAX_PARALLEL_PAGES = 20;
public PdfToJpgService(FileHandlerService fileHandlerService) {
this.fileHandlerService = fileHandlerService;
}
@PostConstruct
public void init() {
try {
int threadCount = getPdfThreadPoolSize();
int queueCapacity = threadCount * 10;
AtomicInteger threadNum = new AtomicInteger(1);
pdfThreadPoolExecutor = new ThreadPoolExecutor(
threadCount,
threadCount,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(queueCapacity),
r -> {
Thread t = new Thread(r);
t.setName("pdf-convert-pool-" + threadNum.getAndIncrement());
t.setUncaughtExceptionHandler((thread, throwable) ->
logger.error("PDF转换线程未捕获异常: {}", thread.getName(), throwable));
return t;
},
new ThreadPoolExecutor.CallerRunsPolicy()
);
pdfThreadPoolExecutor.allowCoreThreadTimeOut(true);
pdfConversionPool = pdfThreadPoolExecutor;
logger.info("PDF转换线程池初始化完成线程数: {}, 队列容量: {}",
threadCount, queueCapacity);
} catch (Exception e) {
logger.error("PDF转换线程池初始化失败", e);
int defaultThreads = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
pdfConversionPool = Executors.newFixedThreadPool(defaultThreads);
logger.warn("使用默认PDF线程池配置线程数: {}", defaultThreads);
}
}
private int getPdfThreadPoolSize() {
try {
String pdfThreadConfig = System.getProperty("pdf.thread.count");
int threadCount;
if (pdfThreadConfig != null) {
threadCount = Integer.parseInt(pdfThreadConfig);
} else {
threadCount = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
}
if (threadCount <= 0) {
threadCount = Runtime.getRuntime().availableProcessors();
logger.warn("PDF线程数配置无效使用CPU核心数: {}", threadCount);
}
int maxThreads = Runtime.getRuntime().availableProcessors() * 2;
if (threadCount > maxThreads) {
logger.warn("PDF线程数配置过大({}),限制为: {}", threadCount, maxThreads);
threadCount = maxThreads;
}
return threadCount;
} catch (Exception e) {
logger.error("获取PDF线程数配置失败", e);
return Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
}
}
@PreDestroy
public void shutdown() {
if (pdfConversionPool != null && !pdfConversionPool.isShutdown()) {
gracefulShutdown(pdfConversionPool, getShutdownTimeout());
}
}
private long getShutdownTimeout() {
try {
String pdfTimeout = System.getProperty("pdf.timeout");
if (pdfTimeout != null) {
return Long.parseLong(pdfTimeout);
}
return Long.parseLong(ConfigConstants.getCadTimeout());
} catch (Exception e) {
logger.warn("获取PDF关闭超时时间失败使用默认值60秒", e);
return 60L;
}
}
private void gracefulShutdown(ExecutorService executor, long timeoutSeconds) {
logger.info("开始关闭{}...", "PDF转换线程池");
executor.shutdown();
try {
if (!executor.awaitTermination(timeoutSeconds, TimeUnit.SECONDS)) {
logger.warn("{}超时未关闭,尝试强制关闭...", "PDF转换线程池");
executor.shutdownNow();
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
logger.error("{}无法完全关闭", "PDF转换线程池");
} else {
logger.info("{}已强制关闭", "PDF转换线程池");
}
} else {
logger.info("{}已正常关闭", "PDF转换线程池");
}
} catch (InterruptedException e) {
logger.error("{}关闭时被中断", "PDF转换线程池", e);
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
public List<String> pdf2jpg(String fileNameFilePath, String pdfFilePath,
String pdfName, FileAttribute fileAttribute) throws Exception {
boolean forceUpdatedCache = fileAttribute.forceUpdatedCache();
boolean usePasswordCache = fileAttribute.getUsePasswordCache();
String filePassword = fileAttribute.getFilePassword();
// 1. 检查缓存
if (!forceUpdatedCache) {
List<String> cacheResult = fileHandlerService.loadPdf2jpgCache(pdfFilePath);
if (!CollectionUtils.isEmpty(cacheResult)) {
return cacheResult;
}
}
// 2. 验证文件存在
File pdfFile = new File(fileNameFilePath);
if (!pdfFile.exists()) {
logger.error("PDF文件不存在: {}", fileNameFilePath);
return null;
}
// 3. 创建输出目录
int index = pdfFilePath.lastIndexOf(".");
String folder = pdfFilePath.substring(0, index);
File path = new File(folder);
if (!path.exists() && !path.mkdirs()) {
logger.error("创建转换文件目录失败: {}", folder);
throw new IOException("无法创建输出目录");
}
// 4. 加载PDF文档获取页数
int pageCount = 0;
try (PDDocument tempDoc = Loader.loadPDF(pdfFile, filePassword)) {
pageCount = tempDoc.getNumberOfPages();
logger.info("PDF文件总页数: {}, 文件: {}", pageCount, pdfFilePath);
} catch (IOException e) {
handlePdfLoadException(e, pdfFilePath);
throw new Exception("PDF文件加载失败", e);
}
// 5. 根据页数决定转换策略
List<String> imageUrls;
if (pageCount > MAX_PARALLEL_PAGES) {
// 大文件使用新方案每页独立加载PDF
imageUrls = convertParallelIndependent(pdfFile, filePassword, pdfFilePath, folder, pageCount);
} else {
// 小文件使用串行处理(稳定)
imageUrls = convertSequentially(pdfFile, filePassword, pdfFilePath, folder, pageCount);
}
// 6. 缓存结果
if (usePasswordCache || ObjectUtils.isEmpty(filePassword)) {
fileHandlerService.addPdf2jpgCache(pdfFilePath, pageCount);
}
logger.info("PDF转换完成成功转换{}页,文件: {}", imageUrls.size(), pdfFilePath);
return imageUrls;
}
/**
* 处理PDF加载异常
*/
private void handlePdfLoadException(Exception e, String pdfFilePath) throws Exception {
Throwable[] throwableArray = ExceptionUtils.getThrowables(e);
for (Throwable throwable : throwableArray) {
if (throwable instanceof IOException || throwable instanceof EncryptedDocumentException) {
if (e.getMessage().toLowerCase().contains(PDF_PASSWORD_MSG)) {
logger.info("PDF文件需要密码: {}", pdfFilePath);
throw new Exception(PDF_PASSWORD_MSG, e);
}
}
}
logger.error("加载PDF文件异常, pdfFilePath{}", pdfFilePath, e);
throw new Exception("PDF文件加载失败", e);
}
/**
* 串行转换(稳定方案)
*/
private List<String> convertSequentially(File pdfFile, String filePassword,
String pdfFilePath, String folder, int pageCount) {
List<String> imageUrls = new ArrayList<>(pageCount);
try (PDDocument doc = Loader.loadPDF(pdfFile, filePassword)) {
doc.setResourceCache(new NotResourceCache());
PDFRenderer pdfRenderer = new PDFRenderer(doc);
pdfRenderer.setSubsamplingAllowed(true);
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
try {
String imageFilePath = folder + File.separator + pageIndex + PDF2JPG_IMAGE_FORMAT;
BufferedImage image = pdfRenderer.renderImageWithDPI(
pageIndex,
ConfigConstants.getPdf2JpgDpi(),
ImageType.RGB
);
ImageIOUtil.writeImage(image, imageFilePath, ConfigConstants.getPdf2JpgDpi());
image.flush();
String imageUrl = fileHandlerService.getPdf2jpgUrl(pdfFilePath, pageIndex);
imageUrls.add(imageUrl);
logger.debug("串行转换页 {} 完成", pageIndex);
} catch (Exception e) {
logger.error("串行转换页 {} 失败: {}", pageIndex, e.getMessage());
}
}
} catch (Exception e) {
logger.error("串行转换PDF失败", e);
}
return imageUrls;
}
/**
* 并行转换 - 每个线程独立加载PDF避免线程安全问题
*/
private List<String> convertParallelIndependent(File pdfFile, String filePassword,
String pdfFilePath, String folder, int pageCount) {
List<String> imageUrls = Collections.synchronizedList(new ArrayList<>());
List<Future<Boolean>> futures = new ArrayList<>();
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger errorCount = new AtomicInteger(0);
// 提交页面转换任务
for (int pageIndex = 0; pageIndex < pageCount; pageIndex++) {
final int currentPage = pageIndex;
Future<Boolean> future = pdfConversionPool.submit(() -> {
try {
// 每个任务独立加载PDF确保线程安全
try (PDDocument pageDoc = Loader.loadPDF(pdfFile, filePassword)) {
pageDoc.setResourceCache(new NotResourceCache());
PDFRenderer renderer = new PDFRenderer(pageDoc);
renderer.setSubsamplingAllowed(true);
String imageFilePath = folder + File.separator + currentPage + PDF2JPG_IMAGE_FORMAT;
BufferedImage image = renderer.renderImageWithDPI(
currentPage,
ConfigConstants.getPdf2JpgDpi(),
ImageType.RGB
);
ImageIOUtil.writeImage(image, imageFilePath, ConfigConstants.getPdf2JpgDpi());
image.flush();
String imageUrl = fileHandlerService.getPdf2jpgUrl(pdfFilePath, currentPage);
synchronized (imageUrls) {
imageUrls.add(imageUrl);
}
successCount.incrementAndGet();
logger.debug("并行转换页 {} 完成", currentPage);
return true;
}
} catch (Exception e) {
errorCount.incrementAndGet();
logger.error("并行转换页 {} 失败: {}", currentPage, e.getMessage());
return false;
}
});
futures.add(future);
}
// 等待所有任务完成
int timeout = calculateTimeout(pageCount);
long startTime = System.currentTimeMillis();
for (Future<Boolean> future : futures) {
try {
future.get(timeout, TimeUnit.SECONDS);
} catch (TimeoutException e) {
logger.warn("页面转换任务超时,取消剩余任务");
for (Future<Boolean> f : futures) {
if (!f.isDone()) {
f.cancel(true);
}
}
break;
} catch (Exception e) {
logger.error("页面转换任务执行失败", e);
}
// 检查是否超时
if (System.currentTimeMillis() - startTime > timeout * 1000L) {
logger.warn("PDF转换整体超时取消剩余任务");
for (Future<Boolean> f : futures) {
if (!f.isDone()) {
f.cancel(true);
}
}
break;
}
}
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("并行转换统计: 成功={}, 失败={}, 总页数={}, 耗时={}ms",
successCount.get(), errorCount.get(), pageCount, elapsedTime);
// 按页码排序
imageUrls.sort(Comparator.comparingInt(url -> {
try {
String pageStr = url.substring(url.lastIndexOf('/') + 1, url.lastIndexOf('.'));
return Integer.parseInt(pageStr);
} catch (Exception e) {
return 0;
}
}));
return imageUrls;
}
/**
* 计算超时时间
*/
private int calculateTimeout(int pageCount) {
if (pageCount <= 50) {
return ConfigConstants.getPdfTimeout();
} else if (pageCount <= 200) {
return ConfigConstants.getPdfTimeout80();
} else {
return ConfigConstants.getPdfTimeout200();
}
}
/**
* 监控线程池状态
*/
public void monitorThreadPoolStatus() {
if (pdfThreadPoolExecutor != null) {
logger.info("PDF线程池状态: 活跃线程={}, 队列大小={}, 完成任务={}, 线程总数={}",
pdfThreadPoolExecutor.getActiveCount(),
pdfThreadPoolExecutor.getQueue().size(),
pdfThreadPoolExecutor.getCompletedTaskCount(),
pdfThreadPoolExecutor.getPoolSize());
}
}
}

View File

@@ -0,0 +1,426 @@
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();
}
}
}
}