别再手动填表了!用Java和iTextPDF 5.5.1自动生成带中文的结算单PDF(附完整源码)
·
电商订单结算单自动化:基于Java与iTextPDF的PDF动态生成实战
财务人员每天重复填写数十份结算单的时代该终结了。在电商订单量激增的背景下,我们为Java工程师准备了一套 开箱即用 的PDF动态生成方案。不同于网上零散的代码片段,本文将系统解决中文字体渲染、动态表格构建、数据绑定等核心痛点,并提供可直接集成到Spring Boot项目的模块化代码。
1. 环境准备与依赖配置
工欲善其事,必先利其器。我们选择iTextPDF 5.5.1版本作为核心库,这是经过大量生产验证的稳定版本。新建Maven项目时,需在pom.xml中添加以下关键依赖:
<dependencies>
<!-- 核心PDF生成库 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.1</version>
</dependency>
<!-- 亚洲字体支持 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<!-- 可选JSON处理 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
注意:实际项目中建议使用dependencyManagement统一管理版本号
开发环境建议配置:
- JDK 8+(推荐JDK 11 LTS版本)
- IntelliJ IDEA或Eclipse最新稳定版
- 测试用中文模板文件(用于验证字体显示)
2. 中文字体解决方案深度剖析
中文乱码是PDF生成的经典难题。我们采用 STSong-Light 字体配合UniGB-UCS2-H编码方案,这是目前最稳定的中文显示方案之一。核心字体初始化代码如下:
// 创建中文字体基础对象
BaseFont chineseFont = BaseFont.createFont(
"STSong-Light",
"UniGB-UCS2-H",
BaseFont.NOT_EMBEDDED
);
// 派生不同样式字体
Font titleFont = new Font(chineseFont, 18, Font.BOLD);
Font contentFont = new Font(chineseFont, 12, Font.NORMAL);
Font highlightFont = new Font(chineseFont, 12, Font.BOLD, BaseColor.RED);
常见字体问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 方块字 | 字体未正确加载 | 检查itext-asian依赖和字体名称 |
| 部分字符缺失 | 编码不匹配 | 确认使用UniGB-UCS2-H编码 |
| 文件体积过大 | 字体全嵌入 | 设置BaseFont.NOT_EMBEDDED |
生产环境建议:将字体文件放入resources/fonts目录,使用createFont加载物理文件而非系统字体
3. 动态表格生成引擎设计
电商结算单通常包含表头、商品明细和汇总三部分。我们设计了一个灵活的表格构建器:
public class DynamicTableBuilder {
private PdfPTable table;
private Font defaultFont;
public DynamicTableBuilder(int columns, Font font) {
this.table = new PdfPTable(columns);
this.defaultFont = font;
table.setWidthPercentage(100);
}
public void addHeaderRow(List<String> headers) {
headers.forEach(header -> {
PdfPCell cell = new PdfPCell(new Phrase(header, defaultFont));
cell.setBackgroundColor(new BaseColor(240, 240, 240));
table.addCell(cell);
});
}
public void addDataRow(List<Object> rowData) {
rowData.forEach(data -> {
String content = data != null ? data.toString() : "";
table.addCell(new Phrase(content, defaultFont));
});
}
public void addToDocument(Document document) throws DocumentException {
document.add(table);
}
}
典型电商订单表格结构示例:
-
表头区域
- 订单编号
- 创建时间
- 客户信息
-
商品明细
- SKU编码
- 商品名称
- 单价
- 数量
- 小计
-
汇总区域
- 商品总数
- 总金额
- 优惠金额
- 实付金额
4. Spring Boot集成实战
将PDF生成能力封装为微服务是更优雅的方案。创建PdfExportService核心类:
@Service
public class PdfExportService {
@Value("${pdf.export.path:/tmp/pdf}")
private String exportPath;
public File generateOrderStatement(OrderDTO order) {
String filename = exportPath + "/order_" + order.getId() + ".pdf";
try {
Document document = new Document(PageSize.A4, 50, 50, 30, 30);
PdfWriter.getInstance(document, new FileOutputStream(filename));
document.open();
addTitle(document, "订单结算单");
addOrderInfo(document, order);
addItemsTable(document, order.getItems());
addSummary(document, order.getSummary());
document.close();
return new File(filename);
} catch (Exception e) {
throw new PdfGenerationException("PDF生成失败", e);
}
}
private void addTitle(Document doc, String title) throws DocumentException {
Font font = ChineseFontUtil.getTitleFont();
Paragraph p = new Paragraph(title, font);
p.setAlignment(Element.ALIGN_CENTER);
doc.add(p);
doc.add(Chunk.NEWLINE);
}
// 其他私有方法省略...
}
控制器层提供REST接口:
@RestController
@RequestMapping("/api/pdf")
public class PdfController {
@Autowired
private PdfExportService pdfService;
@PostMapping("/order")
public ResponseEntity<Resource> generateOrderPdf(@RequestBody OrderDTO order)
throws IOException {
File pdfFile = pdfService.generateOrderStatement(order);
Path path = Paths.get(pdfFile.getAbsolutePath());
ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=order_" + order.getId() + ".pdf")
.contentType(MediaType.APPLICATION_PDF)
.contentLength(pdfFile.length())
.body(resource);
}
}
5. 高级优化技巧
性能优化方案:
- 对象池复用Document和PdfWriter实例
- 异步生成结合缓存机制
- 批量操作时使用PdfCopy合并文档
样式增强技巧:
// 创建渐变背景色
PdfContentByte canvas = writer.getDirectContent();
canvas.setColorFill(new BaseColor(230, 230, 250));
canvas.rectangle(0, 0, PageSize.A4.getWidth(), 80);
canvas.fill();
// 添加水印
PdfGState gs = new PdfGState();
gs.setFillOpacity(0.3f);
canvas.setGState(gs);
canvas.beginText();
canvas.setFontAndSize(bfChinese, 48);
canvas.showTextAligned(Element.ALIGN_CENTER, "CONFIDENTIAL",
300, 400, 45);
canvas.endText();
异常处理建议:
- 对IO操作添加重试机制
- 大文件生成时添加超时控制
- 使用临时文件+原子移动保证操作原子性
6. 完整项目结构参考
标准化的项目布局能提升团队协作效率:
src/main/java
└── com
└── example
└── pdf
├── config
│ └── PdfConfig.java
├── controller
│ └── PdfController.java
├── service
│ ├── PdfExportService.java
│ └── impl
│ └── PdfExportServiceImpl.java
├── util
│ ├── ChineseFontUtil.java
│ └── DynamicTableBuilder.java
└── exception
└── PdfGenerationException.java
src/main/resources
├── fonts
│ └── simsun.ttc
└── templates
└── invoice_template.pdf
在电商项目实际落地时,我们发现将结算单生成时机放在"订单已完成"事件触发时最合理,配合消息队列实现异步生成,系统吞吐量提升了3倍。对于高并发场景,建议采用PDF预生成策略,将结算单模板与动态数据分离处理。
更多推荐


所有评论(0)