SpringBoot实战(三十二)集成 ofdrw,实现 PDF 和 OFD 的转换、SM2 签署OFD
目录
-
- 一、OFD 简介
-
- 1.1 什么是 OFD?
- 1.2 什么是 版式文档?
- 1.3 为什么要用 OFD 而不是PDF?
- 二、ofdrw 简介
-
- 2.1 定义
- 2.2 Maven 依赖
- 2.3 ofdrw 的 13 个模块
- 三、PDF/文本/图片 转 OFD(ofdrw-conterver)
-
- 3.1 介绍:
- 3.2 Maven 依赖:
- 3.3 PDF转换OFD
- 3.5 文本转换OFD
- 3.6 图片转换OFD
- 四、OFD 转 图片/HTML/文本/PDF(ofdrw-conterver)
-
- 4.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 坐标签署OFD
- 5.4 骑缝签署OFD
- 5.5 验签 OFD
- OFDRW-Gitee地址: https://gitee.com/ofdrw/ofdrw
- OFDRW-GitHub地址: https://github.com/ofdrw/ofdrw
- gmhelper-GitHub地址: https://github.com/ZZMarquis/gmhelper
一、OFD 简介
1.1 什么是 OFD?
OFD
是开放版式文档(Open Fixed-layout Document)
的英文缩写,是我国国家版式文档格式标准——《GB/T 33190-2016电子文件存储与交换格式-版式文档》。
1.2 什么是 版式文档?
版式文档
是与Word(doc、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/ofdrw
GitHub地址: https://github.com/ofdrw/ofdrw
2.2 Maven 依赖
Maven 依赖如下:
<!-- ofdrw -->
<dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-full</artifactId><version>2.3.3</version>
</dependency>
2.3 ofdrw 的 13 个模块
- ofdrw-core OFD核心API,参考《GB/T 33190-2016 电子文件存储与交换格式版式文档》实现的基础数据结构。
- ofdrw-font 生成OFD字体相关。
- ofdrw-layout OFD布局引擎库,用于文档构建和渲染。
- ofdrw-pkg OFD文件的容器,用于文档的打包。
- ofdrw-reader OFD文档解析器,用于OFD的反序列化以及签名签章。
- ofdrw-sign OFD文档数字签章。
- ofdrw-gm 用于支持签章模块需要的国密电子签章数据结构。
- ofrw-crypto 用于实现《GM/T 0099-2020 开放版式文档密码应用技术规范》对OFD的密码相关功能。
- ofdrw-gv OFDRW 所有模块所共用的全局变量。
- ofdrw-converter OFD文档转换。
- ofdrw-tool OFD文档工具,文档合并、裁剪、重组。
- ofdrw-graphics2d 实现了AWT Graphics2D接口,生成OFD文档内容。
- ofdrw-full 上述所有模块整合包,用于简化依赖引入。
由于篇幅约束,这里我们只介绍 ofdrw-converter
、ofdrw-sign
两个模块对应的 OFD 转换 和 OFD 签署 两个功能。
三、PDF/文本/图片 转 OFD(ofdrw-conterver)
- 官方文档: https://gitee.com/ofdrw/ofdrw/blob/master/ofdrw-converter/doc/CONVERTER.md
3.1 介绍:
ofdrw-converter
提供了 将其它类型媒体文件或文档转换成 OFD 文档内容 的功能。ofdrw-converter 模块在2.0.0
之后开始提供其它文档或媒体类型向 OFD 文档转换功能。
核心接口类: org.ofdrw.converter.ofdconverter.DocConverter
核心方法签名:
void convert(Path filepath, int... indexes) throws GeneralConvertException;
3 个实现类:
接口实现类命名格式: 原媒体格式+Converter
- PDF文档:
PDFConverter
- 纯文本:
TextConverter
- 图片:
ImageConverter
注意:
convert()
方法的参数页码均从 0 起,例如文档中的第 1 页的 Index 也就是 0,并非所有媒体格式都有页码,在转换无页码的没给是,页码参数无效。
3.2 Maven 依赖:
<dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>2.3.3</version>
</dependency>
3.3 PDF转换OFD
将PDF中页面转换为OFD页面,采用PDFBox PDFRenderer
接口,以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
为15(15像素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/文本/PDF(ofdrw-conterver)
- 官方文档: https://gitee.com/ofdrw/ofdrw/blob/master/ofdrw-converter/doc/EXPORTER.md
4.1 介绍:
ofdrw-converter
的 OFDExporter
接口具有多个实现,其实现与导出的目的文档有关。
接口实现类命名格式为: 目标格式+Exporter
ofdrw-converter
支持导出为以下类型:
- 图片:
ImageExporter
- SVG矢量图形:
SVGExporter
- HTML网页:
HTMLExporter
- 纯文本:
TextExporter
- PDF文档:
PDFExporterIText
、PDFExporterPDFBox
4.2 Maven 依赖:
<dependency><groupId>org.ofdrw</groupId><artifactId>ofdrw-converter</artifactId><version>2.3.3</version>
</dependency>
4.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();
}
效果如下:
特有方法
用途
List<Path> 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();
}
效果如下:(背景已经变成透明色了)
特有方法
用途
List<Path> 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
警告:不推荐导出为PDF,OFD本身就是国产的板式文件,非特殊场景没有必要导出为PDF文件,该模块将进入LTS状态,不再持续更新!
导出OFD文档页面为PDF文件,该导出根据实现所使用的库不一致具有两种导出实现。
实现类:
org.ofdrw.converter.export.PDFExporterIText
org.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("s1="+s1);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;Map<String, Object> stringObjectMap = SESeal.buildBase64(seal, new CASignInterface() {//制章人私钥签名方法(需要调用CA密码机服务)@Overridepublic 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 坐标签署OFD
Java 实现代码如下所示:
/*** 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);List<Pos> 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("s1="+s1);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() {@Overridepublic 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 骑缝签署OFD
Java实现代码如下,除了 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);// 坐标签署
// List<ItemMapDTO> 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 验签 OFD
Java 实现代码如下:
/*** 验签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/145599784
2.一文读懂 OFD 文件格式:国产 PDF,关键,重要,https://www.ithome.com/0/521/264.htm
相关文章:
SpringBoot实战(三十二)集成 ofdrw,实现 PDF 和 OFD 的转换、SM2 签署OFD
目录 一、OFD 简介 1.1 什么是 OFD?1.2 什么是 版式文档?1.3 为什么要用 OFD 而不是PDF? 二、ofdrw 简介 2.1 定义2.2 Maven 依赖2.3 ofdrw 的 13 个模块 三、PDF/文本/图片 转 OFD(ofdrw-conterver) 3.1 介绍…...
SolidWorks使用显卡教程
操作步骤: 打开注册表编辑器 按下键盘上的 Win R 组合键,输入 regedit 并按回车键,打开注册表编辑器。 导航到显卡信息路径 在注册表中依次展开以下路径: plaintext HKEY_CURRENT_USER\Software\SolidWorks\SOLIDWORKS 2021\Per…...
mysql 查询进程查看并释放
在MySQL中,查看和管理进程(例如查询、连接等)是数据库维护和性能调优的重要部分。以下是一些常用的方法来查看MySQL进程并释放它们。 1. 查看进程 你可以使用SHOW PROCESSLIST命令来查看当前MySQL服务器上的所有进程。这个命令会显示正在执…...
C++代码2-多目标算法求解车辆路径规划
为了解决车辆路径规划问题,我们需要在同一模型中同时考虑多个目标,其中一个目标是降低运营总成本,而另一个目标是降低总的碳排放量。使用组合算法,包括人工蜂群算法(Artificial Bee Colony, ABC)、模拟退火算法(Simulated Annealing, SA)、以及多目标优化算法MODAD(Mu…...
JAVA学习*接口
接口 在生活中我们常听说USB接口,那接口是什么呢? 在Java中,接口相当于多个类的一种公共规范,是一种引用数据类型。 定义接口 public interface IUSB {public static final String SIZE "small";public abstract vo…...
Matplotlib
一、Matplotlib快速入门 学习目标 了解什么是matplotlib 为什么要学习matplotlib matplotlib简单图形的绘制 1、什么是Matplotlib 是专门用于开发2D图表(包括3D图表) 以渐进、交互式方式实现数据可视化 2、为什么要学习Matplotlib 可视化是在整个数据挖掘的关键辅助工…...
新版frp-0.61.0 实现泛解析域名穿透 以及 https启用
需要在公网服务器的域名解析平台 泛域名 *.aa.com 解析到frp 公网服务器的ip x.x.x.x 对于frpc.toml 文件的 serverAddr 绑定的ip 需要公网服务器放行 bindPort 对于的端口 frpc.toml serverPort 对于的的是 frps.toml bindPort 端口 frps.toml bindPort 7000 vhostHTTPP…...
HTTPS 加密过程详解
HTTPS 详解及其加密过程流程框架 HTTPS(Hypertext Transfer Protocol Secure)是一种基于 HTTP 协议的安全通信协议,通过 SSL/TLS 协议对传输数据进行加密和身份验证,解决了 HTTP 明文传输的安全隐患。以下是其核心原理和加密流程的…...
lua垃圾回收
lua垃圾回收 lua 垃圾回收 lua 垃圾回收 collectgarbage(“count”)获取当前lua脚本占用内存字节数(单位为KB)。 collectgarbage(“collect”)执行一次垃圾回收。 xxxnil 将变量置为空,会释放内存。 lua中的机制和c#中回收机制很类似 解除羁绊(置为空)。 --垃圾回…...
springboot继承使用mybatis-plus举例相关配置,包括分页插件以及封装分页类
以下是使用 MyBatis-Plus 分页插件的完整配置和封装步骤,包括日志输出、驼峰转下划线、逻辑删除以及分页属性类的封装。 1. 引入依赖 确保在 pom.xml 中已经引入 MyBatis-Plus 的依赖: <XML> <dependency><groupId>com.baomidou<…...
智能汽车以太网系统测试:聚焦ETH链路高负载性能表现
引言 在智能汽车高速发展的今天,车载以太网作为车辆信息交互的“神经网络”,承担着传输海量数据的关键使命。它不仅能够满足车辆对高带宽、低延迟和高可靠性的通信需求,还为自动驾驶、智能座舱等复杂功能提供了强大的技术支持。然而…...
学习笔记:黑马程序员JavaWeb开发教程(2025.3.21)
10.7 案例-员工管理-分页查询-分析 形参的默认值可以使用注解来设置:RequestParam(default “1”) 10.8 案例-员工管理-分页查询-PageHelper插件 分页插件PageHelper帮助完成有关分页的所有操作,我们只要正常使用查询语句就可以了。插件会自动…...
计算机网络精讲day1——计算机网络的性能指标(上)
性能指标1:速率 概念1:比特 英文全称是binary digit,意思是一个二进制数字,因此一个比特就是二进制数字中的1或0,比特也是信息论中使用的信息量单位。 概念2:速率 网络中的速率指的是数据的传送速率&#…...
gin-路由handler封装思路
约束handler入参和返回为func(ctx, req) (resp, error)。通过反射,封装handler,在调用前后写入入参和返回的处理。 package testingimport ("context""fmt""reflect""strings""testing" )type ReqPa…...
【实战案例】用STAR+3W模型拆解电商支付系统设计文档
各位开发者朋友,上次分享了结构化写作的黄金公式后,很多同学反馈需要更具象的落地方法。今天通过真实电商支付系统案例,手把手教你用STAR3W模型写出可执行的设计文档! 结构化写作的「黄金公式」 STAR原则 3W模型 Situation&…...
计算机组成原理和计算机网络常见单位分类及换算
计算机组成原理(主要用于存储、内存、缓存等) 计算机网络(主要用于传输速率) 直观对比...
线性筛法求素数
时间复杂度 o(n) int cnt, primes[N];//cnt用来记录素数的下标 bool st[N];//用来标记合数 int minp[N];//最小质因数 void get_primes(int n) {for(int i 2;i < n;i )//从2开始找数 {if(!st[i])//如果这个数没有被筛出去过,说明是一…...
触动精灵对某东cookie读取并解密--记lua调用C语言
在Mac上构建Lua扩展模块:AES解密与Base64解码实战 今天我要分享一个实用技术:如何在Mac系统上为Lua编写和编译C扩展模块,特别是实现一个某东iOS PIN码解密功能的扩展。这对于需要在Lua环境中执行高性能计算或使用底层系统功能的开发者非常有…...
GEO:在AI时代抢占DeepSeekC位?
前言:当SEO遇见AGI——一场静默的流量革命 在生成式AI日均处理53亿次查询的今天,传统SEO的「关键词-排名-点击」逻辑正在崩塌。DeepSeek、ChatGPT、豆包等大模型用动态生成的答案,悄然截流了68%的搜索需求。更残酷的是:当用户问&q…...
【设计模式】策略模式
以下是格式优化后的Markdown文档,仅调整代码缩进,保持内容不变: 四、策略模式 策略(Strategy) 模式是一种行为型模式,其实现过程与模板方法模式非常类似——都 是以扩展的方式支持未来的变化。本章通过对一个具体范例的逐步重构…...
第J6周:ResNeXt-50实战解析
文章目录 一、前期准备1.设置GPU2.导入数据3.查看数据 二、数据预处理1.加载数据2.可视化数据3.再次检查数据4.配置数据集 四、模型复现1. 分组卷积模块2. 定义残差模块3. 堆叠残差单元4. 搭建ResNext-50网络5. 查看模型摘要 五、训练模型六、结果可视化总结: &…...
调试 ResNet18 cpp实现中的段错误(SIGSEGV)问题
调试 ResNet18 cpp实现中的段错误(SIGSEGV)问题 问题分析 您的程序在运行时遇到了段错误(SIGSEGV),GDB显示错误发生在main()函数的第一行(resnet18_allo_test.cpp:33)。这种情况看起来很奇怪&…...
详细介绍IDI_APPLICATION和IDC_ARROW
书籍:《windows程序设计(第五版)》 环境:visual studio 2022 内容:HELLOWIN程序 说明:以下内容大部分来自腾讯元宝。 IDI_APPLICATION 与 IDC_ARROW 详解 1. IDC_ARROW(光标资源标识符) 定义与…...
curl库+openssl库windows编译
一、工具准备 Visual Studio 2008:确保安装了 C 开发工具。 Git:用于克隆 cURL 的源码。 Perl:可以从 ActiveState Perl 下载并安装。 NASM(可选):如果需要汇编优化,可以从NASM 官方网站 下载并…...
今日行情明日机会——20250321
后续投资机会分析 结合2025年3月21日盘面数据(涨停56家,跌停31家),市场呈现结构性分化行情,海洋经济成为绝对主线,机器人概念局部活跃,人工智能表现较弱。以下是具体方向与策略建议:…...
repo init 错误 Permission denied (publickey)
一、已经生成ssh-key并设置到gerrit上 二、已经设置.gitconfig (此步骤是公司要求,设置gerrit地址为一个别名之类的,有的公司不需要) 然后出现下面的错误,最后发现忘记设置git的用户名和邮箱 1. git config --globa…...
STM32 模拟SPI 模式0
SPI 模式 0 的时钟极性(CPOL)为 0,时钟相位(CPHA)为 0。CPOL 0 意味着时钟信号空闲时为低电平,CPHA 0 表示在时钟信号的第一个跳变沿(上升沿)进行数据采样。 #include "stm3…...
MySQL实现全量同步和增量同步到SQL Server或其他关系型库
在将MySQL中的两张表同步到SQL Server的过程中,全量同步和增量同步各有其优缺点。全量同步简单直接但可能耗时较长且资源消耗大,而增量同步则更加高效但需要额外的逻辑来处理数据的变更。以下是对这两种同步方式的详细解释及代码示例的完善。 完整代码示…...
详细解析GetOpenFileName()
书籍:《Visual C 2017从入门到精通》的2.3.8 Win32控件编程 环境:visual studio 2022 内容:【例2.34】打开文件对话框和另存为。 说明:以下内容大部分来自腾讯元宝。 GetOpenFileName() 是 Windows API 中用于显示标准文件打开…...
FPGA----完美解决Windows下[XSIM 43-3409][XSIM 43-3915]错误
大家好久不见,今天开始又要重操旧业了!最近会更新很多关于petalinux的踩坑日记,敬请期待! 先更新一个常见问题,使用Vivado仿真时C编译器报错问题。如下所示 ERROR: [XSIM 43-3409] Failed to compile generated C fi…...
LeetCode Hot100 刷题路线(Python版)
目录 1. LeetCode Hot100 刷题笔记(1)—— 哈希、双指针、滑动窗口 2. LeetCode Hot100 刷题笔记(2)—— 子串、普通数组、矩阵 3. LeetCode Hot100 刷题笔记(3)—— 链表 4. LeetCode Hot100 刷题笔记&…...
宇树科技纯技能要求总结
一、嵌入式开发与硬件设计 核心技能 嵌入式开发: 精通C/C,熟悉STM32、ARM开发熟悉Linux BSP开发及驱动框架(SPI/UART/USB/FLASH/Camera/GPS/LCD)掌握主流平台(英伟达、全志、瑞芯微等) 硬件设计:…...
Docker学习笔记(十)搭建Docker私有仓库
一、环境配置 1、宿主机系统:macOS Sequoia(版本15.2) 2、虚拟机VMware Fusion版本:专业版 13.6.2 (24409261) 3、虚拟机系统:AlmaLinux-9-latest-x86_64-boot.iso 二、安装Harbor开源企业级Docker镜像 Harbor 是一个开源的企业级 Docker…...
FastAPI WebSocket 无法获取真实 IP 错误记录
FastAPI WebSocket 无法获取真实 IP 错误记录 问题描述 在使用 FastAPI WebSocket 服务时,发现无法获取设备的真实 Remote IP,所有连接均显示为内网地址 10.x.x.1。以下是完整的排查过程及解决方案。 排查步骤 1. 基础信息检查 • 现象复现࿱…...
DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例4,TableView15_04导出当前页数据示例
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能示例4,TableView15_04导出当…...
【Linux】快速上手Makeflie CMake
🦄个人主页:修修修也 🎏所属专栏:Linux ⚙️操作环境:Xshell (操作系统:Ubuntu 22.04 server 64bit) 目录 📌快速上手Makefile 基本结构 变量 自动变量 常用目标 📌快速上手CMake CMake与Makefile的关系 CMake的使用步骤 常用命令…...
Python连接数据库进行增删改查
更多优质文章 _>_>_>_>_>✍✈✉戳我 目录 1.导入相关库 2.创建连接 3.插入数据 4.删除数据 5.修改数据 6.查询数据 7.更多干货 1.导入相关库 import pymysql -----pip install pymysql #下载库 2.创建连接 conn pymysql.connect(hostlocalho…...
数据库的两种模式
数据库的 严格模式(Strict Mode) 和 宽松模式(Non-Strict Mode) 是数据库管理系统(DBMS)中用于控制数据验证和处理方式的两种不同模式。它们的主要区别在于对数据完整性、一致性和错误处理的严格程度。 1. …...
【css酷炫效果】纯CSS实现立体旋转立方体
【css酷炫效果】纯CSS实现立体旋转立方体 缘创作背景html结构css样式完整代码效果图 想直接拿走的老板,链接放在这里:https://download.csdn.net/download/u011561335/90492014 缘 创作随缘,不定时更新。 创作背景 刚看到csdn出活动了&am…...
Cursor与Coze结合开发电影推荐系统:一次高效的技术实践
1 项目背景 有个想法,和朋友打算一起看电影,但是不知道看什么(吃饭也是),于是在豆瓣高分电影榜单中选择出来一些感兴趣的电影,随机挑选一部“天意之选”。为此,我尝试结合Cursor(智…...
【总结篇】java多线程,新建线程有几种写法,以及每种写法的优劣势
java多线程 新建线程有几种写法,以及每种写法的优劣势 [1/5]java多线程 新建线程有几种写法–继承Thread类以及他的优劣势[2/5]java多线程-新建线程有几种写法–实现Runnable接口以及他的优劣势[3/5]java多线程 新建线程有几种写法–实现Callable接口结合FutureTask使用以及他的…...
idea问题(三)pom文件显示删除线
一、问题 1、现象 2、原因 分析原因和出现的流程:创建子模块的时候因为名称错误了,并且通过修改模块模块名称后,又删除了模块,因删除不干净。再次建立了同名模块,会让IDEA认为你再次新建的项目是已经被删除的项目。 …...
python爬虫概述
0x00 python爬虫概述 以豆瓣的选电影模块为例,当查看源代码搜索猫猫的奇幻漂流瓶是搜不到的 这时服务器的工作方式应该是这样的 客户端浏览器第一次访问其实服务器端是返回的一个框架(html代码) 当客户端浏览器第二次通过脚本等方式进行访问时服务器端才返回的数据…...
实现拖拽图片验证的基本步骤
前端部分 UI 设计: 显示一个滑块和一张背景图(通常是带缺口的图片)。滑块可以是拼图的一块或简单的方块。 拖拽功能: 监听滑块的 mousedown、mousemove、mouseup 事件,实现拖拽效果。 验证逻辑: 计算滑块最…...
conda报错activate没办法激活环境
遇到激活环境报错 # >>>>>>>>>>>>>>>>>>>>>> ERROR REPORT <<<<<<<<<<<<<<<<<<<<<< Traceback (most recent call last): File …...
numpy学习笔记3:三维数组 np.ones((2, 3, 4)) 的详细解释
numpy学习笔记3:三维数组 np.ones((2, 3, 4)) 的详细解释 以下是关于三维数组 np.ones((2, 3, 4)) 的详细解释: 1. 三维数组的形状 形状 (2, 3, 4) 表示: 最外层维度:2 个“层”(或“块”); …...
论文笔记(七十三)Gemini Robotics: Bringing AI into the Physical World
Gemini Robotics: Bringing AI into the Physical World 文章概括1. 引言2. Gemini 2.0的具身推理2.1. 具身推理问答(ERQA)基准测试2.2. Gemini 2.0的具身推理能力2.3. Gemini 2.0支持零样本和少样本机器人控制 3. 使用 Gemini Robotics 执行机器人动作3…...
不用 Tomcat?SpringBoot 项目用啥代替?
在SpringBoot框架中,我们使用最多的是Tomcat,这是SpringBoot默认的容器技术,而且是内嵌式的Tomcat。 同时,SpringBoot也支持Undertow容器,我们可以很方便的用Undertow替换Tomcat,而Undertow的性能和内存使…...
ChatTTS 开源文本转语音模型本地部署 API 使用和搭建 WebUI 界面
ChatTTS(Chat Text To Speech),专为对话场景设计的文本生成语音(TTS)模型,适用于大型语言模型(LLM)助手的对话任务,以及诸如对话式音频和视频介绍等应用。支持中文和英文,还可以穿插笑声、说话间的停顿、以…...
嵌入式笔记 | 正点原子STM32F103ZET6 4 | 中断补充
1. 外设引脚重映射 1.1 定义 在STM32中,每个外设的引脚都有默认的GPIO端口,但有些引脚可以通过重映射寄存器将功能映射到其他端口。这种机制称为引脚重映射,主要用于解决引脚复用冲突或优化PCB布线。 1.2 重映射的类型 部分重映射&#x…...