各位在HTML和PDF之间反复横跳的道友们!今天要解锁的是Freemarker+PDF这套组合仙术——用模板引擎生成动态HTML,再一键转化为专业PDF!从此告别手动调整页眉页脚的苦日子,让你的报表优雅如仙门典籍! 📜✨
一、筑基篇:核心法宝准备
1.1 依赖配置(Maven炼丹炉)
<!-- Freemarker模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- PDF转换神器(推荐openhtmltopdf) -->
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-core</artifactId>
<version>1.0.10</version>
</dependency>
<dependency>
<groupId>com.openhtmltopdf</groupId>
<artifactId>openhtmltopdf-pdfbox</artifactId>
<version>1.0.10</version>
</dependency>
1.2 基础目录结构
src/
├── main/
│ ├── resources/
│ │ ├── templates/
│ │ │ ├── pdf/
│ │ │ │ ├── report.ftl # PDF模板文件
│ │ │ │ └── style.css # PDF专用样式
│ │ └── fonts/ # 中文字体必须!
│ │ └── simsun.ttf
二、金丹篇:模板设计心法
2.1 专用CSS(PDF炼体术)
/* style.css */
body {
font-family: "SimSun"; /* 必须指定支持中文的字体 */
font-size: 12pt;
line-height: 1.5;
}
.page-break {
page-break-after: always; /* 分页符 */
}
.table-pdf {
width: 100%;
border-collapse: collapse;
}
.table-pdf th {
background-color: #f2f2f2;
border: 1pt solid #ddd;
}
2.2 动态模板(report.ftl)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>${reportTitle}</h1>
<!-- 分页示例 -->
<div class="page-break"></div>
<!-- 表格数据 -->
<table class="table-pdf">
<tr>
<th>序号</th>
<th>产品名</th>
<th>价格</th>
</tr>
<#list products as item>
<tr>
<td>${item?index + 1}</td>
<td>${item.name}</td>
<td>¥${item.price?string("0.00")}</td>
</tr>
</#list>
</table>
<!-- 页脚 -->
<div style="position: fixed; bottom: 0; width: 100%; text-align: center;">
生成时间:${.now?string("yyyy-MM-dd HH:mm")}
</div>
</body>
</html>
三、元婴篇:Java核心转换术
3.1 PDF生成器(点石成金)
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import freemarker.template.Configuration;
public byte[] generatePdf(String templateName, Map<String, Object> data) throws Exception {
// 1. 渲染HTML
String html = FreeMarkerTemplateUtils.processTemplateIntoString(
freemarkerConfig.getTemplate("pdf/" + templateName),
data
);
// 2. 转换PDF
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
PdfRendererBuilder builder = new PdfRendererBuilder();
builder.useFont(new File("src/main/resources/fonts/simsun.ttf"), "SimSun");
builder.withHtmlContent(html, "/"); // 设置基础路径以加载CSS
builder.toStream(os);
builder.run();
return os.toByteArray();
}
}
3.2 Spring控制器调用
@Controller
public class ReportController {
@Autowired
private Configuration freemarkerConfig;
@GetMapping("/download-report")
public void downloadReport(HttpServletResponse response) throws Exception {
// 准备数据
Map<String, Object> data = new HashMap<>();
data.put("reportTitle", "2023年度销售报告");
data.put("products", Arrays.asList(
new Product("屠龙刀", 9999.99),
new Product("倚天剑", 8888.88)
));
// 生成PDF
byte[] pdfBytes = generatePdf("report.ftl", data);
// 输出到响应流
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=report.pdf");
response.getOutputStream().write(pdfBytes);
}
}
四、化神篇:高级技巧
4.1 中文字体解决方案(必看!)
// 必须显式注册字体(否则中文显示为空白)
builder.useFontResolver(new FontResolver() {
@Override
public FontResource resolveFont(String family, int weight, String style) {
try {
File fontFile = new ClassPathResource("fonts/simsun.ttf").getFile();
return new FontResource(fontFile.toURI().toURL(), weight, style);
} catch (Exception e) {
throw new RuntimeException("字体加载失败", e);
}
}
});
4.2 页眉页脚(宗门印记)
<!-- 在模板中添加 -->
<style>
@page {
@top-center {
content: "${reportTitle} - 机密";
}
@bottom-right {
content: "第 " counter(page) " 页";
}
}
</style>
4.3 复杂表格样式
/* 斑马线表格 */
.table-pdf tr:nth-child(even) {
background-color: #f9f9f9;
}
/* 防止跨页断行 */
.table-pdf tr {
page-break-inside: avoid;
}
五、大乘篇:性能优化
5.1 模板预加载(灵气压缩)
@PostConstruct
public void preloadTemplates() {
// 启动时预加载模板
freemarkerConfig.getTemplate("pdf/report.ftl");
}
5.2 缓存PDF(减少重复计算)
@Cacheable(value = "pdfReports", key = "#reportName")
public byte[] generateCachedPdf(String reportName, Map<String, Object> data) {
// ...同前...
}
六、渡劫警示(常见天劫)
-
中文乱码
- 确保:模板UTF-8编码 + 正确注册中文字体
- 检查:CSS中
font-family
与注册字体名一致
-
分页异常
- 使用
page-break-before/after
控制分页 - 避免在表格/图片中间强制分页
- 使用
-
样式失效
- PDF引擎仅支持部分CSS属性(如不支持flex布局)
- 推荐使用传统
float
+clear
布局
飞升指南:最佳实践
- 模板设计:
- 使用
pt
作为单位(1pt=1/72英寸) - 固定尺寸使用
mm
或cm
- 使用
- 字体管理:
- 商业项目需购买正版字体
- 推荐开源字体:思源宋体/黑体
- 版本控制:
- 将模板文件与CSS纳入Git管理