You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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的内置变量传递页码:

  1. 准备页脚模板footer.ftl:
<div style='width:100%; text-align:center; font-size:12px; padding:8px;'>
    页码:[page] | 唯一码:${utils.generateUniqueNumber(pageNumber)}
</div>
  1. 后台渲染页脚模板时,先预留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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 04:32:09