Skip to content

Officia.Ofd — OFD 读取、电子发票解析、签章验证与转 PDF ​

能力摘要(AI 友好):OfficiaOfd 读取 OFD(GB/T 33190-2016):parseInvoice 把 OFD 电子发票解析成结构化数据;verify 验证电子签章(GB/T 38540 / GM/T 0031,SM2 + SM3,含税务局发票签章);extractText 按阅读顺序抽取文本;toPdf 转 PDF(内嵌字体、矢量印章、超链接与大纲都保留)。门面静态方法,纯 JDK 实现,国密算法自研。Word 转 OFD 见 Officia.Words。

引入 ​

xml
<dependency>
  <groupId>plus.ruoyi</groupId>
  <artifactId>officia-all</artifactId>
  <version>1.1.6</version>
</dependency>

电子发票 → 结构化数据 ​

java
import plus.ruoyi.officia.ofd.OfficiaOfd;
import plus.ruoyi.officia.ofd.invoice.OfdInvoice;

OfdInvoice inv = OfficiaOfd.parseInvoice(ofdBytes);   // 也有 File / InputStream 重载
if (inv.isRecognized()) {
    inv.getInvoiceNumber();       // 发票号码
    inv.getIssueDate();           // 2020-10-04(统一为 yyyy-MM-dd)
    inv.getSellerName();          // 销售方
    inv.getTotalAmount();         // 价税合计 "200.00"(去掉 ¥ 与千分位)
    inv.getItems();               // 明细行:名称、规格、单位、数量、单价、金额、税率、税额
    inv.getRawFields();           // 发票里的全部原始字段:路径 → 值
}

数据来源按可信度合并:先读 OFD 附件里的发票原始 XML(税务系统直接生成),再读自定义标引 CustomTags (字段名 → 页面文字)。字段名按本地名匹配并内置新旧票种别名,标准字段之外的内容一律在 getRawFields() 里。 不是发票时 isRecognized() 为 false、不抛异常——批量处理混杂文件时不用先判断类型。

方法说明
getInvoiceCode() / getInvoiceNumber()发票代码(数电票无)/ 号码
getIssueDate() / getCheckCode() / getMachineNumber()开票日期 / 校验码 / 机器编号
getBuyerName() / getBuyerTaxId() …购买方名称、税号、地址电话、开户行账号
getSellerName() / getSellerTaxId() …销售方同上
getAmountExcludingTax() / getTaxAmount() / getTotalAmount()合计金额 / 税额 / 价税合计
getPayee() / getReviewer() / getDrawer() / getRemark()收款人 / 复核 / 开票人 / 备注
getSources()实际用到的数据来源

数电票(全电发票)

已内置数电票常见字段名的别名。不同地区、不同时期的票面结构仍有差异, 接入前请用你的真实票样跑一遍;缺的字段可先从 getRawFields() 取,并把样本反馈给我们补齐。

签章验证 ​

java
OfdSignatureVerification v = OfficiaOfd.verify(ofdBytes);
if (v.isValid()) {
    for (OfdSignatureVerification.Signature s : v.getSignatures()) {
        s.getSealName();     // 印章名称,如「国家税务总局江苏省税务局」
        s.getSignerName();   // 签章人证书 CN
        s.getSignTime();
    }
} else {
    log.warn(v.getSummary());   // 哪个签名、哪个文件被改动
}

每个签名做三项检查,缺一不可:受保护文件逐个重算杂凑;签章中的原文杂凑与当前 Signature.xml 比对; 用签章人证书的 SM2 公钥验证签名值。getUnprotectedFiles() 列出签完后被塞进包里、不受任何签名保护的文件。

验证 ≠ 信任

verify 只做密码学验证(文件没被改、签名确由该证书签出),不校验证书链、吊销状态与印章制发方。 需要判断"这个章是不是真的税务局章",请结合业务系统的信任根与查验渠道。验签不受授权门控。

OFD → PDF ​

java
byte[] pdf = OfficiaOfd.toPdf(ofdBytes);
OfficiaOfd.toPdf(new File("in.ofd"), new File("out.pdf"));

// 需要页数与告警时
OfficiaOfd.OfdToPdfResult r = OfficiaOfd.convertToPdf(ofdBytes, ConvertOptions.defaults().setFontDirectory("/fonts"));
r.getWarnings();   // 未能完全还原的内容,如无法解码的图像
  • 每个字按 OFD 给出的落点单独定位:即使改用本机字体,字的位置与原文件一致
  • 内嵌子集字体没有可用 cmap 时,按文档里的字形映射重建,转出的 PDF 复制文字是原文
  • 电子签章外观照常显示:PNG / JPEG 印章与矢量(OFD)印章都画
  • 超链接、大纲、文档属性一并带到 PDF
  • 发票二维码等 JBIG2 图像(GBIG2)自研解码后照常显示,转出的二维码可直接扫码

当前限制:用到符号字典 / 文本区域 / 半调 / MMR 编码的 JBIG2 图像暂不支持(记入 getWarnings()、其余内容照常输出); 渐变与底纹按近似纯色;文字裁剪区不生效(按不裁处理)。

文本与其它读取 ​

java
String text = OfficiaOfd.extractText(ofd);            // 页间以 \f 分隔
List<String> pages = OfficiaOfd.extractTextByPage(ofd);
int n = OfficiaOfd.pageCount(ofd);
Map<String, String> meta = OfficiaOfd.metadata(ofd);   // 自定义元数据键为 custom.名称
Map<String, byte[]> files = OfficiaOfd.attachments(ofd);

评估版限制 ​

未加载 officia.lic 时:

操作评估版表现
转 PDF限 30 页并带评估水印
文本抽取只返回前 30 页、开头带评估提示;若是发票,文字中的号码、金额、税号等同样部分遮盖
发票解析字段齐全但关键值部分遮盖(inv.isEvaluation() 为 true),可联调、不可入账
元数据标准项照常;custom.* 自定义项的值部分遮盖(电子发票把号码、金额写在这里)
附件返回空表
验签 / 页数 / 页面尺寸不受限

原则是渲染可试用、结构化数据要授权:转出的 PDF 画面是真实内容(带水印),人眼可以核对; 能被程序直接拿去入账的数据才需要授权。

Word → OFD 属于 Words 能力,计入 words 模块码(与 Word → PDF 同口径),只授权 ofd 不会解除其限制。

方法一览 ​

分类方法
发票parseInvoice(byte[] / File / InputStream) → OfdInvoice
验签verify(byte[]) → OfdSignatureVerification
转 PDFtoPdf(byte[]) / toPdf(byte[], ConvertOptions) / toPdf(File) / toPdf(File, File) / convertToPdf
读取extractText / extractTextByPage / pageCount / pageSizes / metadata / attachments