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

能否程序化向Asciidoc模板注入数据生成发票?求技术方向

嘿,我完全懂你找了好久没头绪的感觉——其实用AsciidoctorJ向Asciidoc模板注入数据生成发票类文档是完全可行的,给你几个具体的技术方向参考:

1. 用AsciidoctorJ属性(Attributes)注入基础键值对数据

这是最直接的方案,Asciidoctor本身支持通过属性传递动态值,适合客户名称、发票编号、日期这类单一值数据。

  • 你的Asciidoc模板可以这样写(用{属性名}做占位符):
    = 正式发票
    :customer-name: {customer-name}
    :invoice-id: {invoice-id}
    :invoice-date: {invoice-date}
    
    客户名称: {customer-name}
    发票编号: {invoice-id}
    开票日期: {invoice-date}
    
  • 然后在Java代码里通过Attributes对象注入数据,再触发PDF转换:
    // 初始化属性并注入数据
    Attributes attributes = Attributes.builder()
        .attribute("customer-name", "张三科技有限公司")
        .attribute("invoice-id", "INV-2024-0078")
        .attribute("invoice-date", "2024-05-20")
        .build();
    
    // 创建Asciidoctor实例并执行转换
    Asciidoctor asciidoctor = Asciidoctor.Factory.create();
    asciidoctor.convertFile(
        new File("invoice-template.adoc"),
        Options.builder()
            .attributes(attributes)
            .backend("pdf")
            .outputDir(new File("output"))
            .build()
    );
    
2. 自定义预处理器(Preprocessor)处理复杂列表/表格数据

如果发票包含商品明细这类需要循环生成的复杂数据,属性就不够用了。这时候可以写自定义预处理器,在渲染前动态生成Asciidoc内容。

思路是:在模板里留一个标记(比如[[invoice-items]]),预处理器读取这个标记后,把你的商品列表转换成Asciidoc表格语法,替换掉标记。

  • 示例预处理器代码:
    public class InvoiceItemsPreprocessor implements Preprocessor {
        private List<InvoiceItem> itemList;
    
        public InvoiceItemsPreprocessor(List<InvoiceItem> itemList) {
            this.itemList = itemList;
        }
    
        @Override
        public Reader process(Document document, Reader reader) {
            try {
                // 读取原始模板内容
                String templateContent = IOUtils.toString(reader);
                // 生成Asciidoc表格格式的商品明细
                StringBuilder itemsTable = new StringBuilder();
                itemsTable.append("[cols=\"1,3,2,2,2\", width=\"100%\"]\n");
                itemsTable.append("|===\n");
                itemsTable.append("|序号|商品名称|单价|数量|小计\n");
                
                int index = 1;
                for (InvoiceItem item : itemList) {
                    double subtotal = item.getPrice() * item.getQuantity();
                    itemsTable.append(String.format(
                        "|%d|%s|%.2f|%d|%.2f\n",
                        index++, item.getName(), item.getPrice(), item.getQuantity(), subtotal
                    ));
                }
                itemsTable.append("|===\n");
                
                // 替换模板中的标记
                String processedContent = templateContent.replace("[[invoice-items]]", itemsTable.toString());
                return new StringReader(processedContent);
            } catch (IOException e) {
                throw new RuntimeException("处理模板失败", e);
            }
        }
    }
    
  • 注册预处理器并执行转换:
    Asciidoctor asciidoctor = Asciidoctor.Factory.create();
    // 注册自定义预处理器,传入商品数据
    asciidoctor.javaExtensionRegistry().preprocessor(new InvoiceItemsPreprocessor(getInvoiceItems()));
    // 执行PDF转换
    asciidoctor.convertFile(new File("invoice-template.adoc"), Options.builder().backend("pdf").build());
    
3. 结合通用模板引擎(Freemarker/Thymeleaf)生成完整Asciidoc

如果你的发票逻辑特别复杂(比如需要条件判断、嵌套循环),可以先把Asciidoc当成模板,用Freemarker这类成熟的模板引擎先填充数据生成完整的Asciidoc文件,再用AsciidoctorJ转PDF。

  • 示例Freemarker模板(invoice-template.ftl):
    = 正式发票
    客户名称: ${customerName}
    发票编号: ${invoiceId}
    开票日期: ${invoiceDate}
    
    [cols="1,3,2,2,2"]
    |===
    |序号|商品名称|单价|数量|小计
    <#list items as item>
    |${item_index + 1}|${item.name}|${item.price?string("0.00")}|${item.quantity}|${(item.price * item.quantity)?string("0.00")}
    </#list>
    |===
    
  • 先用Freemarker生成.adoc文件,再调用AsciidoctorJ转换即可。

内容的提问来源于stack exchange,提问作者Gabriel Tiongco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:11:44