SpringBoot 实现 PDF 导出解决方案
导语
PDF 导出是企业应用中常见的功能需求,如生成报表、合同、发票、证书等。SpringBoot 作为主流的 Java 后端框架,提供了多种实现 PDF 导出的方案。本文将深入探讨 SpringBoot 中实现 PDF 导出的各种方法,包括技术选型、实现细节、性能优化和最佳实践。
一、PDF 导出技术选型
1.1 主流 PDF 库比较
| 库名称 | 许可证 | 特点 | 适用场景 |
|---|---|---|---|
| iText 7 | AGPL/商业 | 功能强大,支持复杂文档 | 企业级应用,复杂报表 |
| OpenPDF | LGPL | iText 5 的开源分支 | 中小型应用,简单报表 |
| Apache PDFBox | Apache 2.0 | 功能丰富,支持 PDF 操作 | PDF 解析和生成 |
| Flying Saucer | LGPL | 基于 XHTML/CSS 生成 PDF | 基于模板的文档 |
| JasperReports | LGPL | 强大的报表引擎 | 复杂报表,数据可视化 |
1.2 技术选型建议
选择因素:
- 功能需求:是否需要复杂布局、表单、图表等
- 许可证:是否需要商业使用
- 性能要求:处理大量数据的效率
- 学习曲线:开发和维护成本
- 社区支持:文档和资源的丰富程度
推荐方案:
- 小型应用:OpenPDF 或 Flying Saucer
- 中型应用:iText 7 社区版
- 大型企业应用:iText 7 商业版或 JasperReports
二、基于 iText 7 的实现方案
2.1 核心依赖
<dependencies>
<!-- iText 7 核心 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version>
</dependency>
<!-- 中文字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>font-asian</artifactId>
<version>7.2.5</version>
</dependency>
<!-- 条形码支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>barcodes</artifactId>
<version>7.2.5</version>
</dependency>
</dependencies>
2.2 基础 PDF 生成
PdfGenerator.java
@Service
public class PdfGenerator {
/**
* 生成基础 PDF 文档
*/
public byte[] generateBasicPdf(String title, String content) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 创建 PDF 文档
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outputStream));
Document document = new Document(pdfDoc);
// 设置中文字体
PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
document.setFont(font);
// 添加标题
Paragraph header = new Paragraph(title)
.setFontSize(18)
.setBold()
.setAlignment(TextAlignment.CENTER);
document.add(header);
// 添加内容
Paragraph body = new Paragraph(content)
.setFontSize(12)
.setLeading(20)
.setFirstLineIndent(20);
document.add(body);
// 关闭文档
document.close();
return outputStream.toByteArray();
}
}
2.3 复杂 PDF 生成
AdvancedPdfGenerator.java
@Service
public class AdvancedPdfGenerator {
/**
* 生成带表格的 PDF
*/
public byte[] generatePdfWithTable(List<Order> orders) throws Exception {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 创建 PDF 文档
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(outputStream));
Document document = new Document(pdfDoc);
// 设置中文字体
PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
document.setFont(font);
// 添加标题
document.add(new Paragraph("订单报表")
.setFontSize(16)
.setBold()
.setAlignment(TextAlignment.CENTER)
.setMarginBottom(20));
// 创建表格
Table table = new Table(5);
table.setWidth(UnitValue.createPercentValue(100));
// 添加表头
table.addHeaderCell(new Cell().add(new Paragraph("订单号").setBold()));
table.addHeaderCell(new Cell().add(new Paragraph("客户名称").setBold()));
table.addHeaderCell(new Cell().add(new Paragraph("订单金额").setBold()));
table.addHeaderCell(new Cell().add(new Paragraph("下单时间").setBold()));
table.addHeaderCell(new Cell().add(new Paragraph("状态").setBold()));
// 添加数据行
for (Order order : orders) {
table.addCell(order.getOrderId());
table.addCell(order.getCustomerName());
table.addCell(String.format("¥%.2f", order.getAmount()));
table.addCell(order.getOrderTime().toString());
table.addCell(order.getStatus());
}
// 添加表格到文档
document.add(table);
// 添加页脚
document.add(new Paragraph("生成时间: " + new Date())
.setFontSize(10)
.setAlignment(TextAlignment.RIGHT)
.setMarginTop(30));
// 关闭文档
document.close();
return outputStream.toByteArray();
}
}
三、基于模板的 PDF 导出
3.1 使用 Flying Saucer
依赖配置:
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-itext5</artifactId>
<version>9.1.22</version>
</dependency>
实现代码:
@Service
public class TemplatePdfGenerator {
/**
* 基于 HTML 模板生成 PDF
*/
public byte[] generatePdfFromTemplate(String htmlTemplate, Map<String, Object> data) throws Exception {
// 填充模板数据
String filledHtml = fillTemplate(htmlTemplate, data);
// 创建 PDF
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ITextRenderer renderer = new ITextRenderer();
// 注册中文字体
renderer.getFontResolver().addFont("path/to/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
// 设置 HTML 内容
renderer.setDocumentFromString(filledHtml);
renderer.layout();
renderer.createPDF(outputStream);
return outputStream.toByteArray();
}
/**
* 填充模板数据
*/
private String fillTemplate(String template, Map<String, Object> data) {
// 使用 Freemarker 或 Thymeleaf 填充模板
// 这里简化处理,实际项目中应使用模板引擎
String result = template;
for (Map.Entry<String, Object> entry : data.entrySet()) {
result = result.replace("${" + entry.getKey() + "}", entry.getValue().toString());
}
return result;
}
}
3.2 HTML 模板示例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body {
font-family: SimSun;
font-size: 12px;
}
.header {
text-align: center;
margin-bottom: 20px;
}
.title {
font-size: 16px;
font-weight: bold;
}
.table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.table th, .table td {
border: 1px solid #000;
padding: 8px;
text-align: center;
}
.footer {
margin-top: 30px;
text-align: right;
font-size: 10px;
}
</style>
</head>
<body>
<div class="header">
<div class="title">${title}</div>
<div>${subtitle}</div>
</div>
<table class="table">
<tr>
<th>姓名</th>
<th>部门</th>
<th>职位</th>
<th>入职时间</th>
</tr>
<tr>
<td>${name}</td>
<td>${department}</td>
<td>${position}</td>
<td>${hireDate}</td>
</tr>
</table>
<div class="footer">
生成时间: ${generateTime}
</div>
</body>
</html>
四、SpringBoot 集成实现
4.1 控制器实现
PdfController.java
@RestController
@RequestMapping("/api/pdf")
public class PdfController {
@Autowired
private PdfGenerator pdfGenerator;
@Autowired
private AdvancedPdfGenerator advancedPdfGenerator;
@Autowired
private TemplatePdfGenerator templatePdfGenerator;
/**
* 生成基础 PDF
*/
@GetMapping("/basic")
public ResponseEntity<byte[]> generateBasicPdf() {
try {
byte[] pdfBytes = pdfGenerator.generateBasicPdf(
"测试文档",
"这是一个使用 SpringBoot 和 iText 生成的 PDF 文档示例。\n\n" +
"PDF 导出是企业应用中常见的功能需求,如生成报表、合同、发票等。\n\n" +
"本文档展示了基础的 PDF 生成功能。"
);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=basic.pdf")
.body(pdfBytes);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 生成带表格的 PDF
*/
@GetMapping("/table")
public ResponseEntity<byte[]> generatePdfWithTable() {
try {
// 模拟订单数据
List<Order> orders = generateMockOrders();
byte[] pdfBytes = advancedPdfGenerator.generatePdfWithTable(orders);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=orders.pdf")
.body(pdfBytes);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
/**
* 基于模板生成 PDF
*/
@GetMapping("/template")
public ResponseEntity<byte[]> generatePdfFromTemplate() {
try {
// 读取 HTML 模板
String htmlTemplate = readTemplateFile("templates/employee.html");
// 准备数据
Map<String, Object> data = new HashMap<>();
data.put("title", "员工信息表");
data.put("subtitle", "人力资源部");
data.put("name", "张三");
data.put("department", "技术部");
data.put("position", "高级工程师");
data.put("hireDate", "2024-01-01");
data.put("generateTime", new Date().toString());
byte[] pdfBytes = templatePdfGenerator.generatePdfFromTemplate(htmlTemplate, data);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=employee.pdf")
.body(pdfBytes);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
private List<Order> generateMockOrders() {
List<Order> orders = new ArrayList<>();
// 模拟数据...
return orders;
}
private String readTemplateFile(String path) throws IOException {
// 读取模板文件...
return "";
}
}
4.2 异步 PDF 生成
AsyncPdfService.java
@Service
public class AsyncPdfService {
@Autowired
private AdvancedPdfGenerator pdfGenerator;
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 异步生成 PDF
*/
@Async
public CompletableFuture<String> generatePdfAsync(List<Order> orders, String taskId) {
try {
// 生成 PDF
byte[] pdfBytes = pdfGenerator.generatePdfWithTable(orders);
// 存储 PDF 到文件系统或对象存储
String filePath = savePdfFile(pdfBytes, taskId);
// 更新任务状态
redisTemplate.opsForHash().put(taskId, "status", "COMPLETED");
redisTemplate.opsForHash().put(taskId, "filePath", filePath);
return CompletableFuture.completedFuture(filePath);
} catch (Exception e) {
// 更新任务状态为失败
redisTemplate.opsForHash().put(taskId, "status", "FAILED");
redisTemplate.opsForHash().put(taskId, "error", e.getMessage());
return CompletableFuture.failedFuture(e);
}
}
private String savePdfFile(byte[] pdfBytes, String taskId) throws IOException {
// 保存文件到指定位置
String directory = "./pdfs/" + LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
File dir = new File(directory);
if (!dir.exists()) {
dir.mkdirs();
}
String fileName = taskId + ".pdf";
String filePath = directory + "/" + fileName;
try (FileOutputStream fos = new FileOutputStream(filePath)) {
fos.write(pdfBytes);
}
return filePath;
}
}
五、性能优化与最佳实践
5.1 性能优化策略
1. 内存管理
- 使用
ByteArrayOutputStream避免频繁 I/O - 及时关闭资源,使用 try-with-resources
- 对于大文件,考虑流式处理
2. 线程处理
- 对于大文件生成,使用异步处理
- 配置合理的线程池大小
- 避免阻塞主线程
3. 缓存策略
- 缓存字体和模板
- 对于重复生成的内容,使用缓存
- 考虑使用 Redis 缓存中间结果
4. 资源优化
- 优化图片大小和质量
- 减少 PDF 中的冗余内容
- 使用适当的压缩级别
5.2 错误处理与监控
1. 异常处理
- 捕获并记录 PDF 生成过程中的异常
- 提供友好的错误信息给客户端
- 实现重试机制
2. 监控指标
- 记录 PDF 生成时间
- 监控内存使用情况
- 跟踪生成失败率
3. 日志记录
- 记录关键操作日志
- 实现请求追踪
- 保存生成历史记录
5.3 安全考虑
1. 输入验证
- 验证用户输入,防止注入攻击
- 限制文件大小和生成频率
- 防止恶意模板注入
2. 权限控制
- 限制 PDF 生成权限
- 实现访问控制
- 加密敏感 PDF 文档
3. 数据保护
- 处理敏感数据时注意保护
- 实现数据脱敏
- 遵循数据保护法规
六、生产环境部署
6.1 容器化部署
Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/pdf-export-demo-1.0.0.jar app.jar
# 安装中文字体
RUN apt-get update && apt-get install -y fonts-wqy-zenhei
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compose.yml
version: '3.8'
services:
pdf-export:
build: .
ports:
- "8080:8080"
volumes:
- ./pdfs:/app/pdfs
environment:
- SPRING_PROFILES_ACTIVE=prod
- TZ=Asia/Shanghai
6.2 配置优化
application-prod.yml
# 生产环境配置
spring:
application:
name: pdf-export-demo
# 线程池配置
task:
execution:
pool:
core-size: 10
max-size: 50
queue-capacity: 100
# PDF 配置
pdf:
# 字体路径
font-path: /usr/share/fonts/truetype/wqy/wqy-zenhei.ttc
# 生成配置
generate:
timeout: 30000 # 超时时间(毫秒)
max-size: 10485760 # 最大文件大小(10MB)
max-records: 10000 # 最大记录数
# 存储配置
storage:
path: ./pdfs
base-url: http://localhost:8080/pdf
6.3 监控与告警
Prometheus 配置
scrape_configs:
- job_name: 'pdf-export'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['pdf-export:8080']
Grafana 仪表板
- PDF 生成请求数
- 平均生成时间
- 失败率
- 内存使用情况
七、常见问题与解决方案
7.1 中文字体问题
问题:PDF 中中文显示乱码或不显示
解决方案:
- 嵌入中文字体
- 确保字体文件存在
- 正确设置字体编码
代码示例:
// 方法 1:使用 iText 内置字体
PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
// 方法 2:使用系统字体
PdfFont font = PdfFontFactory.createFont("path/to/simsun.ttc", PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
7.2 大文件生成问题
问题:生成大文件时内存溢出或超时
解决方案:
- 使用异步处理
- 分批处理数据
- 优化 PDF 结构
- 增加 JVM 内存
代码示例:
// 分批处理数据
public byte[] generateLargePdf(List<Data> dataList) throws Exception {
// 分批处理
int batchSize = 1000;
List<List<Data>> batches = new ArrayList<>();
for (int i = 0; i < dataList.size(); i += batchSize) {
int end = Math.min(i + batchSize, dataList.size());
batches.add(dataList.subList(i, end));
}
// 生成 PDF
// ...
}
7.3 性能问题
问题:PDF 生成速度慢
解决方案:
- 缓存字体和模板
- 优化数据查询
- 使用更高效的 PDF 库
- 考虑使用预生成策略
代码示例:
// 字体缓存
private static Map<String, PdfFont> fontCache = new ConcurrentHashMap<>();
public PdfFont getFont(String fontPath) throws Exception {
return fontCache.computeIfAbsent(fontPath, path -> {
try {
return PdfFontFactory.createFont(path, PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
八、案例分析
8.1 报表生成案例
需求:生成包含大量数据的销售报表
解决方案:
- 使用 iText 7 生成复杂表格
- 实现异步生成
- 分页处理大数据
- 添加图表和汇总信息
关键代码:
// 分页处理
public void addTableWithPagination(Document document, List<SalesData> data) {
int pageSize = 50;
int totalPages = (data.size() + pageSize - 1) / pageSize;
for (int i = 0; i < totalPages; i++) {
int start = i * pageSize;
int end = Math.min(start + pageSize, data.size());
List<SalesData> pageData = data.subList(start, end);
// 添加表格
addTable(document, pageData);
// 分页
if (i < totalPages - 1) {
document.add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
}
}
8.2 合同生成案例
需求:基于模板生成个性化合同
解决方案:
- 使用 HTML 模板
- 填充动态数据
- 添加电子签名
- 加密 PDF
关键代码:
// 添加电子签名
public void addDigitalSignature(PdfDocument pdfDoc, String signatureFieldName, String signerName) {
// 创建签名外观
Rectangle rect = new Rectangle(36, 748, 200, 100);
PdfSignatureFormField field = PdfFormField.createSignature(pdfDoc, rect, signatureFieldName);
// 添加签名字段
PdfAcroForm form = PdfAcroForm.getAcroForm(pdfDoc, true);
form.addField(field);
// 签名外观
PdfCanvas canvas = new PdfCanvas(pdfDoc.getPage(pdfDoc.getNumberOfPages()));
canvas.rectangle(rect);
canvas.stroke();
// 添加签名信息
Document doc = new Document(pdfDoc);
doc.showTextAligned(new Paragraph("签名: " + signerName),
rect.getLeft() + 10, rect.getBottom() + 10,
pdfDoc.getNumberOfPages(), TextAlignment.LEFT, VerticalAlignment.BOTTOM, 0);
}
九、未来发展趋势
9.1 技术演进
1. 云原生 PDF 服务
- 基于 Serverless 的 PDF 生成服务
- 容器化部署和自动扩缩容
- 与云存储集成
2. AI 辅助 PDF 生成
- 智能内容布局
- 自动数据填充
- 个性化模板生成
3. 交互式 PDF
- 可填写表单
- 数字签名
- 多媒体内容
9.2 工具链发展
1. 低代码 PDF 生成
- 可视化模板设计
- 拖拽式布局
- 自动数据绑定
2. 集成开发工具
- IDE 插件
- 代码生成器
- 测试工具
3. 标准规范
- PDF/A 合规性
- 无障碍支持
- 数字签名标准
十、总结与展望
10.1 核心要点
- 技术选型:根据需求选择合适的 PDF 库
- 实现方案:基础生成、模板生成、异步处理
- 性能优化:内存管理、线程处理、缓存策略
- 生产部署:容器化、配置优化、监控告警
- 最佳实践:错误处理、安全考虑、代码质量
10.2 实施建议
- 评估需求:明确 PDF 复杂度和性能要求
- 技术选型:选择适合的 PDF 库
- 架构设计:考虑异步处理和扩展性
- 测试验证:充分测试各种场景
- 监控运维:建立完善的监控体系
10.3 未来展望
随着业务需求的不断增长,PDF 导出功能将变得更加重要和复杂。未来的发展方向包括:
- 智能化:利用 AI 技术优化 PDF 生成过程
- 云原生:基于云服务的 PDF 生成解决方案
- 标准化:遵循行业标准和最佳实践
- 生态化:与其他系统的深度集成
通过本文介绍的技术方案,您可以在 SpringBoot 应用中实现高效、可靠的 PDF 导出功能,满足各种业务场景的需求。
小结
本文介绍了 SpringBoot 实现 PDF 导出的完整解决方案,包括:
- 技术选型:主流 PDF 库的比较和选择建议
- 核心实现:基于 iText 7 的基础和高级 PDF 生成
- 模板方案:使用 Flying Saucer 基于 HTML 模板生成 PDF
- SpringBoot 集成:控制器实现和异步处理
- 性能优化:内存管理、线程处理和缓存策略
- 生产部署:容器化配置和监控告警
- 常见问题:中文字体、大文件生成和性能优化
- 案例分析:报表生成和合同生成的实际应用
- 未来趋势:云原生、AI 辅助和交互式 PDF
通过这些技术方案,您可以在 SpringBoot 应用中实现高质量的 PDF 导出功能,为用户提供专业、美观的文档体验。
互动话题
- 您在项目中使用过哪些 PDF 生成库?有什么使用心得?
- 您遇到过哪些 PDF 生成的性能问题?是如何解决的?
- 您对本文介绍的技术方案有什么改进建议?
- 您认为未来 PDF 生成技术会向哪些方向发展?
欢迎在评论区分享您的经验和看法!
标题:SpringBoot 实现 PDF 导出解决方案
作者:jiangyi
地址:http://jiangyi.space/articles/2026/03/04/1772516664298.html
公众号:服务端技术精选
- 导语
- 一、PDF 导出技术选型
- 1.1 主流 PDF 库比较
- 1.2 技术选型建议
- 二、基于 iText 7 的实现方案
- 2.1 核心依赖
- 2.2 基础 PDF 生成
- 2.3 复杂 PDF 生成
- 三、基于模板的 PDF 导出
- 3.1 使用 Flying Saucer
- 3.2 HTML 模板示例
- 四、SpringBoot 集成实现
- 4.1 控制器实现
- 4.2 异步 PDF 生成
- 五、性能优化与最佳实践
- 5.1 性能优化策略
- 5.2 错误处理与监控
- 5.3 安全考虑
- 六、生产环境部署
- 6.1 容器化部署
- 6.2 配置优化
- 6.3 监控与告警
- 七、常见问题与解决方案
- 7.1 中文字体问题
- 7.2 大文件生成问题
- 7.3 性能问题
- 八、案例分析
- 8.1 报表生成案例
- 8.2 合同生成案例
- 九、未来发展趋势
- 9.1 技术演进
- 9.2 工具链发展
- 十、总结与展望
- 10.1 核心要点
- 10.2 实施建议
- 10.3 未来展望
- 小结
- 互动话题
评论