Java后端PDF导出实战:FreeMarker+HTML转PDF方案与踩坑记录

JavaPDF导出FreeMarker
于 2026-10-01 03:19:25 修改
·本内容遵循CC 4.0 BY-SA版权协议

说了句“最后再说一句”,但实际上这是个 Java 导出 PDF 的技术分享场景,不需要刻意安排一句文案,“最后再说一句”反而显得刻意。但既然明确要求了,我们就自然地在正文引用这句开头。

Java 后端做 PDF 导出,需求其实很常见:合同、对账单、报名表、检测报告……但如果想把数据塞进一张固定样式的 PDF,很多同学第一反应是用 iText 一个坐标一个坐标地画,或者去啃 JasperReport 的报表设计器。我这边有个生产环境已经稳定跑了两年的导出系统,核心凭证模块用的是 FreeMarker 渲染 HTML 模板,再用 Flying Saucer + OpenPDF 输出 PDF。这套组合表面像“野路子”,实际用下来开发效率极高,也踩了一堆文档搜不到、只能靠读源码定位的坑。今天这篇就是把这两年积攒下来的方案细节和踩坑实录完整写出来,给准备做导出功能的同学一个参考。

1. 方案选型:为什么我用 FreeMarker + HTML + PDF 转换

1.1 主流 Java PDF 导出方案对比

做技术选型前,先看清楚市面上常见的四条路:JasperReports、Apache POI + Word 转 PDF、iText 直接画坐标、FreeMarker/Thymeleaf 渲染 HTML 再转 PDF。这四条路我都做过 POC,各有各的问题。

方案 优点 缺点 适用场景
JasperReports 功能强,有可视化设计器、图表、报表引擎 学习曲线陡,JRXML 模板升级兼容难,调试麻烦 复杂专业报表、固定样式要求极高的金融单据
Apache POI + Word 转 PDF 可复用 Word 模板,业务方可以直接编辑 Word 依赖 Office 环境或 LibreOffice,样式映射容易丢失 企业合同、服务商配合改格式
iText 7 + 手绘布局 灵活,完全控制输出位置 开发成本高,改布局需要改代码 动态版式复杂,且样式不常改
FreeMarker + HTML 转 PDF 模板像网页,浏览器可预览,数据与样式分离 CSS 兼容有限,需要处理字体和分页 对账单、报告、证书、凭证等固定版式动态数据

JasperReports 不是不好,而是太重。对于“每个季度改一次模板样式”的业务,用 JasperReports 就得让 Java 开发陪着报表设计器来回调。而 iText 画坐标的方式,每改一次版式都要重新定位所有文本和线条,稍微复杂一点的单据,代码量能吓跑新人。

我最后选择 FreeMarker + HTML 转 PDF,最大的原因是:模板开发可以像写网页一样迭代。设计稿出来,先写一个静态 HTML 看效果,然后塞入 FreeMarker 变量,再跑一个转换工具类生成 PDF。整个链路里,前端能帮忙调样式,后端只负责数据模型和产出接口,分工非常清晰。

1.2 最终选型:用 HTML 当模板,拿 PDF 当输出

这套方案的原理其实不复杂。FreeMarker 负责把动态数据填入 HTML 模板,生成一段完整的、带样式的 HTML 字符串;再用 PDF 渲染引擎把这段 HTML 解析成 PDF 文档。中间产物是 HTML,最终交付物是 PDF。

我常用一个类比解释这种模式:把 HTML 模板当成“冲印照片的底片”,PDF 就是“最终拿给客户看的照片”,而数据则是“被冲印到照片上的人脸”。底片决定了布局,数据决定了内容,两者互不干扰。

从工程层面看,这个模式有三个实实在在的好处:

一是布局可预览。开发阶段直接把 FreeMarker 渲染后的 HTML 字符串扔到浏览器里,基本能看出最终 PDF 的版型骨架,不需要每次生成 PDF 再用阅读器打开。

二是模板复用和组织简单。FreeMarker 支持 <#include>、<#macro>、<#list>,像对账单这种由多个区块组成的文档,可以拆成表头宏、明细模板、备注块,按需拼接。

三是部署轻量。不依赖 Office 套件、不依赖外部服务,只要 JVM 里能加载字体文件就能出 PDF,这一点在容器化部署时尤其省心。

2. 落地步骤:依赖、模板与核心代码

2.1 依赖长这样,版本注意别踩雷

我用的 PDF 渲染引擎是 Flying Saucer 的 openpdf 分支,flying-saucer-pdf-openpdf。为什么不用原始 iText?因为早期 iText 版本涉及商业授权条款,社区维护的开源版本更稳妥。OpenPDF 是 iText 4 的社区分支,继续维护且没有商业授权顾虑。

XML
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf-openpdf</artifactId>
<version>9.1.22</version>
</dependency>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.30</version>
</dependency>

需要注意 flying-saucer-pdf-openpdf 和 flying-saucer-pdf 类名不同。前者依赖 OpenPDF,核心类是 ITextRenderer;后者对应的是旧版 iText。网上很多教程两者混着写,照抄后经常出现 NoClassDefFoundError。

OpenPDF 1.3.30 这个版本和 Flying Saucer 9.1.22 的兼容性,我实测是没问题的。曾经为了升级字体渲染性能,尝试过更高版本的 OpenPDF,结果某些样式渲染行为变了,后来就锁死了这两个版本在生产使用。如果你不想折腾,就按我上面的版本组合来。

2.2 写一个最小可用的 PDF 模板

FreeMarker 模板文件同样放在 resources/templates/pdf 下,文件名后缀 .ftl。来看一个简化版的模板 report.ftl:

HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<style>
body { font-family: SimSun; font-size: 12px; }
.title { text-align: center; font-size: 20px; margin: 10px 0 20px; }
table { width: 100%; border-collapse: collapse; }
td, th { border: 1px solid #333; padding: 5px; }
</style>
</head>
<body>
<div class="title">${title}</div>
<table>
<tr>
<th>序号</th>
<th>项目</th>
<th>金额</th>
</tr>
<#list items as it>
<tr>
<td>${it.index}</td>
<td>${it.name}</td>
<td>${it.amount?string(",##0.00")}</td>
</tr>
</#list>
</table>
</body>
</html>

这个模板看着简单,但有一个必须记住的坑:Flying Saucer 内部用 XML 解析器处理 HTML,所以标签必须闭合,属性必须带引号,<br> 写成 <br/>。 曾经一个同事在模板里写了 <br>,开发环境用浏览器打开没问题,一调 PDF 接口直接报解析异常,排查了大半天才定位到是这个空标签没闭合。

<#list items as it> 这种写法告诉我们,模板里不能写死任何业务数据。所有动态内容都通过 model 传入,模板只负责展示逻辑。

2.3 核心导出工具类(可直接抄)

生产环境的工具类会比这里复杂,但核心骨架就是下面这段:

JAVA
public class PdfExportUtil {
 
private static final Configuration FREEMARKER_CFG =
new Configuration(Configuration.VERSION_2_3_32);
 
static {
FREEMARKER_CFG.setDefaultEncoding("UTF-8");
FREEMARKER_CFG.setClassLoaderForTemplateLoading(
PdfExportUtil.class.getClassLoader(), "/templates/pdf");
FREEMARKER_CFG.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
// 生产环境保留模板缓存,开发环境可以改成 0
FREEMARKER_CFG.setTemplateUpdateDelayMilliseconds(60_000);
}
 
public static void export(String templateName,
Map<String, Object> model,
OutputStream out) throws Exception {
Template template = FREEMARKER_CFG.getTemplate(templateName);
StringWriter writer = new StringWriter();
template.process(model, writer);
 
ITextRenderer renderer = new ITextRenderer();
// 关键:注册中文字体,不注册就是方块字
renderer.getFontResolver().addFont(getSimSunFontPath(),
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
renderer.setDocumentFromString(writer.toString());
renderer.layout();
renderer.createPDF(out);
out.flush();
}
 
private static String getSimSunFontPath() {
// 建议把字体放在 resources/fonts 下,不要用本机绝对路径
return PdfExportUtil.class.getResource("/fonts/simsun.ttf").getPath();
}
}

这里有几个细节值得展开说:

第一,FREEMARKER_CFG 必须是全局单例。如果每次导出都 new Configuration(),模板缓存会完全失效,性能会断崖式下降,后面第三节还会聊到并发问题。

第二,addFont 方法每次创建渲染器时都要调用,但实际字体文件的读取成本可以通过外部缓存优化。生产环境我维护了一个静态 Set<String> 记录已注册的字体路径,虽然每次还是要调 addFont,但内部会按路径判断,重复注册的开销会小很多。

第三,字体路径尽量不要从 jar 包内 getResource().getPath() 拼。最稳妥的方案是把字体放到 classpath,然后用 ClassPathResource 或 ClassLoader.getResourceAsStream 输出到临时文件再交给 addFont,避免 jar 包内路径和文件系统中的空格问题。

2.4 中文字体:漏了它全是乱码

HTML 里的 font-family: SimSun 只是个名字,最终 PDF 渲染引擎必须能找到对应的字体文件。Flying Saucer 的 FontResolver 会把 CSS 里的字体名映射到系统字体或注册字体,映射不到就会用默认字体,而默认字体通常不支持中文,结果就是满屏“口口口”。

处理方式分两步:

第一步,在项目 resources/fonts 下放一个 TrueType 中文字体,比如 simsun.ttf。为什么强调 .ttf?因为 .ttc 是字体集合,某些版本的 OpenPDF 读 .ttc 会报错或者只能读取第一个字体,兼容性比较差。如果只能拿到 .ttc,可以先用字体转换工具转成 .ttf 再放进项目。

第二步,在渲染前调用:

JAVA
renderer.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

IDENTITY_H 表示用 Unicode 编码方式映射文字,NOT_EMBEDDED 表示不把字体文件嵌进 PDF 里。如果业务要求 PDF 必须在其他环境一致显示,可以用 BaseFont.EMBEDDED,但文件会大出不少,我见过一份对账单从 200KB 涨到 8MB,就是因为嵌入了完整字体,所以生产上默认是不嵌入。

3. 生产环境两年积累的坑

3.1 中文乱码,比你想的复杂

字体注册是对付乱码的基础,但真实生产环境里这不是唯一原因。我曾经遇到过一种非常隐蔽的乱码:模板里写的是 &nbsp;,渲染出来的 PDF 里变成了奇怪的空白块。排查后发现,XML 解析器把 HTML 实体 &nbsp; 直接当作字符实体转换,在 OpenPDF 字体映射里找不到对应的字形,就渲染成乱码。

从那以后,模板里我统一用空格和 padding 来控制间距,尽量避免 &nbsp;。另外,如果数据字段里带有 HTML 标签,比如用户填的备注是 <b>加急</b>,也会被解析成真实标签,轻则排版混乱,重则整个结构崩掉,这个问题后面第五节会专门讲。

还有一个经验:FreeMarker 的 Configuration 必须设置 setDefaultEncoding("UTF-8"),模板文件本身也必须以 UTF-8 编码保存。有人模板文件是 UTF-8,但 IDE 没有正确识别,导出时中文直接乱码。定位方式很简单,把渲染后的 HTML 字符串打印出来看中文是否正常,如果 HTML 正常而 PDF 乱,就是字体问题;如果 HTML 就已经乱,立即检查模板文件的编码。

3.2 CSS 兼容性:千万别拿浏览器标准写模板

这是这套方案最大的坑,也是新人最常踩的坑。Flying Saucer 底层的 CSS 解析器对 CSS 2.1 的支持都不完整,Flex、Grid、position: fixed、border-radius、box-shadow 这些现代 CSS 特性基本别想用。

我踩过一次很离谱的:想用 position: fixed 在每一页底部加一个页脚,结果这个 div 只出现在最后一页的底部,而不是每一页重复。后来才明白,Flying Saucer 对固定定位的支持非常弱,position: fixed 在 PDF 分页时并不会像浏览器那样“固定视口”。

这种情况下,我的布局原则是:

  • 能用表格就用表格,table + td 是 Flying Saucer 支持最稳定的布局方式;
  • 不用 Flex、Grid,不要指望 float 一定能按预期排;
  • 不要用 position: fixed,页眉页脚用 @page 规则做;
  • 背景图片偶尔会消失,尽量用纯色背景。

如果确实需要跨页表格,给 table 加 -fs-table-paginate: paginate;,至少能让表头在每页重复,这个属性在生产环境实测是有效的。

3.3 页码页脚和 @page,这样设置才有效

没有页码的正式文档是不完整的。Flying Saucer 支持 CSS @page 规则里的页边距盒子,可以做页码和页脚:

CSS
@page {
size: A4;
margin: 20mm 15mm 15mm 15mm;
@bottom-center {
content: "第 " counter(page) " 页 / 共 " counter(pages) " 页";
font-size: 9px;
color: #666;
}
}

把这套 CSS 放在模板的 <style> 里,渲染出来的 PDF 就会自动带上页码,不需要自己在 Java 代码里拼接。这个特性在高版本 Flying Saucer 中稳定可用,但用 HtmlConverter.convertToPdf 就未必支持,所以生产环境我坚持用 ITextRenderer 而不是 HtmlConverter。

如果还要加公司 Logo 作为页眉,推荐用页边距盒子:

CSS
@top-left {
content: url('/logos/company.png');
}

但这里有个问题:页边距盒子的 content: url() 只支持绝对路径或 file: 协议,classpath 下的图片需要提前输出到临时目录。所以生产上更简单的做法是把页眉信息直接放在模板正文第一行,配合 -fs-table-paginate 让表头在每页重复,效果也够用。

3.4 并发导出:系统卡顿的真正原因

刚上线那会儿,导出一张 PDF 没什么感觉。等用户量涨起来,同一个接口同时来了十几个导出请求,CPU 直接飙升,其他普通接口也跟着超时。排查到最后,问题出在三个地方:

第一,原来的代码每次请求都 new Configuration(),模板缓存完全没生效,Freemarker 每次都要重新解析 .ftl 文件。

第二,addFont 每次导出都做一次字体文件 IO,十几个请求并发时,磁盘和内存都扛不住。

第三,导出是 CPU 密集型操作,没有做并发控制,所有请求一窝蜂冲进 layout() 和 createPDF(),内存瞬间耗尽。

改进方案很直接:

  • 全局复用 Configuration 单例;
  • 字体文件用常量路径注册,保留静态判断避免重复 IO;
  • 导出接口增加 Semaphore 限制并发数,比如最大同时 4 个 PDF 渲染任务,其余请求排队或走异步任务表;
  • 真正的大批量导出,从同步接口改为异步任务,生成完成后发送下载通知。

改了之后,系统在高并发时段再也没有因为导出接口挂掉过。

3.5 模板缓存:改模板不生效的真相

Freemarker 默认会缓存模板。开发环境为了改模板即时生效,需要设置:

JAVA
FREEMARKER_CFG.setTemplateUpdateDelayMilliseconds(0);

但在生产环境,如果你把模板文件放在 classpath 下,发布后改了模板文件而不重新打包,程序读到的永远是旧模板。这个坑有两种解法:

一种是模板放在外部目录,不上 jar。通过 cfg.setDirectoryForTemplateLoading(new File("/data/pdf-templates")) 加载,部署时单独覆盖模板文件。这种方案适合业务方频繁改模板、不想每次发版的情况。

另一种是模板仍然进 jar,但发布时用版本号区分模板文件名,比如 report_v3.ftl。代码里配置模板版本号,切换版本时改配置重新加载。这种方式更安全,因为模板和代码一起走版本管理,不会出现线上模板和接口逻辑对不上的情况。

我个人更推荐第二种。因为模板本质上是代码的一部分,脱离版本管理很容易埋雷。

4. 典型问题排查实录(当速查表用)

4.1 表格边框时有时无

有段时间业务反馈 PDF 里表格的右侧边框偶尔消失。排查流程下来,发现是两个原因叠加:

  • table 设置了 width: 100%,但内容过长时表格溢出页面边界,右侧边框被裁掉;
  • border-collapse: collapse 在 Flying Saucer 里实现不完整,跨页时有些行的边框不渲染。

我的改法很简单:

CSS
table {
width: 100%;
table-layout: fixed;
}
td, th {
border: 1px solid #000;
word-break: break-all;
white-space: pre-wrap;
}

然后去掉 border-collapse: collapse,改为每个单元格独立设置边框。这样虽然代码冗余一点,但渲染结果稳定。另外给 table 加上 HTML 属性 border="1" 作为兜底,这样即使某些 CSS 没生效,原始边框属性也能保护一下。

4.2 几万条明细直接 OOM,怎么办

线上出过一次事故:某个业务导出月度明细,数据量 5 万行。Freemarker 渲染出一段近 10MB 的 HTML 字符串,然后一次性交给 ITextRenderer 转 PDF,JVM 内存飙到 2GB,接口超时,连带整个服务被 GC 拖垮。

痛定思痛,三个优化:

首先,模板端控制每页数据量。不要把所有明细全部塞进一个 HTML 表格,而是分段渲染。如果数据结构本身允许分页,可以每页 N 行,然后通过 page-break-before: always 强制分页。5 万行拆成每页 50 行,就是 1000 页,PDF 虽然页数多,但内存压力小得多。

其次,HTML 字符串不要用 StringWriter 整个留在内存里。改成把渲染结果写入临时文件:

JAVA
File tmp = File.createTempFile("pdf-export-", ".html");
try (Writer writer = new OutputStreamWriter(
new FileOutputStream(tmp), StandardCharsets.UTF_8)) {
template.process(model, writer);
}
renderer.setDocument(tmp.toURI().toURL().toExternalForm());
renderer.layout();
renderer.createPDF(out);

最后,数据查询不能一次性 SELECT * FROM detail WHERE order_id = ? 拿全部 5 万条。最好是分批查询,每次只取当前页需要的数据放入 model。虽然模板代码稍微复杂一点,但内存稳定性提升非常明显。

4.3 用户填的数据把 PDF 搞得乱七八糟

用户备注或者商品名称里如果包含 &、<、>,直接放进模板会破坏 HTML 结构。最常见的场景:用户填了一个“测试 < 5 分钟”,模板里渲染成 ${remark},最终 HTML 变成 <div>测试 < 5 分钟</div>,XML 解析器看到 < 5 分钟 这种未闭合语法,直接解析报错。

解决办法是强制转义。FreeMarker 里对所有用户数据字段加 ?html:

HTML
<div>${remark?html}</div>

更彻底一点,在模板开头用 <#escape> 包裹全局:

HTML
<#escape x as x?html>
...整个页面内容...
</#escape>

这样每个变量输出都会自动经过 HTML 转义,省得每个字段都写一遍 ?html。

如果业务字段本身就想存富文本,比如包含加粗或换行的备注,则不能直接转义。这时应该走白名单:只保留 <b>、<br/>、<p> 等少数标签,其余标签全部过滤掉。否则用户填一个 <table> 就能把整个 PDF 版式撑爆。

4.4 Linux 上中文方块字排查

本地 Windows 开发环境一切正常,部署到 CentOS 7 上之后,导出的 PDF 中文全变成方块。这个问题的根源就是服务器没有中文字体。

排查步骤我列成清单:

  • 先看服务器上有没有中文字体:fc-list :lang=zh,没有输出就说明系统缺字体;
  • 有权限直接装:yum install fontconfig wqy-zenhei;
  • 不想依赖系统环境,就把字体文件放进项目 resources/fonts/simsun.ttf,用代码注册。

这里要注意一个细节:getResource("/fonts/simsun.ttf").getPath() 在 jar 包内能拿到路径,但如果项目打成 fat jar,或者部署目录带空格,这个方法会失效。我的经验是用 Spring 的 ClassPathResource 把字体流拷贝到临时文件再注册:

JAVA
File fontFile = File.createTempFile("simsun", ".ttf");
try (InputStream in = new ClassPathResource("fonts/simsun.ttf").getInputStream()) {
Files.copy(in, fontFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
renderer.getFontResolver().addFont(fontFile.getAbsolutePath(),
BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

这样字体一定拿得到、一定读得进来,linux 和 windows 双环境通吃。

5. 如果重新做一次,我会这样优化

5.1 模板纳入版本管理,并提供预览

第一版时模板基本是“写死在代码里改一版发一版”,等业务方参与进来后,问题就来了:业务方要调样式,前端不能直接看 PDF,只能反复导出来发截图,效率很低。

后期我加了一个“模板预览”接口:入参是模板名和 JSON 数据模型,直接返回 FreeMarker 渲染后的 HTML。前端拿到这个 HTML 在浏览器里查看版式,需要调整时不用再去生成 PDF,开发效率提升了不止一倍。

所有模板文件也全部纳入代码仓库,跟着 Git 版本走。线上模板变了,代码版本一定对应一个明确的模板版本,出现问题时可以快速回退。

5.2 不同业务用不同导出策略

两年运行下来,我把导出任务分了四类,对应不同处理策略:

  • 低频小文件(比如单张回执):同步导出,客户端直接下载;
  • 高频小文件(比如用户自己的报告):增加进程内缓存,重复生成条件一致时直接返回缓存 PDF;
  • 低频大文件(比如月度汇总):异步任务,生成完成后通知用户;
  • 高频大文件(比如运营批量导出):限制数据规模,强制拆分导出任务,每次最多 2 万条。

这个矩阵看起来简单,但能帮我们把有限的服务器资源花在刀刃上。之前出现过所有人都在导出大文件导致 OOM 的情况,做了策略分级后再也没有复现。

5.3 要不要换个 Chromium 渲染引擎

如果模板对 CSS 的依赖已经复杂到 HTML 方案完全撑不住,比如需要 Flex 布局、精细的 CSS 背景、或者现代伪元素,那 Flying Saucer 这套方案确实到头了。这个时候我会考虑用无头浏览器方案。

具体就是用 Playwright 或者 Spring Boot 集成 headless Chrome,加载一个模板 URL,然后调用浏览器打印页面的能力生成 PDF。优点是现代 CSS 几乎全部支持,效果和浏览器看到的一模一样。缺点是部署重量上来了,内存占用更高,并发能力也更弱,而且打包一个 Chromium 到 Docker 镜像,体积会膨胀不少。

我的观点是:ToB 后台导出的凭证、报告、对账单,HTML 转 PDF 完全够用;如果是做面向 C 端的精美票据,需要像素级还原设计稿,那再用 Chromium 路线。不要一开始就引入重型依赖,先把方案跑通,再按复杂度升级。

跑了两年的体会是:模板导出 PDF 本身不是核心技术难点,真正的难点在于把模板开发流程、字体环境、并发策略和异常处理做透。如果你刚开始做这个功能,别急着上重型报表引擎,先从 HTML + CSS 模板入手,把上面这些坑提前埋好,后面会省很多时间。最后再说一句,每次发布完模板,找一个小数据手工跑一份对比,把生成的 PDF 和上一版做一次文本差异比对,很多线上问题其实能提前发现。

java 通过模板生成导出PDF方案对比
本文介绍两种通过模板生成PDF文件的方法:一种使用Adobe Acrobat Pro与itextpdf;另一种利用Freemarker和flying-saucer-pdf。文章详细展示了每种方案的技术实现细节及常见问题解决办法。
风中思絮
2269
Java实现pdf导出
本文介绍基于FreeMarker模板引擎与OpenHTMLtoPDF库在Java中实现动态PDF导出的技术方案,涵盖HTML模板渲染、中文字体配置、表格截断修复、分页控制及高并发优化策略;重点解决A4页面宽度限制导致的表格溢出、中文乱码、跨页行断裂等问题,并支持批量PDF打包ZIP下载。
重生之我在学java
589
Java+FreeMarker+OpenHTMLtoPDF实现数据库驱动标签批量打印与PDF导出
本文介绍基于Java、FreeMarker模板引擎与OpenHTMLtoPDF库构建的数据库驱动标签批量打印与PDF导出系统。核心流程包括:CSV/数据库数据源接入、FreeMarker动态HTML模板设计、Java代码完成数据绑定与模板渲染、OpenHTMLtoPDF将HTML精准转换为分页PDF。方案支持条码嵌入、模板参数化、异步生成等工程实践,适用于电商面单、资产标签、仓储管理等场景。
小泉水
233
前后端分离项目PDF生成与打印全链路方案:从OpenPDF到前端打印踩坑实录
本文详述前后端分离架构下PDF生成与打印的完整技术方案:后端采用FreeMarker模板+OpenPDF渲染,解决中文乱码、分页控制、水印添加及PDF加密;前端通过Blob+iframe方式安全获取并预览PDF,规避跨域与鉴权问题,并提供打印触发优化策略。重点覆盖字体嵌入、浏览器兼容性、打印样式一致性及高频问题排查方法。
技术小天使
205
Java+FreeMarker+OpenHTMLtoPDF实现数据库数据批量生成标签PDF
本文介绍基于Java、FreeMarker与OpenHTMLtoPDF构建标签PDF批量生成服务的技术方案,涵盖模板设计、数据库数据绑定、条码动态生成(ZXing)、HTML渲染及PDF导出全流程。重点解决电商、仓储等场景中标签尺寸对齐、批量输出、打印适配与生产环境性能优化等关键问题,支持高并发异步处理与多格式扩展。
Respect yourself
307
Java处理Docx模板编译失败排查指南:从FreeMarker到POI的实战解决方案
本文系统梳理Java中使用FreeMarker与Apache POI动态生成Docx文档时出现'Compile template failed'错误的根因与解决方案。重点涵盖模板文件格式损坏、XML非法字符、FreeMarker语法错误、POI及模板引擎版本冲突、类路径资源加载异常、字符编码不一致、JVM内存不足等典型问题,并提供堆栈分析、依赖树诊断、模板健康度验证、流式处理优化等实战排查方法。
weixin_30820077
454
Spring Boot整合x-easypdf:5分钟搞定中文PDF导出
本文详细介绍了在Spring Boot项目中整合x-easypdf实现高效中文PDF导出的完整流程,涵盖选型对比、Maven依赖配置、中文字体注册与映射、HTML模板渲染、页面布局控制(尺寸/边距/横纵向)、表格排版优化、页眉页脚实现,以及中文乱码、字体加载失败、依赖冲突和并发性能等常见问题的排查与解决方案。
lnstagram优选
322
快马AI×Freemarker:3分钟打造智能合同生成系统
本文介绍了如何利用Freemarker模板引擎与Spring Boot快速搭建智能合同生成系统。文章详细讲解了系统的三大核心模块及关键技术实现,并分享了开发过程中的踩坑经验。结合快马平台的便捷操作,可在3分钟内完成项目生成与部署。
408
Spring Boot整合FreeMarker实战指南:从JSP迁移到模板引擎
本文系统讲解Spring Boot集成FreeMarker模板引擎的完整流程,涵盖选型原因、核心渲染机制(模板+数据模型)、最小工程搭建、生产级配置(日期格式、公共片段抽取、静态资源路径、空值防御)、典型问题排查(缓存、乱码、变量冲突、Controller注解误用)及进阶用法(宏、自定义指令、字符串模板渲染)。强调服务端渲染场景下模板复用与前后端协作最佳实践。
weixin_34038293
363
Java动态导出Word文档:基于Apache POI的模板替换方案详解
本文详解使用Apache POI实现Java动态导出Word文档的模板替换方案,涵盖环境搭建、文本占位符替换、表格循环渲染、图片动态插入等核心功能,并针对格式丢失、样式错位、中文乱码、性能瓶颈等常见问题提供实战解决方案,强调数据与样式的分离设计及模板可维护性。
weixin_30239339
356
JasperReports实战:从模板设计到PDF生成,解决分页统计与金额大写难题
本文系统讲解JasperReports在Java应用中生成PDF报表的完整流程,涵盖环境搭建、Jaspersoft Studio模板设计、JDBC/JavaBean数据源集成、变量实现分页与分组统计、自定义金额中文大写转换、图表与子报表嵌入、字体嵌入防乱码、性能优化及部署调试要点,突出其模板驱动、声明式计算和高可维护性优势。
weixin_34318272
534
Java后端动态生成Word,Poi-tl模板引擎从入门到实战
在Java后端开发中,动态生成Word文档(如合同、验收报告、结算单等)是典型高频需求。传统做法依赖原生Apache POI逐行构建表格,代码量大且样式调整困难,而Freemarker拼接XML又存在兼容性问题。基于Apache POI的Word模板引擎Poi-tl,通过{{变量}}、{{*循环}}等标签将样式与数据分离,让开发人员专注于数据组装,模板负责文档样式,能大幅提升导出效率并降低维护成本。Poi-tl支持文本、表格、图片、条件动态渲染,可无缝集成SpringBoot项目,非常适合用于报表系统、企业
JeecgBoot安全漏洞深度剖析:从低代码平台特性到SRC实战攻防
本文深入剖析JeecgBoot低代码平台在真实SRC与渗透测试中暴露的四大高频安全漏洞:未授权访问(源于Shiro配置误区与拦截器绕过)、SQL注入(MyBatis-Plus动态查询陷阱)、任意文件读取(路径遍历与配置文件泄露)以及远程代码执行(Freemarker SSTI、Fastjson反序列化等漏洞链)。内容涵盖成因机制、实战挖掘技巧、利用链构造及防御加固方案,聚焦框架特性和开发者配置失误导致的安全风险。
weixin_30530339
372
Java家政服务平台毕设项目运行指南:Spring Boot与MySQL部署全攻略
本文详细讲解基于Spring Boot与MySQL构建的Java家政服务平台毕设项目的本地部署全流程,涵盖技术栈识别(Spring Boot 2.x/3.x、MyBatis-Plus、JDK版本)、MySQL数据库创建与SQL导入、多处配置修改(数据库连接URL、账号密码、时区、认证插件)、前后端分离模式下的后端启动(Maven命令)与前端联调(跨域配置、静态资源部署),以及五大高频问题解决方案,如MyBatis-Plus字段映射、跨域拦截、空白页404、MySQL 8驱动连接失败等。
weixin_33785108
630
SpringBoot+Vue财务系统:生产级凭证与报表实现
本博客介绍一套基于SpringBoot、Vue和MySQL构建的生产级财务管理系统,聚焦凭证管理、科目核算、往来对账与报表生成四大核心模块。强调财务数据的ACID保障、BigDecimal精确计算、借贷平衡双保险校验、动态SQL报表及中文化PDF/Excel导出等关键技术实践。内容涵盖技术选型依据、数据库设计哲学、部署调优要点及高频问题避坑方案,适用于中小企业财务IT落地与高校毕设开发。
weixin_33912453
639
[F2F] Java后端AI大纲
120 道 Java 后端 AI 方向面试题,按六个类别整理:数据与接口契约、高并发与流式响应、状态与架构、性能安全成本、工程化与可观测性、Function Calling 与 RAG。排版沿用 MySQL 经典 50 题的做法,第 3 节把试卷整份摊开供自测,第 4 节按同一套全局编号给参考答案:每题一句一针见血的结论,加 2~4 条能落到白板上的要点,写到具体类名、注解、配置项和阈值。
OxYGC
99
Java自动化生成Word文档:从POI到poi-tl的实战指南
本文聚焦Java生态中Word文档自动化生成的最佳实践,重点介绍基于Apache POI封装的模板引擎poi-tl。内容涵盖技术选型对比(POI vs 模板引擎)、Word模板设计规范、数据模型绑定、复杂场景处理(条件判断、嵌套表格、页眉页脚)、性能优化(模板缓存、大数据量处理)及企业级服务封装。强调数据与样式分离原则,突出poi-tl在易用性、样式继承和工程落地能力上的优势。
weixin_30660027
296
Spring Boot + Vue 学生综合素质测评系统全栈开发实战
本博客详细阐述基于Spring Boot与Vue.js开发的学生综合素质测评全栈系统,涵盖RBAC权限管理、动态指标配置、多角色测评流程引擎、加权分数计算、ECharts雷达图展示及大数据导出优化等关键技术。系统采用前后端分离架构,后端使用MyBatis-Plus与MySQL,前端集成Element UI与Axios,支持JWT鉴权、异步计算、流式Excel导出与Token刷新机制,是教育信息化领域典型的企业级Java全栈实践案例。
weixin_34228387
344
Java个人收款系统XPay V3.1:无需签约,资金直达的部署与实战
XPay V3.1是一款基于Spring Boot的Java开源个人收款系统,支持支付宝/微信个人收款码集成,实现资金直连本人账户、无需签约、零平台抽成。系统采用前后端分离架构,后端使用Spring Boot+MyBatis/MySQL,前端基于Bootstrap;核心解决支付状态同步难题(前端轮询+手动确认+跳转回调),并提供订单管理、自动邮件发货、对账补偿与安全加固等能力,适用于个体开发者、知识付费等轻量级收款场景。
csid_502
291
Java实战:从零构建高并发留学生教务系统(Spring Boot+Vue)
本文详解基于Spring Boot与Vue构建的高并发留学生教务管理系统,涵盖学籍管理(含签证预警)、选课排课(Redis分布式锁+乐观锁保障一致性)、成绩管理(GPA计算、PDF成绩单生成)、RBAC权限控制及安全设计。技术栈包括MyBatis-Plus、JWT鉴权、Redis缓存、HikariCP连接池、Actuator+Prometheus监控,并针对中文乱码、事务失效、日期时区、文件上传、并发超卖等典型问题提供企业级解决方案。
weixin_33922670
316
Java html转word 使用FreeMarker
这个标题“Java html转word 使用FreeMarker”暗示了一个Java项目,它利用FreeMarker模板引擎来实现HTML到Word的转换。
rehuojunguan
2360
基于itext7+freemarker模板导出pdf
在iText7中,你可以创建新的PDF文档,或者对已有文档进行修改。其次,FreeMarker是一个模板引擎,它允许我们将HTML样式的模板与数据模型结合,生成动态内容。
Timfeng冯
2816
java后台实现pdf下载导出
在Java后台实现PDF下载导出的过程中,我们通常会涉及到几个关键技术和工具,这些技术与工具在Web开发中用于创建和处理PDF文档。
lei_1994
5793
Java中使用 FreeMarker 生成pdf盖章合同文件
首先,让我们了解FreeMarker的基本概念。FreeMarker是一个基于模板的语言,它与Java代码分离,允许开发者用简单的模板语法来表示数据。
stylewe
5664
Freemarker模板生成pdf+java
该项目基于Java技术栈,利用Freemarker模板引擎生成动态HTML内容,并结合Flying Saucer库将其转换为PDF文档。项目通过Maven进行依赖管理,集成了Servlet API支持
qq_21248023
2975
html转pdfHTML导出PDF中文、ITEXT转HTML为PDF包括中文显示解决方案
在Java开发领域,将HTML内容动态转换为PDF文档是一项常见且重要的需求,尤其在企业级报表生成、合同导出、发票打印、电子凭证生成等业务场景中广泛应用。本方案标题“html转pdf HTML导出PDF中文、ITEXT转HTML为PDF包括中文显示解决方案”精准概括了其核心目标:基于iText库(特指iText 2.x系列,因代码中使用了ITextRenderer——该类属于iText-RTF或早期iText-XMLWorker生态),实现HTML字符串到PDF的高质量、高兼容性、支持中文渲染的自动化转换。该技术并非简单的格式转换,而是一套融合了字体管理、CSS解析、DOM布局、资源路径解析、编码处理与PDF底层字形嵌入机制的综合解决方案。首先,中文显示问题是HTML转PDF过程中最典型、最棘手的技术瓶颈。根本原因在于PDF规范对Unicode的支持依赖于字体的显式声明与嵌入。iText默认内置字体(如Helvetica)仅支持ASCII字符集,无法渲染GB2312、GBK或UTF-8编码下的汉字。若不显式指定中文字体并配置正确的编码策略,PDF中所有中文将显示为方框(□)、空格或乱码。本方案通过`BaseFont.IDENTITY_H`这一关键参数彻底解决该问题。`IDENTITY_H`是iText中专为CJK(中日韩)文字设计的字体编码模式,它启用Unicode双字节水平书写(Horizontal Identity-H),允许字体映射完整的Unicode BMP平面(含U+4E00–U+9FFF等常用汉字区),从而保障每个汉字都能被正确索引和绘制。该编码必须与真实存在的中文字体文件严格绑定,缺一不可。其次,字体资源的加载与注册是支撑`IDENTITY_H`生效的前提。方案中明确引入`simsun.ttc`(即Windows系统自带的宋体TrueType Collection文件),并通过`fontResolver.addFont(...)`将其注册至iText的字体解析器。值得注意的是,此处采用`BaseFont.NOT_EMBEDDED`参数,表明字体未嵌入PDF文件本体,而是依赖目标阅读器(如Adobe Reader)本地是否存在同名字体。虽可减小PDF体积,但存在跨平台兼容风险(Linux/macOS无simsun)。更健壮的做法应为`BaseFont.EMBEDDED`,配合`getResourceAsStream()`读取classpath内字体流,确保PDF在任意环境均可精确还原中文样式。此外,字体路径使用`ItextUtil.class.getResource("simsun.ttc")`,说明字体文件需置于项目resources目录下,体现资源管理的工程化思维。第三,动态HTML拼接能力赋予该方案极强的业务适配性。示例代码中通过纯字符串拼接构建完整HTML表格,涵盖``、``、``、``等语义化标签,并支持CSS属性(如`align="center"`)。这表明系统可无缝对接模板引擎(如Thymeleaf、Freemarker)或动态数据组装逻辑,实现“数据驱动PDF”。`getConversionHtmlCode(linkcss, htmlCode, title)`方法暗示已封装HTML结构增强逻辑(如注入基础CSS重置、添加title元信息、整合外部样式表linkcss),体现模块化设计思想。同时,注释中提及“解决图片的相对路径问题”,揭示了iTextRenderer对``路径解析的局限性——默认以JVM工作目录为基准,需通过`setBaseURL("file:/.../")`强制指定根路径,否则图片将无法加载,此为生产环境高频踩坑点。第四,底层渲染流程体现iText-XMLWorker的核心机制:`ITextRenderer`本质是XMLWorker的HTML专用封装,其执行分三阶段:`setDocumentFromString()`完成HTML语法解析与DOM树构建;`layout()`触发CSS计算、盒模型布局、分页逻辑及字体度量;`createPDF(os)`最终调用iText底层API将渲染结果序列化为PDF二进制流。整个过程高度依赖`SharedContext`上下文管理,其中不仅包含字体解析器,还控制着图片缓存、URI处理器、事件监听器等扩展点,为深度定制(如水印添加、页眉页脚注入、数字签名集成)预留接口。最后,工程实践层面需关注诸多细节:`OutputStream`必须显式`close()`与`flush()`防止文件写入不全;异常捕获需区分`DocumentException`(布局错误)、`IOException`(IO异常)、`RuntimeException`(字体缺失等);多线程环境下`ITextRenderer`非线程安全,须按请求新建实例;HTML语义完整性至关重要——缺少``闭合标签可能导致布局错乱;CSS支持有限,复杂Flex/Grid布局、@media查询、JavaScript交互均不被识别,故应遵循“语义清晰、样式简洁、结构扁平”的HTML编写原则。综上,该方案不仅是代码片段,更是贯通前端HTML规范、Java字节码运行机制、PDF文件结构标准与中文信息处理理论的综合性技术范式,具备极高的学习价值与落地参考意义。
程序员大本营
java使用freemarker模板技术导出word
总之,Java 使用 FreeMarker 导出 Word 是一个灵活且强大的解决方案,尤其适用于需要大量动态生成文档的场景。
清澈@Cherry
3939
java根据模板导出PDF详细教程(包含统计图)
FreeMarker则是一个基于模板的Java模板引擎,常用于生成HTML、XML或其他格式的文档。它允许开发者定义模板,然后结合后端数据填充模板生成最终的文档。
qq_26676207
3103
Java实现HTML页面转PDF解决方案
"Java实现HTML页面转PDF的解决方案主要涉及两个关键组件:IText和Flying-Saucer。IText是一个强大的PDF处理库,适合处理简单的PDF生成任务,如将图片或自定义HTML模
weixin_38535221
981