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。
引入
<dependency>
<groupId>plus.ruoyi</groupId>
<artifactId>officia-all</artifactId>
<version>1.1.6</version>
</dependency>电子发票 → 结构化数据
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() 取,并把样本反馈给我们补齐。
签章验证
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
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()、其余内容照常输出); 渐变与底纹按近似纯色;文字裁剪区不生效(按不裁处理)。
文本与其它读取
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 |
| 转 PDF | toPdf(byte[]) / toPdf(byte[], ConvertOptions) / toPdf(File) / toPdf(File, File) / convertToPdf |
| 读取 | extractText / extractTextByPage / pageCount / pageSizes / metadata / attachments |