Java 实现 Excel 转 PDF 预览的全面指南

一、需求背景与技术挑战

在企业级应用(如报表系统、数据导出模块)中,经常需要将 Excel 文件转换为 PDF 格式以便于跨平台预览、打印或归档。Java 生态中有多种处理 Excel 和 PDF 的库,但完美保持 Excel 样式、图表及复杂布局到 PDF 并非易事。

二、主流技术方案对比

库名称处理 Excel生成 PDF特点
Apache POI + iTextPOI 读写 ExceliText 生成 PDF社区活跃,功能强大,但 iText 商业使用需注意许可
Apache POI + PDFBoxPOI 读写 ExcelPDFBox 生成 PDF完全开源(Apache License),轻量级
Spire.XLS(商业库)一体化转换一体化转换开箱即用,样式保真度高,但需付费
JExcelApi + iText读写 .xlsiText 生成仅支持旧版 .xls 格式,不推荐

对于大多数项目,推荐使用 Apache POI(处理 Excel)+ iText 或 PDFBox(生成 PDF) 的组合。

三、以 Apache POI + iText 为例的实现

1. 依赖配置(Maven)

<!-- Apache POI -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>
<!-- iText -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext7-core</artifactId>
    <version>7.2.5</version>
    <type>pom</type>
</dependency>

2. 核心转换代码示例

import org.apache.poi.ss.usermodel.*;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Table;

public class ExcelToPdfConverter {
    public void convert(String excelPath, String pdfPath) throws Exception {
        // 1. 读取 Excel
        Workbook workbook = WorkbookFactory.create(new File(excelPath));
        Sheet sheet = workbook.getSheetAt(0);

        // 2. 创建 PDF
        PdfWriter writer = new PdfWriter(pdfPath);
        PdfDocument pdfDoc = new PdfDocument(writer);
        Document document = new Document(pdfDoc);

        // 3. 将 Excel 表格转换为 iText Table
        int columnCount = sheet.getRow(0).getPhysicalNumberOfCells();
        Table table = new Table(columnCount);

        for (Row row : sheet) {
            for (Cell cell : row) {
                // 提取单元格值(注意处理公式、样式等)
                String cellValue = getCellValue(cell);
                table.addCell(cellValue);
            }
        }

        // 4. 将表格添加到 PDF 文档
        document.add(table);
        document.close();
        workbook.close();
    }

    private String getCellValue(Cell cell) {
        // 简单处理:仅提取字符串值,实际需处理数字、日期、公式等
        if (cell.getCellType() == CellType.STRING) {
            return cell.getStringCellValue();
        } else if (cell.getCellType() == CellType.NUMERIC) {
            return String.valueOf(cell.getNumericCellValue());
        }
        return "";
    }
}

3. 代码优化要点

  • 样式处理:在遍历单元格时,需读取 Cell 的字体、背景色、边框等样式,并转换为 iText 的样式(如字体颜色、背景、边框样式)。
  • 合并单元格:需要解析 Excel 中的合并区域(MergedRegion),并在 PDF Table 中使用 table.addCell(new Cell(rowSpan, colSpan).add(...)) 实现跨行跨列。
  • 分页处理:当 Excel 行数很多时,需要考虑 PDF 分页,可以通过设置 iText 的 Document 布局策略或分段添加表格实现。
  • 公式与图表:POI 可以计算公式(需调用 workbook.evaluateAllFormulaCells()),但图表转换较为复杂,通常需要将图表渲染为图片再插入 PDF。

四、实现预览功能

在 Web 应用中,预览通常通过以下方式实现:

  1. 后端生成 PDF 流:转换后直接将 PDF 输出到 HttpServletResponse,前端使用 PDF.js 等库渲染。
  2. 生成临时文件并返回 URL:适用于需要缓存或多次预览的场景。
  3. 使用现成转换服务:如 LibreOffice 的命令行转换(soffice --headless --convert-to pdf input.xlsx),但需服务器安装 LibreOffice。
// 示例:在 Spring Controller 中生成 PDF 流
@GetMapping("/previewExcel")
public void previewExcel(@RequestParam String fileId, HttpServletResponse response) throws Exception {
    // 根据 fileId 获取 Excel 文件路径
    String excelPath = fileService.getFilePath(fileId);
    
    // 设置响应头
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition", "inline; filename=preview.pdf");
    
    // 转换并输出到 OutputStream
    ExcelToPdfConverter converter = new ExcelToPdfConverter();
    converter.convert(excelPath, response.getOutputStream());
}

五、常见问题与最佳实践

1. 中文乱码问题

iText 默认不包含中文字体,需要嵌入支持中文的字体文件(如思源黑体),否则中文会显示为空白或乱码。

2. 性能优化

  • 对于大型 Excel,使用 StreamingReader(POI)流式读取以减少内存占用。
  • 避免在循环中频繁创建对象,复用样式对象。

3. 格式保真度

追求完美格式可考虑商业库(如 Spire.XLS),或使用 LibreOffice 作为后端转换服务,其格式支持最全面。

六、总结

Java 中实现 Excel 转 PDF 预览,需要综合考虑功能需求、格式保真度、性能和成本。对于大多数项目,Apache POI 结合 iText/PDFBox 是一个平衡且灵活的选择。开发中应重点处理好样式转换、合并单元格及大文件场景,并通过流式响应实现高效的 Web 预览功能。