PDF 功能使用
Micro-PDF 提供基于 Thymeleaf 模板 + 动态 JSON 数据的 PDF 生成能力:先把 templateContext(Thymeleaf HTML)与 data(JSON 字符串)渲染成 HTML,再通过 XHTMLRenderer 生成 PDF。PDF 模板通过数据库(mdm_pdf_template 表)+ Redis 缓存管理,业务侧只需注入 PdfApi 即可使用。
注入 Pdf API
在任意 Spring 组件中注入 PdfApi:
@Service
public class YourService {
@Autowired
private PdfApi pdfApi;
}PdfApi 位于 com.wkclz.micro.pdf.api,由 PdfAutoConfig 自动装配扫描,无需额外配置。
生成 PDF 并响应
使用 PdfApi.responsePdf 直接生成 PDF 并写入 HttpServletResponse,浏览器内联预览:
public void preview(HttpServletResponse response) {
String templateCode = "contract";
String data = "{\"title\":\"采购合同\",\"partyA\":\"甲方公司\"}";
pdfApi.responsePdf(templateCode, data, response);
}说明:
templateCode:PDF 模板编码,对应mdm_pdf_template表的templateCode字段;模板内容会从缓存(PdfTemplateCache)中按编码加载,若不存在会抛出ValidationException(error templateCode)data:模板渲染所需的 JSON 字符串,会被解析为 Thymeleaf 上下文变量(key 即变量名);非 JSON 格式会抛出ValidationExceptionresponse:方法内部设置Content-Disposition: inline; filename=xxx.pdf与Content-Type: application/pdf后,将生成的 PDF 文件内容写入响应输出流,因此浏览器可以直接内联预览
responsePdf 内部即 writePdf 生成临时文件 + PdfHelper.pdfResponse 写出,适用于无需保留文件的即时预览/下载场景。
生成 PDF 文件
使用 PdfApi.writePdf 生成 PDF 文件,返回生成的 PDF 临时文件路径:
public String generatePdf() {
String templateCode = "invoice";
String data = "{\"invoiceNo\":\"INV-2026-0001\",\"amount\":\"1999.00\"}";
String pdfPath = pdfApi.writePdf(templateCode, data);
// pdfPath 形如:/tmp/pdf/pdf_1756xxxxxxx.pdf
return pdfPath;
}说明:
- 返回值为生成的 PDF 文件完整路径,由
PdfHelper.pdfRenderer写入FileUtil.getTempPath("pdf")目录下,文件名形如pdf_<时间戳>.pdf - 生成流程:加载模板 →
PdfHelper.getContext(data)解析 JSON →PdfHelper.thymeleafRenderer渲染 HTML →PdfHelper.pdfRenderer生成 PDF - 拿到路径后可自行上传到文件服务、发送邮件附件,或手动写入响应流等
Thymeleaf 模板编写
mdm_pdf_template.templateContext 存放的是 Thymeleaf HTML 模板,data(JSON 字符串)中的字段会被解析为 Thymeleaf 上下文变量,通过 th:text 等指令动态填充。
渲染时若 HTML 包含 <body> 标签,会自动替换为:
<body style="font-family: SimSun, sans-serif;">即自动为正文指定宋体(SimSun)字体,保证中文正常显示;Linux 环境下还会从 shrimp.pdf.simsun.path(默认 /usr/share/fonts/zh/simsun.ttf)加载宋体字体文件嵌入 PDF。
模板示例
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>合同确认单</title>
<style>
body { padding: 40px; }
h1 { text-align: center; font-size: 24px; }
.info { line-height: 2; }
</style>
</head>
<body>
<h1 th:text="${title}">合同标题</h1>
<div class="info">
<p>合同编号:<span th:text="${contractNo}">CONTRACT-0000</span></p>
<p>甲方:<span th:text="${partyA}">甲方名称</span></p>
<p>乙方:<span th:text="${partyB}">乙方名称</span></p>
<p>签署日期:<span th:text="${signDate}">2026-01-01</span></p>
<p>合同金额:<span th:text="${amount}">0.00</span> 元</p>
</div>
</body>
</html>对应 mockData JSON
{
"title": "采购合同确认单",
"contractNo": "CONTRACT-2026-0001",
"partyA": "华东科技有限公司",
"partyB": "华南贸易有限公司",
"signDate": "2026-08-30",
"amount": "1999.00"
}模板编写要点:
- 变量通过
${变量名}引用,data中 JSON 的 key 即变量名 - 支持 Thymeleaf 的
th:text、th:each、条件判断等全部表达式能力 <body>标签会自动附加宋体样式,无需手动指定字体- 建议先通过
## Mock 在线预览接口快速验证模板效果,再保存为正式模板
模板管理 REST 接口
模板管理接口统一以 /micro-pdf 为前缀,支持分页查询、详情、新增、修改、删除、Mock 预览。所有请求参数与响应字段均以源码为准。
1. 分页查询模板
GET /micro-pdf/template/page请求参数(Query):
templateCode:模板编码(模糊匹配,可选)templateName:模板名称(模糊匹配,可选)current/size:分页参数(继承PageReq)
请求示例:
curl "http://localhost:8080/micro-pdf/template/page?templateCode=contract¤t=1&size=10"响应:R<PageData<PdfTemplatePageResp>>,列表项包含 templateCode、templateName 及基础字段。
2. 查询模板详情
GET /micro-pdf/template/info?id=1请求参数(Query):id(模板主键 ID)
请求示例:
curl "http://localhost:8080/micro-pdf/template/info?id=1"响应:R<PdfTemplateInfoResp>,包含 templateCode、templateName、templateContext(完整模板 HTML)、mockData。
3. 新增模板
POST /micro-pdf/template/create
Content-Type: application/json请求参数(Body,JSON):
templateCode:模板编码(可选,建议填写,作为writePdf/responsePdf的入参)templateName:模板名称(必填)templateContext:模板内容(必填,Thymeleaf HTML)mockData:模拟数据(可选,JSON 字符串,创建时会校验必须是合法 JSON)sort:排序(可选)remark:备注(可选)
请求示例:
curl -X POST "http://localhost:8080/micro-pdf/template/create" \
-H "Content-Type: application/json" \
-d '{
"templateCode": "contract",
"templateName": "合同确认单",
"templateContext": "<html><body><h1 th:text=\"${title}\">合同标题</h1></body></html>",
"mockData": "{\"title\":\"采购合同确认单\"}",
"sort": 1,
"remark": "采购合同模板"
}'响应:R<PdfTemplateInfoResp>,返回创建后的模板完整信息。
说明:
templateCode唯一,重复新增会抛出ValidationException(RECORD_DUPLICATE)。
4. 修改模板
POST /micro-pdf/template/update
Content-Type: application/json请求参数(Body,JSON):
id:模板主键 ID(必填,继承UpdateReq)version:数据版本(必填,乐观锁,继承UpdateReq)templateName:模板名称(必填)templateContext:模板内容(必填)mockData:模拟数据(可选,JSON 字符串,修改时会校验必须是合法 JSON)sort/remark:排序 / 备注(可选)
请求示例:
curl -X POST "http://localhost:8080/micro-pdf/template/update" \
-H "Content-Type: application/json" \
-d '{
"id": 1,
"version": 0,
"templateName": "合同确认单(2026版)",
"templateContext": "<html><body><h1 th:text=\"${title}\">合同标题</h1></body></html>",
"mockData": "{\"title\":\"采购合同确认单(2026)\"}"
}'响应:R<PdfTemplateInfoResp>,返回修改后的模板完整信息。
5. 删除模板
POST /micro-pdf/template/remove
Content-Type: application/json请求参数(Body,JSON):id(模板主键 ID)或 ids(主键 ID 清单,与 id 二选一,继承 RemoveReq)
请求示例:
curl -X POST "http://localhost:8080/micro-pdf/template/remove" \
-H "Content-Type: application/json" \
-d '{"id": 1}'响应:R<Void>。
6. Mock 在线预览
POST /micro-pdf/template/mock
Content-Type: application/json请求参数(Body,JSON):
templateContext:模板内容(必填,Thymeleaf HTML)mockData:模拟数据(可选,JSON 字符串)
请求示例:
curl -X POST "http://localhost:8080/micro-pdf/template/mock" \
-H "Content-Type: application/json" \
-d '{
"templateContext": "<html><body><h1 th:text=\"${title}\">合同标题</h1><p th:text=\"${partyA}\">甲方</p></body></html>",
"mockData": "{\"title\":\"采购合同\",\"partyA\":\"华东科技有限公司\"}"
}' \
-o preview.pdf响应:直接返回 PDF 流(application/pdf),Content-Disposition: inline,文件名形如 Mock_<时间戳>.pdf。
Mock 接口用于"所见即所得"地验证模板效果,无需保存模板即可即时生成 PDF 预览:
- 传入
templateContext(Thymeleaf HTML)与mockData(JSON 数据),接口内部完成「解析数据 → 渲染 HTML → 生成 PDF → 写入响应」 - 与正式模板的唯一区别是:
mock直接使用请求中传入的templateContext,而writePdf/responsePdf通过templateCode从缓存加载模板内容 - 非常适合模板调试:编辑模板时先用 mock 验证渲染效果,确认无误后再通过 create/update 保存
mockData必须是合法 JSON,否则抛出ValidationException(mock 数据不是 json 格式,请修正后重试)
完整业务接入场景示例
以下示例演示在一个 Service 中注入 PdfApi,实现「根据模板生成 PDF → 上传到文件服务 → 输出下载链接」,以及「直接在线预览 PDF」两种常见场景:
@Service
public class ContractPdfService {
@Autowired
private PdfApi pdfApi;
@Autowired
private FileosUploadApi fileosUploadApi;
/**
* 生成合同 PDF 并上传到文件服务,返回可下载/预览的文件记录
*
* @param contractNo 合同编号
*/
public MdmFileosRecordDto generateContractPdf(String contractNo) {
// 1. 组装模板渲染数据(JSON 字符串)
String data = JSON.toJSONString(new HashMap<String, Object>() {{
put("contractNo", contractNo);
put("title", "采购合同确认单");
put("partyA", "华东科技有限公司");
put("partyB", "华南贸易有限公司");
put("signDate", "2026-08-30");
put("amount", "1999.00");
}});
// 2. 根据模板编码生成 PDF 临时文件
String pdfPath = pdfApi.writePdf("contract", data);
// 3. 读取 PDF 文件并上传到文件服务(分类:pdf)
try (InputStream in = Files.newInputStream(Paths.get(pdfPath))) {
MultipartFile file = ...; // 将 InputStream 包装为 MultipartFile
return fileosUploadApi.upload(file, "pdf");
} catch (IOException e) {
throw new RuntimeException("合同 PDF 上传失败", e);
}
}
/**
* 在线预览合同 PDF(浏览器内联展示)
*
* @param contractNo 合同编号
* @param response HTTP 响应,用于输出 PDF 流
*/
public void previewContractPdf(String contractNo, HttpServletResponse response) {
String data = "{\"contractNo\":\"" + contractNo
+ "\",\"title\":\"采购合同确认单\",\"partyA\":\"华东科技有限公司\",\"partyB\":\"华南贸易有限公司\"}";
// 直接写入响应,浏览器内联预览(Content-Disposition: inline)
pdfApi.responsePdf("contract", data, response);
}
}接入流程小结:
- 先在管理端(或直接调用 create 接口)维护好
templateCode对应的 PDF 模板 - Service 注入
PdfApi - 需要下载/留档时调用
writePdf获取 PDF 临时文件路径,自行处理上传、发送等后续操作 - 需要在线预览时调用
responsePdf直接将 PDF 写入HttpServletResponse - 模板内容修改后,
PdfTemplateCache会自动刷新缓存(定时 12 秒),无需重启服务