目录一、OFD 简介1.1 什么是 OFD1.2 什么是 版式文档1.3 为什么要用 OFD 而不是PDF二、ofdrw 简介2.1 定义2.2 Maven 依赖2.3 ofdrw 的 13 个模块三、PDF/文本/图片 转 OFDofdrw-conterver3.1 介绍3.2 Maven 依赖3.3 PDF转换OFD3.5 文本转换OFD3.6 图片转换OFD四、OFD 转 图片/HTML/文本/PDFofdrw-conterver4.1 介绍4.2 Maven 依赖4.3 导出为图片4.4 导出为SVG图形4.5 导出为HTML网页4.6 导出为文本4.7 [不推荐] 导出为PDF五、OFD签署5.1 在线生成 sm2 证书5.2 制作 esl 印章5.3 坐标签署OFD5.4 骑缝签署OFD5.5 验签 OFDOFDRW-Gitee地址https://gitee.com/ofdrw/ofdrwOFDRW-GitHub地址https://github.com/ofdrw/ofdrwgmhelper-GitHub地址https://github.com/ZZMarquis/gmhelper一、OFD 简介1.1 什么是 OFDOFD是开放版式文档Open Fixed-layout Document的英文缩写是我国国家版式文档格式标准——《GB/T 33190-2016电子文件存储与交换格式-版式文档》。1.2 什么是 版式文档版式文档是与Worddoc、docx等流式文件相对的具有格式独立、版本固定、固化呈现的文档。版式文档不宜修改且在不同设备中显示效果不变而流式文档会根据设备版面显示发生变化。举例来说一个 doc 格式的 Word 文档使用 Word 与 WPS 打开容易发生版面样式变化、内容重排现象同一篇 Word 文档在 Office 的不同版本中打开也会发生不一致的情况。而版式文档则是不受设备影响版式固定。在版式、版面、字体、字号等方面与纸质文档保持完全一致。版式文档格式的特点使它称为严肃类电子文档发布、数字化信息传播和存档的理想文档格式。版式文档的代表就是我们工作和生活中非常熟悉的PDF文档OFD文档则是我国自主研发自主指定的版式文件格式标准。在 PDF基础上加入了许多基于我国社会发展需要的应用场景功能。可以说 OFD 与 PDF 定位一致同为版式文档格式但 OFD 后发制人青出于蓝。1.3 为什么要用 OFD 而不是PDF既然 PDF 和 OFD 都是版式文件那么问题来了PDF 用得好好的我们为什么要自己再做一个 OFD 的文件格式呢主要出于以下几点原因格式不统一目前在国内可使用的版式文件格式包括PDF、CEB等在内有不下十种来自不同厂商和不同的技术没有统一标准就会导致不同机构、不同企业之间的文件交流存在阻碍文件长期存档很困难。难以自主可控我们知道在目前办公文档市场上来自 Adobe 的 PDF 五一占据绝对主流而在 PDF 阅读工具的市场上Adobe 旗下的Adobe Acrobat DC又以55.18%的市占率位于领先地位国内厂商 Foxit福昕昕xin一声虽然排名第二但占有率仅有1.92%。这背后有一个严重的问题就是在版式文件技术上我们目前难以做到自主可控如果我们相对文件做一些针对国内特殊领域的技术扩展时极容易受制于外部厂商或者如果未来 Adobe 停止技术授权那可能有很多文档遭受损失。此外OFD 作为我们自主研发、自主可控的版式文件格式相比 PDF 等其他版式文件OFD 有一些技术上的优势第一OFD 文档内部采用可扩展标记语言XML来描述数据和结构体积精简安全开放易于扩展。第二OFD支持国产加密算法具有全面的安全保障体系可防止信息被窃取并且和数字签名技术集合可防篡改更加安全。第三永久刻度可用可对文件长久保存且可以精准呈现文件的版式内容在不同场景、设备下都能保持一致性。第四支持直接进行文件归档的一系列处理。这些优势总结起来就是在 PDF 基础上加入了许多基于我国社会发展需要的应用场景功能。二、ofdrw 简介2.1 定义ofdrw全称OFD Reader Writer意为 OFD 读写器是开源的 OFD 处理库支持文档生成、数字签名、文档保护、文档合并、转换、导出等功能。Gitee地址https://gitee.com/ofdrw/ofdrwGitHub地址https://github.com/ofdrw/ofdrw2.2 Maven 依赖Maven 依赖如下!-- ofdrw -- dependency groupIdorg.ofdrw/groupId artifactIdofdrw-full/artifactId version2.3.3/version /dependency2.3 ofdrw 的 13 个模块ofdrw-coreOFD核心API参考《GB/T 33190-2016 电子文件存储与交换格式版式文档》实现的基础数据结构。ofdrw-font生成OFD字体相关。ofdrw-layoutOFD布局引擎库用于文档构建和渲染。ofdrw-pkgOFD文件的容器用于文档的打包。ofdrw-readerOFD文档解析器用于OFD的反序列化以及签名签章。ofdrw-signOFD文档数字签章。ofdrw-gm用于支持签章模块需要的国密电子签章数据结构。ofrw-crypto用于实现《GM/T 0099-2020 开放版式文档密码应用技术规范》对OFD的密码相关功能。ofdrw-gvOFDRW 所有模块所共用的全局变量。ofdrw-converterOFD文档转换。ofdrw-toolOFD文档工具文档合并、裁剪、重组。ofdrw-graphics2d实现了AWT Graphics2D接口生成OFD文档内容。ofdrw-full上述所有模块整合包用于简化依赖引入。由于篇幅约束这里我们只介绍ofdrw-converter、ofdrw-sign两个模块对应的OFD 转换和OFD 签署两个功能。三、PDF/文本/图片 转 OFDofdrw-conterver官方文档https://gitee.com/ofdrw/ofdrw/blob/master/ofdrw-converter/doc/CONVERTER.md3.1 介绍ofdrw-converter提供了将其它类型媒体文件或文档转换成 OFD 文档内容的功能。ofdrw-converter 模块在2.0.0之后开始提供其它文档或媒体类型向 OFD 文档转换功能。核心接口类org.ofdrw.converter.ofdconverter.DocConverter核心方法签名void convert(Path filepath, int... indexes) throws GeneralConvertException;3 个实现类接口实现类命名格式原媒体格式ConverterPDF文档PDFConverter纯文本TextConverter图片ImageConverter注意convert()方法的参数页码均从 0 起例如文档中的第 1 页的 Index 也就是 0并非所有媒体格式都有页码在转换无页码的没给是页码参数无效。3.2 Maven 依赖dependency groupIdorg.ofdrw/groupId artifactIdofdrw-converter/artifactId version2.3.3/version /dependency3.3 PDF转换OFD将PDF中页面转换为OFD页面采用PDFBoxPDFRenderer接口以AWT graphics2d接口桥接并通过ofdrw-graphics2d 模块完成转换功能。实现类org.ofdrw.converter.ofdconverter.PDFConverter注意事项转换后的页面将采用PDF中页面尺寸。目前该转换器任然有改进空间可能存在部分特性在转换过程中丢失显示效果与原PDF文档不一致。示例import org.ofdrw.converter.ofdconverter.PDFConverter; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; /** * pdf 转 ofd */ private static void pdf2Ofd() { Path src Paths.get(D:\test.pdf); Path dst Paths.get(D:\test.ofd); try (PDFConverter converter new PDFConverter(dst)) { converter.convert(src); } catch (IOException e) { log.error(pdf 转 ofd 失败请稍后重试原因{}, e.getMessage(), e); throw new RuntimeException(pdf 转 ofd 失败请稍后重试, e); } System.out.println(pdf 转 ofd 成功路径 dst.toAbsolutePath()); }特有方法用途void setEnableCopyAttachFiles(boolean enableCopyAttachFiles)设置是否复制附件默认复制。void setEnableCopyBookmarks(boolean enableCopyBookmarks)设置是否复制书签默认复制。void setUUPMM(double UUPMM)设置毫米表示的用户单元数PDF单位详见 测试用例执行结果3.5 文本转换OFD将文本转换为OFD。实现类org.ofdrw.converter.ofdconverter.TextConverter注意事项文本文件为无格式文件若您需要设置文本格式请使用ofdrw-layout模块。示例import org.ofdrw.converter.ofdconverter.TextConverter; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; /** * text 转 ofd */ private static void text2Ofd() { Path src Paths.get(D:\test.txt); Path dst Paths.get(D:\test.ofd); try (TextConverter converter new TextConverter(dst)) { converter.convert(src); converter.convert(src); converter.convert(src); } catch (IOException e) { log.error(text 转 ofd 失败请稍后重试原因{}, e.getMessage(), e); throw new RuntimeException(text 转 ofd 失败请稍后重试, e); } System.out.println(text 转 ofd 成功路径 dst.toAbsolutePath()); }特有方法用途void append(String txt)追加文本文本将新起一行。void setPageSize(PageLayout pageLayout)设置OFD页面尺寸。void setFontSize(double fontSize)设置字号单位毫米。详见 测试用例执行结果3.6 图片转换OFD导入图片到OFD中图片格式支持PNG、BPM、JPG。实现类org.ofdrw.converter.ofdconverter.ImageConverter注意事项可以通过构造器指定导出的图片类型目前支持PNG、JPG、BPM默认为PNG格式。若图片格式不在上述范围您可能需要通过手动的方式设置加入图片大小。可以通过方法设置导出图片的质量也就是ppm参数默认ppm为1515像素1毫米。每个添加的图片都将独立为一页并且居中。示例import org.ofdrw.converter.ofdconverter.ImageConverter; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; /** * img 转 ofd */ private static void img2Ofd() { Path src Paths.get(D:\signature.png); Path dst Paths.get(D:\test.ofd); try (ImageConverter converter new ImageConverter(dst)) { // 可以加多个图片每张图片一页 converter.convert(src); converter.convert(src); converter.convert(src); } catch (IOException e) { log.error(img 转 ofd 失败请稍后重试原因{}, e.getMessage(), e); throw new RuntimeException(img 转 ofd 失败请稍后重试, e); } System.out.println(img 转 ofd 成功路径 dst.toAbsolutePath()); }特有方法用途void setPPM(double ppm)设置图片质量单位为每毫米像素数量。void append(Path filepath, double width, double height)追加图片到新页面并指定显示大小。void setPageSize(PageLayout pageLayout)设置OFD页面尺寸。详见 测试用例执行结果四、OFD 转 图片/HTML/文本/PDFofdrw-conterver官方文档https://gitee.com/ofdrw/ofdrw/blob/master/ofdrw-converter/doc/EXPORTER.md4.1 介绍ofdrw-converter的OFDExporter接口具有多个实现其实现与导出的目的文档有关。接口实现类命名格式为目标格式Exporterofdrw-converter支持导出为以下类型图片ImageExporterSVG矢量图形SVGExporterHTML网页HTMLExporter纯文本TextExporterPDF文档PDFExporterIText、PDFExporterPDFBox4.2 Maven 依赖dependency groupIdorg.ofdrw/groupId artifactIdofdrw-converter/artifactId version2.3.3/version /dependency4.3 导出为图片导出OFD文档页面为图片图片格式支持PNG、BPM、JPG。实现类org.ofdrw.converter.export.ImageExporter注意事项可以通过构造器指定导出的图片类型目前支持PNG、JPG、BPM默认为PNG格式。可以通过构造器或方法设置导出图片的质量也就是ppm参数默认ppm为15。导出图片将存放于同一个目录在该目录中图片以的页面索引作为文件名如第1页的文件名为0.png。示例Path ofdPath Paths.get(src/test/resources/999.ofd); Path imgDirPath Paths.get(target/999.ofd/); try (ImageExporter exporter new ImageExporter(ofdPath, imgDirPath, PNG, 20d)) { exporter.export(); }效果如下特有方法用途ListPath getImgFilePaths()获取导出页面对应图片文件路径列表中次序与导出时的页码次序一致。void setPPM(double ppm)设置导出图片质量单位为每毫米像素数量。详见 测试用例4.4 导出为SVG图形导出OFD文档页面为SVG图形文本中的所有文字都将转换为矢量路径。实现类org.ofdrw.converter.export.SVGExporter注意事项可以通过构造器或方法设置导出SVG图形大小也就是ppm参数默认ppm为15。导出SVG图形文件将存放于同一个目录在该目录中以页面索引作为文件名如第1页的文件名为0.svg。示例Path ofdPath Paths.get(src/test/resources/999.ofd); Path svgPath Paths.get(target/999.ofd/); try (SVGExporter exporter new SVGExporter(ofdPath, svgPath, 15d)) { exporter.export(); }效果如下背景已经变成透明色了特有方法用途ListPath getSvgFilePaths()获取导出页面对应SVG文件路径列表中次序与导出时的页码次序一致。void setPPM(double ppm)设置导出SVG大小单位为每毫米像素数量。详见 测试用例4.5 导出为HTML网页导出OFD文档页面为HTML网页需要浏览器支持HTML5才可正常预览由于是基于SVG方案导出的HTML因此导出文件可能较大。实现类org.ofdrw.converter.export.HTMLExporter注意事项若您需要调整HTML网页样式可以通过继承HTMLExporter并覆盖header、booter、margin_bottom属性使用自定义的HTML样式。导出的HTML网页需要浏览器支持HTML5才可正常预览。若页面文字内容由文字图元构成且都由Unicode组成那么导出网页可能可以通过鼠标选中与复制。示例Path ofdPath Paths.get(src/test/resources/n.ofd); Path htmlPath Paths.get(target/n.html); try (HTMLExporter exporter new HTMLExporter(ofdPath, htmlPath)) { exporter.export(); }效果如下详见 测试用例4.6 导出为文本导出OFD文档页面为文本文件并非所有OFD页面都能导出文本只有符合特定条件的OFD才可导出。实现类org.ofdrw.converter.export.TextExporter注意事项部分OFD文档由于采用字形索引来定位文字、有个OFD整个页面均为路径数据图元而不是文字图元、有的OFD页面整个都为图片等诸多原因无法保证一定能够导出文本。由于文本布局等各种因素导出文本顺序也难以与原文文本顺序一致。示例Path ofdPath Paths.get(src/test/resources/999.ofd); Path txtPath Paths.get(target/999.txt); try (TextExporter exporter new TextExporter(ofdPath, txtPath)) { exporter.export(); }效果如下详见 测试用例4.7 [不推荐] 导出为PDF警告不推荐导出为PDFOFD本身就是国产的板式文件非特殊场景没有必要导出为PDF文件该模块将进入LTS状态不再持续更新导出OFD文档页面为PDF文件该导出根据实现所使用的库不一致具有两种导出实现。实现类org.ofdrw.converter.export.PDFExporterITextorg.ofdrw.converter.export.PDFExporterPDFBox注意事项导出无法保证文档效果一致性若您有建设性意见请提交PR。基于PDFBox实现示例Path ofdPath Paths.get(src/test/resources/999.ofd); Path pdfPath Paths.get(target/999.pdf); try (OFDExporter exporter new PDFExporterPDFBox(ofdPath, pdfPath)) { exporter.export(); }详见 基于PDFBox导出 测试用例基于iText实现示例Path ofdPath Paths.get(src/test/resources/999.ofd); Path pdfPath Paths.get(target/999.pdf); try (OFDExporter exporter new PDFExporterIText(ofdPath, pdfPath)) { exporter.export(); }详见 基于iText导出 测试用例效果如下五、OFD签署5.1 在线生成 sm2 证书在线生成地址https://www.gmcrt.cn/gmcrt/index.jsp生成证书如下所示5.2 制作 esl 印章Java 实现代码如下/** * 生成ESL印章 */ public static void buildEsl() throws IOException, CertificateEncodingException { String imagePath D:\signature.jpg; String sealerCertPath D:\keystore\sm2.p12; String imageBase64 java.util.Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(new File(imagePath))); Path userP12Path Paths.get(sealerCertPath); PrivateKey sealerPrvKey; try { byte[] bytes FileUtils.readFileToByteArray(new File(sealerCertPath)); sealerPrvKey NativePKCS12Tools.ReadPrvKey(java.util.Base64.getEncoder().encodeToString(bytes), ACGkaka, 123456); } catch (Exception e) { throw new RuntimeException(e); } Certificate signCert null ; try { signCert PKCS12Tools.ReadUserCert(userP12Path, ACGkaka, 123456); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } byte[] encoded signCert.getEncoded(); String s1 java.util.Base64.getEncoder().encodeToString(encoded); System.out.println(s1s1); System.out.println(imageBase64:imageBase64); long start System.currentTimeMillis(); Calendar now Calendar.getInstance(); now.add(Calendar.YEAR, 2); Date then now.getTime(); BuildSESealBase64 seal new BuildSESealBase64().setEsID(UUID.randomUUID().toString().replace(-, ))//印章ID .setSealImageBase64(imageBase64)//印章图片base64 .setSealName(ACGkaka测试章)//印章名称 .setBuildSealerCertBase64(s1)//制章人公钥 .setHeight(40)//印章高度单位毫米 .setWidth(40)//印章宽度单位毫米 .setSesheader(chinamobile sign)//印章头部信息 .setUseSealerCertBase64(s1)//用章人公钥 .setValidStartDate(new Date())//印章有效期 传空,固定读用章人证书有效 .setValidEndDate(then);//印章有效期传空,固定读用章人证书有效 try { PrivateKey finalSealerPrvKey sealerPrvKey; MapString, Object stringObjectMap SESeal.buildBase64(seal, new CASignInterface() {//制章人私钥签名方法(需要调用CA密码机服务) Override public byte[] sign(byte[] data) { Signature sg null; try { sg Signature.getInstance(SM3WithSM2, new BouncyCastleProvider()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } try { sg.initSign(finalSealerPrvKey); } catch (InvalidKeyException e) { e.printStackTrace(); } try { sg.update(data); } catch (SignatureException e) { e.printStackTrace(); } byte[] sigVal new byte[0]; try { sigVal sg.sign(); } catch (SignatureException e) { e.printStackTrace(); } System.out.println(sigVal.length); return sigVal; } }); String s stringObjectMap.get(base64).toString(); FileUtils.writeByteArrayToFile(new File(D:\keystore\sm2-signature.esl), java.util.Base64.getDecoder().decode(s)); } catch (Exception e) { e.printStackTrace(); } long end System.currentTimeMillis(); System.out.println(执行耗时间(end-start)/1000); }生成 ESL 印章如下所示5.3 坐标签署OFDJava 实现代码如下所示/** * OFD坐标签 */ private static void signByXy() throws GeneralSecurityException, IOException { Pos pos new Pos(); pos.setPage(1); pos.setX(100); pos.setY(100); pos.setWidth(40); pos.setHeigh(40); ListPos apList new ArrayList(); apList.add(pos); String req D:\test.ofd; String reqbase java.util.Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(new File(req))); String eslpath D:\keystore\sm2-signature.esl; String eslbase java.util.Base64.getEncoder().encodeToString(FileUtils.readFileToByteArray(new File(eslpath))); Path userP12Path Paths.get(D:\keystore\sm2.p12); PrivateKey sealerPrvKey null ; try { sealerPrvKey PKCS12Tools.ReadPrvKey(userP12Path, ACGkaka, 123456); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } Certificate signCert null ; try { signCert PKCS12Tools.ReadUserCert(userP12Path, ACGkaka, 123456); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } byte[] encoded signCert.getEncoded(); String s1 java.util.Base64.getEncoder().encodeToString(encoded); System.out.println(s1s1); BuildOFDSignerBase64 buildOFDSigner new BuildOFDSignerBase64() .setOfdReqBase64(reqbase) .setEslBase64(eslbase) .setSignMode(0) .setUseSealerCertBase64(s1) .setApList(apList); byte[] bytes null; try { PrivateKey finalSealerPrvKey sealerPrvKey; bytes OFDSigner.signByXyBase64(buildOFDSigner, new CASignInterface() { Override public byte[] sign(byte[] data) { Signature sg null; try { sg Signature.getInstance(SM3WithSM2, new BouncyCastleProvider()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } try { sg.initSign(finalSealerPrvKey); } catch (InvalidKeyException e) { e.printStackTrace(); } try { sg.update(data); } catch (SignatureException e) { e.printStackTrace(); } byte[] sigVal new byte[0]; try { sigVal sg.sign(); } catch (SignatureException e) { e.printStackTrace(); } System.out.println(sigVal.length); return sigVal; } }); }catch (Exception e){ e.printStackTrace(); } FileUtils.writeByteArrayToFile(new File(D:\test3.ofd),bytes); }签署结果如下所示5.4 骑缝签署OFDJava实现代码如下除了 main() 方法之外还有 3 个方法public static void main(String[] args) throws IOException, GeneralSecurityException { byte[] pdfBytes FileUtils.readFileToByteArray(new File(D:\test_2页.ofd)); byte[] certBytes FileUtils.readFileToByteArray(new File(D:\keystore\sm2.p12)); String certBase64 Base64.getEncoder().encodeToString(certBytes); // 坐标签署 // ListItemMapDTO itemList getItemList(); // byte[] newPdfBytes OFDSignUtil.signSm2PDFBySeal(pdfBytes, itemList, els, certBase64); // FileUtils.writeByteArrayToFile(new File(D:\test\test2.ofd), newPdfBytes); // 骑缝签署 String side Right; double offset 40.0d; byte[] eslBytes FileUtils.readFileToByteArray(new File(D:\keystore\sm2-signature.esl)); String eslBase64 Base64.getEncoder().encodeToString(eslBytes); String pdfBase64 Base64.getEncoder().encodeToString(pdfBytes); byte[] bytes1 OFDSignUtil.signByRidingStampPos(pdfBase64, eslBase64, certBase64, side, offset); FileUtils.writeByteArrayToFile(new File(D:\test\test3.ofd), bytes1); } /** * 骑缝签署 * * param reqBase64 待签署文件base64 * param eslBase64 印章文件base64 * param certBase64 用户证书base64 * param side Left左骑缝 Right右骑缝(默认) * return 签署后文件 */ public static byte[] signByRidingStampPos(String reqBase64, String eslBase64, String certBase64, String side, double offset) { log.info(OFD骑缝签署开始); BuildOFDSignerRideBase64 buildOFDSigner new BuildOFDSignerRideBase64() .setOfdReqBase64(reqBase64) .setEslBase64(eslBase64) .setSignMode(0) .setUseSealerCertBase64(fileToInputStream(certBase64)) .setSide(side) .setOffset(offset); try { byte[] bytes OFDSigner.signByRidingStampPosBase64(buildOFDSigner, data - { try { PrivateKey sealerPrvKey NativePKCS12Tools.ReadPrvKey(certBase64, ACGkaka, 123456); Signature sg null; try { sg Signature.getInstance(SM3WithSM2, new BouncyCastleProvider()); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } try { sg.initSign(sealerPrvKey); } catch (InvalidKeyException e) { e.printStackTrace(); } try { sg.update(data); } catch (SignatureException e) { e.printStackTrace(); } byte[] sigVal new byte[0]; try { sigVal sg.sign(); } catch (SignatureException e) { e.printStackTrace(); } System.out.println(sigVal.length); return sigVal; } catch (Exception e) { log.error(OFD骑缝签署异常, e); throw new IllegalArgumentException(骑缝签署异常); } }); log.info(骑缝签署完成,签署后文件大小:{}kb, bytes null ? 0 : bytes.length / 1024); return bytes; } catch (Exception e) { log.error(OFD骑缝签署异常, e); throw new IllegalArgumentException(骑缝签署异常); } } /** * 读取证书内容 * param base64String * return */ public static String fileToInputStream(String base64String){ try { Certificate signCert PKCS12Tools.ReadUserCert(base64ToInputStream(base64String), ACGkaka, 123456); byte[] encoded signCert.getEncoded(); return Base64.getEncoder().encodeToString(encoded); } catch (IOException | GeneralSecurityException e) { throw new RuntimeException(e); } }签署结果如下所示5.5 验签 OFDJava 实现代码如下/** * 验签OFD */ public static void signVerify() throws IOException, GeneralSecurityException { Path out Paths.get(D:\test3.ofd); // 验证 try (OFDReader reader new OFDReader(out); OFDValidator validator new OFDValidator(reader)) { validator.setValidator(new SESV4ValidateContainer()); validator.exeValidate(); System.out.println( 验证通过); } }验签结果如下整理完毕完结撒花~ ??参考地址1.科普 | ofd文件是什么https://zhuanlan.zhihu.com/p/1455997842.一文读懂 OFD 文件格式国产 PDF关键重要https://www.ithome.com/0/521/264.htm