如何在Freemarker模板中传页码调用Java工具类生成每页唯一码?
解决方案:Freemarker生成多页PDF并添加基于页码的唯一码
核心逻辑梳理
首先明确:Freemarker本身不负责分页,它只生成HTML/XHTML静态内容;分页是由后续的PDF渲染工具(如Flying Saucer、iText PdfHTML、wkhtmltopdf)根据CSS规则(page-break-after/page-break-before)驱动的。你之前用CSS实现的页码计数器,是渲染引擎内部的计数,Freemarker无法直接获取这个值传给工具类,所以必须换思路:在PDF渲染阶段,拿到真实页码后再调用工具类生成唯一码。
具体实现方案
根据常用的PDF渲染工具,分三种场景给出实现:
1. 使用Flying Saucer(XHTMLRenderer)
通过自定义页眉/页脚提供者,在渲染每页时获取页码并调用工具类:
// 自定义页脚提供者类 public class UniqueCodeFooterProvider implements PageFooterProvider { // 注入你的工具类实例 private final Utils utils; public UniqueCodeFooterProvider(Utils utils) { this.utils = utils; } @Override public String renderFooter(int pageNumber, int pageCount) { // 调用工具类生成唯一码 String uniqueCode = utils.generateUniqueNumber(pageNumber); // 返回页脚HTML片段 return "<div style='width:100%; text-align:center; font-size:12px; padding:8px;'>" + "页码:" + pageNumber + " | 唯一码:" + uniqueCode + "</div>"; } } // 在渲染PDF时绑定提供者 public void generatePdf(String freemarkerHtml, OutputStream output) throws Exception { ITextRenderer renderer = new ITextRenderer(); // 设置自定义页脚 renderer.getSharedContext().setPageFooterProvider(new UniqueCodeFooterProvider(yourUtilsInstance)); // 加载Freemarker生成的HTML renderer.setDocumentFromString(freemarkerHtml); renderer.layout(); renderer.createPDF(output); }
2. 使用iText 7 + PdfHTML
通过PdfPageEvent监听分页事件,在每页结束时绘制带唯一码的页脚:
// 自定义分页事件类 public class UniqueCodePageEvent extends PdfPageEventHelper { private final Utils utils; public UniqueCodePageEvent(Utils utils) { this.utils = utils; } @Override public void endPage(PdfWriter writer, Document document) { int pageNumber = writer.getPageNumber(); String uniqueCode = utils.generateUniqueNumber(pageNumber); // 绘制页脚到指定位置(底部居中) Canvas canvas = new Canvas(writer.getDirectContent(), document.getPageSize()); canvas.setFont(PdfFontFactory.createFont(StandardFonts.HELVETICA)); canvas.setFontSize(12); canvas.showTextAligned( String.format("页码:%d | 唯一码:%s", pageNumber, uniqueCode), document.getPageSize().getWidth() / 2, 20, // 距离底部的距离 TextAlignment.CENTER ); canvas.close(); } } // 渲染PDF时绑定事件 public void generatePdf(String freemarkerHtml, OutputStream output) throws Exception { PdfWriter writer = new PdfWriter(output); // 绑定分页事件 writer.setPageEvent(new UniqueCodePageEvent(yourUtilsInstance)); Document document = new Document(writer); // 转换Freemarker生成的HTML为PDF HtmlConverter.convertToDocument(freemarkerHtml, document); document.close(); }
3. 使用wkhtmltopdf
通过独立的页眉/页脚模板,结合wkhtmltopdf的内置变量传递页码:
- 准备页脚模板
footer.ftl:
<div style='width:100%; text-align:center; font-size:12px; padding:8px;'> 页码:[page] | 唯一码:${utils.generateUniqueNumber(pageNumber)} </div>
- 后台渲染页脚模板时,先预留wkhtmltopdf的页码占位符,再通过工具类生成对应唯一码(需要提前获取总页数,或者通过wkhtmltopdf的参数传递页码);或者更简单的方式是,在后端先调用工具类生成所有页码的唯一码,渲染页脚模板时传入对应页码的唯一码,再由wkhtmltopdf替换占位符。
关键注意事项
- 不要试图在Freemarker模板中直接引用
pageNumber:Freemarker生成内容时,PDF的分页还未发生,页码是未知的,必须在渲染阶段获取真实页码。 - CSS分页规则依然有效:你之前写的
page-break-after: always等CSS规则,还是用来控制内容的分页位置,渲染工具会根据这些规则拆分页面,然后触发分页事件。 - 工具类的线程安全:如果是多线程生成PDF,确保工具类的
generateUniqueNumber方法是线程安全的。
参考资料
- Flying Saucer官方文档:重点关注
PageHeaderProvider/PageFooterProvider的接口定义和使用示例,以及分页上下文的参数传递。 - iText 7 PdfHTML官方指南:查看自定义分页事件的章节,了解如何在PDF渲染过程中动态插入内容。
- wkhtmltopdf官方文档:学习页眉页脚模板的内置变量(如
[page]、[topage])和参数配置方式。
内容的提问来源于stack exchange,提问作者Václav
相关产品推荐
相关产品推荐

