SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出

'# SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出

一、背景与问题

在企业级应用开发中,常常需要将用户界面数据导出为PDF格式。例如:订单导出、报表生成、文档打印等场景。传统方案通常采用以下模式:

  1. 使用iText直接操作PDF,需要手动处理布局和样式
  2. 使用第三方服务如wkhtmltopdf,但存在跨平台兼容性问题
  3. 直接渲染HTML到PDF,需要处理复杂的CSS兼容性问题

本方案采用Thymeleaf模板引擎+Flying Saucer+iText5的组合,通过以下优势解决上述问题:

  • 保持HTML样式和布局的完整性
  • 兼容现代CSS3特性
  • 支持复杂表格和分页处理
  • 与SpringBoot生态无缝集成

二、基本原理

1. 技术栈工作原理

Thymeleaf:作为模板引擎,负责将动态数据渲染为完整的HTML内容。其核心特性包括:

  • 双向数据绑定
  • 自动转义处理
  • 高性能的模板编译机制

Flying Saucer:基于iText的HTML转PDF引擎,其核心流程如下:

  1. 解析HTML内容
  2. 将CSS样式转换为PDF布局指令
  3. 使用iText5进行PDF渲染
  4. 处理分页、字体、图片等复杂要素

iText5:PDF生成库,提供丰富的PDF操作功能,但需要注意其已停止维护的现状。

2. 关键技术点

  • PDF布局引擎:Flying Saucer使用iText的布局引擎,支持CSS3选择器
  • 字体处理:需要注册自定义字体,支持中文字体
  • 分页机制:自动处理页面分割,支持页眉页脚
  • 内存管理:处理大量数据时需要优化内存使用

三、环境准备

1. 依赖配置(SpringBoot 2.7+)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-core</artifactId>
    <version>1.4.1</version>
</dependency>
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.13.2</version>
</dependency>

2. 中文字体配置

需要添加中文字体文件(如SimSun.ttf),并注册到iText:

public static void registerFonts() {
    BaseFont baseFont = BaseFont.createFont(
        "src/main/resources/fonts/SimSun.ttf", 
        BaseFont.IDENTITY_H, 
        BaseFont.EMBEDDED
    );
    
    Font font = new Font(baseFont, 12, Font.NORMAL);
    FontFactory.registerFont(font);
}

四、核心实现

1. HTML模板设计(orders.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>订单导出</title>
    <style>
        body { font-family: SimSun; }
        table { width: 100%; border-collapse: collapse; }
        th, td { border: 1px solid #000; padding: 8px; }
    </style>
</head>
<body>
    <h1>订单列表</h1>
    <table>
        <tr>
            <th>订单号</th>
            <th>客户</th>
            <th>金额</th>
        </tr>
        <tr th:each="order : ${orders}">
            <td th:text="${order.id}">123</td>
            <td th:text="${order.customer}">张三</td>
            <td th:text="${order.amount}">100.00</td>
        </tr>
    </table>
</body>
</html>

2. PDF生成服务实现

@Service
public class PdfService {

    private static final Logger logger = LoggerFactory.getLogger(PdfService.class);

    public byte[] generatePdf(String htmlContent) {
        try {
            // 创建PDF文档
            Document document = new Document();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            PdfWriter.getInstance(document, outputStream);
            document.open();
            
            // 创建PDF转换器
            ITextRenderer renderer = new ITextRenderer();
            renderer.setDocumentFromString(htmlContent);
            renderer.layout();
            
            // 设置PDF页面大小
            document.setPageSize(renderer.getOutputSize());
            document.setMargins(50, 50, 50, 50);
            
            // 渲染PDF
            renderer.render(document);
            
            // 保存PDF
            document.close();
            return outputStream.toByteArray();
        } catch (Exception e) {
            logger.error("PDF生成失败", e);
            throw new RuntimeException("PDF生成失败", e);
        }
    }
}

3. 控制器接口实现

@RestController
@RequestMapping("/pdf")
public class PdfController {

    @Autowired
    private PdfService pdfService;

    @GetMapping("/orders")
    public ResponseEntity<byte[]> exportOrders(@RequestParam String orderId) {
        // 构建HTML内容(实际应从数据库获取数据)
        String htmlContent = "<html><body><h1>订单详情</h1><p>订单号: " + orderId + "</p></body></html>";
        
        byte[] pdfBytes = pdfService.generatePdf(htmlContent);
        
        return ResponseEntity.ok()
                .header("Content-Type", "application/pdf")
                .header("Content-Disposition", "attachment; filename=orders.pdf")
                .body(pdfBytes);
    }
}

五、完整案例

1. 项目结构

src
├── main
│   ├── java
│   │   └── com.example.demo
│   │       ├── controller
│   │       ├── service
│   │       └── PdfApplication.java
│   └── resources
│       ├── templates
│       │   └── orders.html
│       └── fonts
│           └── SimSun.ttf
│
└── test

2. 实际运行流程

  1. 用户访问 /pdf/orders?orderId=123 接口
  2. 控制器获取HTML模板内容
  3. 使用Thymeleaf渲染动态数据
  4. 调用PDF服务生成PDF
  5. 返回PDF文件流给客户端

3. 关键代码解释

Thymeleaf模板渲染

String htmlContent = TemplateEngineUtils.renderTemplate(
    "orders.html", 
    Map.of("orders", orders)
);

PDF生成流程

// 设置PDF页面尺寸
document.setPageSize(renderer.getOutputSize());

// 渲染PDF
renderer.render(document);

字体注册

FontFactory.registerFont(FontFactory.getFont("SimSun", BaseFont.IDENTITY_H, BaseFont.EMBEDDED));

六、源码解析

1. Flying Saucer源码关键点

  • ITextRenderer类:核心处理类,负责HTML解析和PDF渲染
  • Layout类:处理页面布局和分页
  • CSSResolver类:CSS样式解析和转换
public class ITextRenderer {
    public void setDocumentFromString(String html) {
        // 解析HTML内容
        Document document = new Document();
        document.add(new Paragraph(html));
        // 其他处理逻辑
    }
}

2. iText5源码关键点

  • Document类:PDF文档的容器
  • PdfWriter类:将内容写入PDF
  • BaseFont类:字体处理核心
public class Document {
    public void setPageSize(Rectangle pageSize) {
        // 设置页面尺寸
    }
    
    public void setMargins(float left, float right, float top, float bottom) {
        // 设置页边距
    }
}

七、进阶使用

1. 复杂表格处理

<table>
    <tr>
        <th>序号</th>
        <th>产品</th>
        <th>单价</th>
        <th>数量</th>
    </tr>
    <tr th:each="item : ${items}">
        <td th:text="${item.index}">1</td>
        <td th:text="${item.product}">商品A</td>
        <td th:text="${item.price}">100.00</td>
        <td th:text="${item.quantity}">2</td>
    </tr>
</table>

2. 自定义样式处理

<style>
    .highlight {
        background-color: #FFD700;
        font-weight: bold;
    }
</style>
<div class="highlight">特殊标注内容</div>

3. 嵌入图片处理

<img src="/images/logo.png" alt="公司logo" width="100">

八、性能与工程实践

1. 性能优化策略

优化措施说明
流式处理使用ByteArrayOutputStream避免大内存占用
字体缓存预注册常用字体避免重复加载
并行处理使用线程池处理并发请求
压缩输出使用PDF压缩算法优化文件体积

2. 异常处理机制

try {
    pdfService.generatePdf(htmlContent);
} catch (DocumentException e) {
    logger.error("PDF生成异常", e);
    throw new CustomException("PDF生成失败,请重试");
}

3. 安全防护措施

  • 对用户输入内容进行XSS过滤
  • 限制PDF生成的页面数量
  • 设置PDF文件大小上限
  • 使用安全的字体处理机制

九、常见问题与踩坑

1. 常见错误及解决办法

错误现象原因分析解决方案
PDF显示乱码字体未正确注册确认字体路径和注册方式
页面未分页布局未正确设置调用document.setPageSize()
CSS样式丢失CSS解析器未启用配置CSSResolver
内存溢出大文件处理使用流式处理
依赖冲突版本不兼容检查依赖版本

2. 典型问题示例

错误代码

Document document = new Document();
document.open();
document.add(new Paragraph(htmlContent));
document.close();

错误原因:直接使用Document的add方法无法正确解析HTML内容。

正确做法

ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(htmlContent);
renderer.layout();
renderer.render(document);

十、最佳实践

1. 推荐方案

  • 使用SpringBoot 2.7+版本
  • 使用iText5 5.5.13.2版本
  • 使用Flying Saucer 1.4.1版本
  • 预注册常用字体
  • 使用线程池处理并发请求
  • 实现PDF大小限制

2. 实施建议

  • 对敏感数据进行脱敏处理
  • 对生成的PDF进行校验
  • 记录PDF生成日志
  • 实现PDF文件的自动清理机制

十一、总结

本方案通过Thymeleaf模板引擎与Flying Saucer/iText5的结合,实现了企业级PDF生成需求。其核心优势在于:

  • 保持HTML样式完整性
  • 支持复杂布局和分页
  • 与SpringBoot生态无缝集成
  • 兼容现代CSS特性

适用场景包括:

✅ 订单导出
✅ 报表生成
✅ 文档打印
✅ 系统操作日志导出

不适用场景包括:

❌ 需要实时生成的场景
❌ 高并发的PDF生成
❌ 需要支持PDF/A标准的场景
❌ 需要处理大量图像的场景

在实际开发中,建议根据业务需求选择合适的PDF生成方案。对于需要复杂格式的场景,推荐使用本方案;对于简单需求,可以考虑更轻量的方案。同时,要注意版本兼容性和安全防护,确保系统稳定运行。

最后修改于:2026年09月16日 11:08

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日