SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出
'# SpringBoot Thymeleaf企业级真实应用:使用Flying Saucer结合iText5将HTML界面数据转换为PDF输出
一、背景与问题
在企业级应用开发中,常常需要将用户界面数据导出为PDF格式。例如:订单导出、报表生成、文档打印等场景。传统方案通常采用以下模式:
- 使用iText直接操作PDF,需要手动处理布局和样式
- 使用第三方服务如wkhtmltopdf,但存在跨平台兼容性问题
- 直接渲染HTML到PDF,需要处理复杂的CSS兼容性问题
本方案采用Thymeleaf模板引擎+Flying Saucer+iText5的组合,通过以下优势解决上述问题:
- 保持HTML样式和布局的完整性
- 兼容现代CSS3特性
- 支持复杂表格和分页处理
- 与SpringBoot生态无缝集成
二、基本原理
1. 技术栈工作原理
Thymeleaf:作为模板引擎,负责将动态数据渲染为完整的HTML内容。其核心特性包括:
- 双向数据绑定
- 自动转义处理
- 高性能的模板编译机制
Flying Saucer:基于iText的HTML转PDF引擎,其核心流程如下:
- 解析HTML内容
- 将CSS样式转换为PDF布局指令
- 使用iText5进行PDF渲染
- 处理分页、字体、图片等复杂要素
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
│
└── test2. 实际运行流程
- 用户访问
/pdf/orders?orderId=123接口 - 控制器获取HTML模板内容
- 使用Thymeleaf渲染动态数据
- 调用PDF服务生成PDF
- 返回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生成方案。对于需要复杂格式的场景,推荐使用本方案;对于简单需求,可以考虑更轻量的方案。同时,要注意版本兼容性和安全防护,确保系统稳定运行。
评论已关闭