
1. 简介
在企业级应用中,PDF生成是常见的文档处理需求。无论是订单确认、发票开具、合同签署,还是报表导出,用户普遍期望获得格式规范、内容完整且可打印的PDF文件作为正式凭证或存档依据。相比HTML或Excel,PDF具有跨平台一致性、防篡改性强、布局固定等优势,尤其适用于金融、电商、政务等对文档合规性要求较高的场景。
本篇文章将通过通过itextpdf + html模板生成pdf文档(支持外部资源)。
2.实战案例
2.1 环境准备
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>kernel</artifactId>
<version>9.3.0</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>6.2.1</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.4.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
我们将通过thymeleaf模板技术生成PDF。thymeleaf支持各种强劲的表达式,超级适合生成各种复杂的文档。
配置thymeleaf
spring:
thymeleaf:
mode: HTML
encoding: UTF-8
prefix: classpath:/templates/
suffix: .html
cache: false
2.2 准备数据 & 模板
public class Receipt {
// 收单机构信息
private String scope;
// 商户信息
private String merchantName;
private String merchantId;
private String terminalId;
private String merchantCity;
// 交易基本信息
private String stan; // 系统跟踪号
private String transactionDate; // 交易日期
private String transactionType; // 交易类型
private BigDecimal requestAmount; // 交易金额
// 交易详情
private String mcc; // 商户类别码
private String scheme; // 卡组织
private String maskedPan; // 掩码卡号
private String acquirer; // 收单机构BIN
// 系统信息
private String approvalNumber; // 授权号
private String processingCode; // 处理代码
private String responseCode; // 响应码
private String retrievalNumber; // 检索参考号
private String displayMessage; // 交易状态信息
// getters, setters
}
模板定义
<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>收据副本</title>
<link href="/google.css" rel='stylesheet' type='text/css' />
<style>
html, body {
font-family: "Microsoft YaHei", sans-serif;;
}
.wrapper {
width: 440px;
height: 1024px;
}
.bolded {
font-weight: bolder;
}
.content {
width: 45vh;
padding: 20px 40px;
}
.section {
border-bottom: 2px dashed black;
padding: 20px 0px;
}
.section-item {
margin-bottom: 20px;
overflow-wrap: break-word;
}
.section-item:last-child {
margin-bottom: 0px;
}
small:last-child {
text-align: right;
float: right;
}
</style>
</head>
<body>
<div class="wrapper">
<div class="content">
<h1 style="text-align: center; border-bottom: 2px dashed black; padding-bottom: 20px;">** 收据副本 **<img src="/seo.png" width="32" height="32"/></h1>
<div class="section">
<div class="section-item">
<small>收单机构名称:</small>
<small style="word-wrap: anywhere" th:text="${receipt.scope}"></small>
</div>
<div class="section-item">
<small>商户名称:</small>
<small style="word-wrap: anywhere" th:text="${receipt.merchantName}"></small>
</div>
<div class="section-item">
<small>商户ID:</small>
<small th:text="${receipt.merchantId}"></small>
</div>
<div class="section-item">
<small>终端ID:</small>
<small th:text="${receipt.terminalId}"></small>
</div>
<div class="section-item">
<small>商户城市:</small>
<small th:text="${receipt.merchantCity}"></small>
</div>
</div>
<div class="section">
<div class="section-item">
<small>系统跟踪号(STAN):</small>
<small th:text="${receipt.stan}"></small>
</div>
<div class="section-item">
<small>交易日期:</small>
<small th:text="${receipt.transactionDate}"></small>
</div>
</div>
<div class="section">
<div class="section-item">
<small>交易类型:</small>
<small th:text="${receipt.transactionType}"></small>
</div>
<div class="section-item">
<small>交易金额:</small>
<small th:text="'NGN ' + ${receipt.requestAmount}"><b></b></small>
</div>
</div>
<div class="section">
<div class="section-item">
<small>商户类别码(MCC):</small>
<small th:text="${receipt.mcc}"></small>
</div>
<div class="section-item">
<small>卡组织:</small>
<small th:text="${receipt.scheme}"></small>
</div>
<div class="section-item">
<small>卡号:</small>
<small th:text="${receipt.maskedPan}"></small>
</div>
<div class="section-item">
<small>收单机构识别码(BIN)</small>
<small th:text="${receipt.acquirer}"></small>
</div>
</div>
<div class="section">
<div class="section-item">
<small>授权号:</small>
<small th:text="${receipt.approvalNumber}"></small>
</div>
<div class="section-item">
<small>处理代码:</small>
<small th:text="${receipt.processingCode}"></small>
</div>
<div class="section-item">
<small>响应码:</small>
<small th:text="${receipt.responseCode}"></small>
</div>
<div class="section-item">
<small>检索参考号(RRN):</small>
<small th:text="${receipt.retrievalNumber}"></small>
</div>
<div class="section-item">
<small>状态</small>
<small th:text="${receipt.displayMessage}"></small>
</div>
</div>
<h3 style="text-align: center; padding-bottom: 20px;">感谢您的光临!</h3>
</div>
</div>
</body>
</html>
在该模板中,我们使用了外部的css资源以及图片资源。
如下是我们需要准备的资源

2.3 Controller接口定义
@RestController
@RequestMapping("/html2pdf")
public class Html2PdfController {
private final ITemplateEngine templateEngine ;
public Html2PdfController(ITemplateEngine templateEngine) {
this.templateEngine = templateEngine;
}
@GetMapping("/download")
public ResponseEntity<byte[]> downloadEJournalFile( HttpServletRequest request,
HttpServletResponse response) throws Exception {
// 1.准备数据
Map<String, Object> variables = Map.of("receipt", getData()) ;
// 2.创建上下文并添加变量(模板需要的数据)
Context context = new Context();
context.setVariables(variables);
// 2.1.获取解析后的模板内容
String receiptTemplate = templateEngine.process("receipt", context);
// 3.配置模板中使用的基础资源信息
ConverterProperties converterProperties = new ConverterProperties();
// 如果你的模板中引用了样式,那么你需要设置
converterProperties.setBaseUri("http://localhost:8080") ;
// 3.1.设置字体
FontProvider fontProvider = new FontProvider();
// 加载系统所有字体(最简单方式)
fontProvider.addSystemFonts() ;
converterProperties.setFontProvider(fontProvider) ;
// 3.2.HTML到PDF转换
ByteArrayOutputStream target = new ByteArrayOutputStream();
HtmlConverter.convertToPdf(receiptTemplate, target, converterProperties);
// 4.设置下载信息
String fileName = URLEncoder.encode("收据明细.pdf", "UTF-8");
HttpHeaders header = new HttpHeaders();
header.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName);
header.add("Cache-Control", "no-cache, no-store, must-revalidate") ;
header.add("Pragma", "no-cache") ;
header.add("Expires", "0") ;
return ResponseEntity.ok()
.headers(header)
.contentType(MediaType.APPLICATION_PDF)
.body(target.toByteArray()) ;
}
public Receipt getData() {
return ... ;
}
}
该接口中已经详细的说明关键代码的作用。
2.4 测试
调用上面接口后,生成的pdf如下:

图片、样式都正确的加载。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
相关文章
暂无评论...


