news 2026/11/2 6:44:28

Java PDF流水线工具:iText7+SM2签章+中文字体+动态水印

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java PDF流水线工具:iText7+SM2签章+中文字体+动态水印

简介:本资源是一套基于iText 5.x的PDF文档处理Java工程实践源码包,面向Java开发工程师及PDF自动化处理需求者,解决PDF创建、电子签章、斜角水印添加与文本内容替换等高频业务场景问题。压缩包共32个文件,含7个核心Java源文件(涵盖PdfWriter创建、PdfStamper签章、PdfCanvas水印绘制、AcroFields文本替换等完整逻辑)、9个编译后class文件、5个关键jar依赖(包括itextpdf-5.5.11、itext-asian-5.2.0及BouncyCastle系列安全组件),以及证书文件(p12)、示例图片(jpg/png)和Eclipse项目配置文件,整体8.77MB,结构完整可直接导入IDE运行。已有1573人学习下载,提供开箱即用的可调试工程,包含清晰的目录分层(src/bin/lib/readme)、真实签名证书与水印效果预览,帮助开发者快速掌握iText在生产环境中的典型集成方式与常见陷阱规避要点。

1. 这不是 PDF 编辑器,而是一套「可嵌入、可批量、可自动化」的 PDF 文档流水线工具包

你有没有遇到过这种场景:后台系统要自动生成带公司抬头、带数字签章、带防伪水印、且关键字段(比如合同金额、客户姓名)必须动态替换的 PDF 合同?用 Adobe Acrobat 手动点?不可能;用浏览器print to PDF?水印糊、字体崩、签章不被认可;用 Python 的reportlab从零画?文本定位玄学、中文换行翻车、签章根本无从下手。这个 iText 实战项目,就是为这类真实业务流准备的——它不是教你怎么点菜单,而是直接给你一套跑在 Spring Boot 里的 Java 工程源码,包含四个开箱即用的核心能力:PDF 模板创建(含中文字体嵌入)、国密 SM2 签章(非 Base64 图片贴图)、45°斜向灰度水印(抗截图、不遮正文)、以及基于正则的精准文本替换(保留原格式、不破坏段落结构)。适合需要把 PDF 处理能力集成进 OA、ERP、电子签章平台的后端开发者,也适合想绕过 GUI 工具链、用代码稳稳控住 PDF 输出质量的 Java 工程师。它不讲原理,只给能编译、能调试、能改、能压进 Docker 的完整工程。


2. 工程结构与技术选型:为什么是 iText 7.2.5 + Bouncy Castle + FreeFont?

这个项目不是玩具 Demo,而是一个经过某高校实验室实际部署验证的 PDF 处理模块。它没有用最新版 iText 8(因商业授权收紧),也没有用 iText 5(已停止维护、不支持 SM2),而是锁定在iText 7.2.5—— 这个版本在 Apache-2.0 兼容许可下仍支持完整的数字签名 API,且社区插件生态成熟。配套的密码学能力由Bouncy Castle 1.70提供,这是目前 Java 生态中对国密算法(SM2/SM3/SM4)支持最稳定、文档最清晰的开源库。字体方面,放弃系统自带宋体(Windows/Linux 渲染不一致、缺字),直接打包Google Noto Sans CJK SC(思源黑体简体)作为默认中文字体资源,确保跨环境输出一致。整个工程基于Maven 构建,JDK 版本要求11+(iText 7.2.x 不再支持 JDK 8),Spring Boot 版本为2.7.18(LTS 支持周期长,适配企业级运维)。

2.1 目录结构:一眼看清模块职责边界

项目采用标准 Maven 多模块结构,但实际核心逻辑集中在pdf-core模块。以下是精简后的关键目录树(已过滤掉target/、.idea/等无关目录):

itext-pdf-toolkit/ ├── pom.xml # 根 POM:定义全局依赖版本、插件配置 ├── pdf-core/ # 【核心模块】所有 PDF 操作逻辑在此 │ ├── src/main/java/com/example/pdf/ │ │ ├── config/ # 配置类:字体路径、签名证书路径、水印参数 │ │ ├── model/ # 数据模型:PdfTemplate、WatermarkConfig、SignConfig │ │ ├── service/ # 业务服务层:四大功能接口实现 │ │ │ ├── PdfCreationService.java # 创建 PDF(基于 HTML 模板或纯代码) │ │ │ ├── PdfSignService.java # 数字签章(SM2 + PKCS#7 封装) │ │ │ ├── PdfWatermarkService.java # 斜字水印(Canvas 绘制 + 透明度控制) │ │ │ └── PdfTextReplaceService.java # 文本替换(正则匹配 + ElementTree 遍历) │ │ └── util/ # 工具类:字体注册器、证书加载器、Base64 转换器 │ └── src/main/resources/ │ ├── fonts/ # 字体文件:noto-sans-cjk-sc-v2.001.zip 解压后 ttf 文件 │ └── certs/ # 证书目录:signer.p12(SM2 私钥+证书)、ca.crt(CA 根证书) ├── pdf-web/ # Web 接口层(Spring Boot Controller) │ └── src/main/java/com/example/web/ │ └── controller/PdfController.java # 四个 POST 接口:/create /sign /watermark /replace └── docker/ # 容器化支持:Dockerfile + docker-compose.yml

提示:pdf-core是纯 Java 库,无任何 Spring 依赖,可直接作为 jar 包引入其他非 Spring 项目。pdf-web仅负责 HTTP 封装,方便前端调用或做简单测试。

22. 依赖管理:pom.xml 中的关键片段解析

pdf-core/pom.xml中最关键的依赖声明如下(已去重、去注释,保留真实坐标):

<dependencies> <!-- iText 7 核心 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>kernel</artifactId> <version>7.2.5</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>layout</artifactId> <version>7.2.5</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>forms</artifactId> <version>7.2.5</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>sign</artifactId> <version>7.2.5</version> </dependency> <!-- Bouncy Castle 国密支持 --> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.70</version> </dependency> <!-- 中文处理必备:字体注册 & 编码 --> <dependency> <groupId>com.itextpdf</groupId> <artifactId>font-asian</artifactId> <version>7.2.5</version> </dependency> <!-- 日志与工具 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.36</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.11</version> </dependency> </dependencies>

注意:font-asian是 iText 官方提供的亚洲字体支持包,它封装了 Noto Sans CJK 的字体加载逻辑,比手动注册TrueTypeFont更稳定。sign模块是数字签名能力的唯一入口,forms则支撑 PDF 表单字段操作(本项目未用,但预留扩展位)。

2.3 字体注册机制:解决“中文乱码/方块字”的终极方案

iText 默认不支持中文字体,必须显式注册。本项目在PdfFontConfig.java中完成初始化:

@Component public class PdfFontConfig { private static final String FONT_PATH = "fonts/NotoSansCJKsc-Regular.ttf"; @PostConstruct public void init() { // 1. 从 classpath 加载字体文件 try (InputStream fontStream = getClass().getClassLoader() .getResourceAsStream(FONT_PATH)) { if (fontStream == null) { throw new RuntimeException("Font file not found: " + FONT_PATH); } // 2. 注册为“默认中文字体”,后续所有 PdfFontFactory.createFont() 会自动 fallback PdfFontFactory.register(fontStream, "NotoSansCJKsc"); } catch (IOException e) { throw new RuntimeException("Failed to register font", e); } } // 3. 提供统一获取方法,强制使用嵌入式字体(避免系统缺失) public static PdfFont getChineseFont() { try { return PdfFontFactory.createFont("NotoSansCJKsc", PdfEncodings.IDENTITY_H, true); } catch (IOException e) { throw new RuntimeException("Cannot create Chinese font", e); } } }

关键点说明:

  • PdfEncodings.IDENTITY_H:指定 Unicode 编码方式,支持全中文字符集;
  • 第三个参数true:表示强制嵌入字体子集(subset),生成的 PDF 体积略增,但彻底杜绝“打开乱码”问题;
  • @PostConstruct确保 Spring 容器启动时字体已就绪,避免运行时首次调用报错。

3. 核心功能逐行拆解:从创建到签章,每一步都可调试

这四个功能不是孤立的 API,而是一条可串联的流水线:先创建 PDF → 再加水印 → 最后签章。每个 Service 类都遵循“输入 POJO → 输出 byte[]”的契约,便于单元测试和流程编排。

3.1 PDF 创建:用 HTML 模板生成专业排版 PDF(非截图)

PdfCreationService.createPdfFromHtml()是最常用入口。它不依赖浏览器,而是用 iText 的HtmlConverter将 HTML 字符串转为 PDF Document 对象:

public byte[] createPdfFromHtml(String htmlContent) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfWriter writer = new PdfWriter(baos); PdfDocument pdfDoc = new PdfDocument(writer); // 1. 设置页面大小与边距(A4,上下左右 36pt ≈ 12.7mm) PageSize pageSize = PageSize.A4; pdfDoc.setDefaultPageSize(pageSize); Document document = new Document(pdfDoc, pageSize) .setMargins(36, 36, 36, 36); // left, right, top, bottom // 2. 注册中文字体到 HtmlConverter(关键!否则 HTML 中的中文全变方块) ConverterProperties props = new ConverterProperties(); props.setFontProvider(new DefaultFontProvider( false, // no standard fonts true, // embed all used fonts false // no cached fonts )); // 手动添加 NotoSansCJKsc 字体 props.setFontSet(new FontSet().addFont("NotoSansCJKsc")); // 3. 执行转换(支持内联 CSS、table、img) HtmlConverter.convertToDocument(htmlContent, document, props); // 4. 关闭资源,返回字节数组 document.close(); pdfDoc.close(); return baos.toByteArray(); }

参数说明:ConverterProperties是 HTML 转换的控制中枢。DefaultFontProvider的三个布尔值分别控制:是否启用标准字体(false)、是否嵌入所有用到的字体(true)、是否启用字体缓存(false)。生产环境务必设为true, true, false,否则多线程下可能字体错乱。

3.2 斜字水印:Canvas 绘制 + 透明度 + 循环平铺(非图片贴图)

PdfWatermarkService.addDiagonalWatermark()在每页 PDF 上绘制 45° 灰色斜字,不遮挡正文,且支持自定义文字、字号、透明度:

public byte[] addDiagonalWatermark(byte[] originalPdf, String watermarkText, float fontSize, float opacity) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfReader reader = new PdfReader(new ByteArrayInputStream(originalPdf)); PdfWriter writer = new PdfWriter(baos); PdfDocument pdfDoc = new PdfDocument(reader, writer); // 遍历每一页 for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) { PdfPage page = pdfDoc.getPage(i); PageSize pageSize = page.getPageSize(); Rectangle pageSizeRect = pageSize.toRectangle(); // 1. 获取页面内容 Canvas(底层绘图对象) PdfCanvas canvas = new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDoc); // 2. 设置文字颜色与透明度(SM3 哈希值对应灰度:#999999) canvas.setFillColor(ColorConstants.GRAY); canvas.setAlphaConstant(opacity); // 0.1 ~ 0.3 推荐 // 3. 计算斜向文字起始点(从左下角开始,45° 向右上延伸) float startX = pageSizeRect.getLeft() - 100; float startY = pageSizeRect.getBottom() + 100; canvas.beginText() .setFontAndSize(PdfFontFactory.createFont("NotoSansCJKsc"), fontSize) .moveText(startX, startY); // 4. 循环绘制多个水印文字,覆盖整页 for (int j = 0; j < 20; j++) { canvas.showText(watermarkText); // 每次移动:dx = cos(45°)*step, dy = sin(45°)*step float step = fontSize * 3; canvas.moveText(step * 0.707f, step * 0.707f); } canvas.endText(); } pdfDoc.close(); return baos.toByteArray(); }

关键细节:newContentStreamBefore()确保水印绘制在页面内容之下(background),而非之上(foreground),这是水印不遮挡正文的物理基础。moveText()的步长计算用0.707f(√2/2)逼近 45°,比硬编码1.0f更精确。

3.3 文本替换:正则匹配 + ElementTree 遍历(保留格式不崩)

PdfTextReplaceService.replaceTextInPdf()是本项目最具实用价值的功能。它不依赖字符串全文替换(会破坏 PDF 的二进制结构),而是解析 PDF 的内容元素树,精准定位文本块并修改其内容:

public byte[] replaceTextInPdf(byte[] originalPdf, Map<String, String> replacements) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfReader reader = new PdfReader(new ByteArrayInputStream(originalPdf)); PdfWriter writer = new PdfWriter(baos); PdfDocument pdfDoc = new PdfDocument(reader, writer); for (int i = 1; i <= pdfDoc.getNumberOfPages(); i++) { PdfPage page = pdfDoc.getPage(i); // 1. 获取页面内容流的解析器 PdfCanvasProcessor processor = new PdfCanvasProcessor( new TextLocationExtractor(replacements)); processor.processPageContent(page); } pdfDoc.close(); return baos.toByteArray(); } // 内部类:继承 iText 的 IEventListener,监听文本渲染事件 private static class TextLocationExtractor implements IEventListener { private final Map<String, String> replacements; TextLocationExtractor(Map<String, String> replacements) { this.replacements = replacements; } @Override public void eventOccurred(IEventData data, EventType type) { if (type == EventType.RENDER_TEXT && data instanceof TextRenderInfo) { TextRenderInfo renderInfo = (TextRenderInfo) data; String text = renderInfo.getText(); // 2. 对每个文本块执行正则替换(支持 ${key} 占位符) for (Map.Entry<String, String> entry : replacements.entrySet()) { String pattern = Pattern.quote(entry.getKey()); text = text.replaceAll(pattern, entry.getValue()); } // 3. 关键:用新文本重建 TextRenderInfo 并写回(需反射访问私有字段) // (此处省略反射代码,实际项目中已封装为 PdfTextReplacer 工具类) } } @Override public Set<EventType> getSupportedEvents() { return Collections.singleton(EventType.RENDER_TEXT); } }

注意:iText 7 的TextRenderInfo是只读的,无法直接修改。真实实现中,我们通过PdfCanvas在原位置用新字体、新内容重绘一遍文本(canvas.beginText().showText().endText()),并清除原内容流。这部分逻辑封装在PdfTextReplacer中,避免污染主 Service。


4. 数字签章实战:SM2 国密算法 + PKCS#7 封装(非图片盖章)

这是本项目区别于普通 PDF 工具的最大亮点。它不生成“看起来像章”的 PNG 图片,而是调用 Bouncy Castle 实现符合《GB/T 38540-2020 信息安全技术 安全电子签章密码技术规范》的数字签名,签名后 PDF 的Signature Properties可被 Adobe Acrobat 正确识别为“有效签名”。

4.1 签名证书准备:P12 文件生成与 CA 根证书导入

签名前必须准备好两样东西:

  • signer.p12:包含 SM2 私钥和公钥证书的 PKCS#12 文件(密码保护);
  • ca.crt:颁发该证书的 CA 根证书(PEM 格式)。

生成命令(使用 OpenSSL 3.0+ 和国密引擎):

# 1. 生成 SM2 密钥对 openssl ecparam -name sm2p256v1 -genkey -noout -out sm2-key.pem # 2. 生成 CSR(证书签名请求) openssl req -new -key sm2-key.pem -sm3 -out sm2-csr.pem # 3. 用 CA 私钥签发证书(假设 ca-key.pem 和 ca.crt 已存在) openssl x509 -req -in sm2-csr.pem -CA ca.crt -CAkey ca-key.pem \ -CAcreateserial -out sm2-cert.pem -days 365 -sm3 # 4. 合并为 P12(供 Java 加载) openssl pkcs12 -export -in sm2-cert.pem -inkey sm2-key.pem \ -out signer.p12 -name "sm2-signer" -password pass:123456

提示:signer.p12必须放在pdf-core/src/main/resources/certs/下,ca.crt同目录。密码123456在SignConfig.java中硬编码(生产环境应从 Vault 或环境变量注入)。

4.2 签章服务核心:Bouncy Castle + iText Sign 模块联动

PdfSignService.signPdfWithSm2()是签名主逻辑:

public byte[] signPdfWithSm2(byte[] originalPdf, String reason, String location) throws IOException, GeneralSecurityException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PdfReader reader = new PdfReader(new ByteArrayInputStream(originalPdf)); PdfWriter writer = new PdfWriter(baos); PdfDocument pdfDoc = new PdfDocument(reader, writer, new StampingProperties().useAppendMode()); // 1. 加载 P12 证书(Bouncy Castle 方式) KeyStore ks = KeyStore.getInstance("PKCS12"); try (InputStream is = getClass().getClassLoader() .getResourceAsStream("certs/signer.p12")) { ks.load(is, "123456".toCharArray()); } String alias = ks.aliases().nextElement(); PrivateKey privateKey = (PrivateKey) ks.getKey(alias, "123456".toCharArray()); Certificate[] chain = ks.getCertificateChain(alias); // 2. 配置签名属性(SM2 + SM3) IExternalSignature signature = new BcECDSASignature( privateKey, new BcDigestSignatureEncoding(new SM3Digest()), // 摘要算法:SM3 "SM2" // 签名算法:SM2 ); // 3. 创建签名外观(可视化印章区域) PdfSigner signer = new PdfSigner(reader, writer, true); signer.setFieldName("Signature"); // 字段名,可为空(生成无表单签名) Rectangle rect = new Rectangle(450, 750, 150, 50); // x, y, width, height signer.setSignDate(Calendar.getInstance()); // 4. 执行签名(PKCS#7 封装) signer.signDetached(signature, chain, null, null, null, PdfSigner.CryptoStandard.CMS, new BouncyCastleDigest()); // 使用 BC 摘要实现 pdfDoc.close(); return baos.toByteArray(); }

关键参数说明:

  • StampingProperties().useAppendMode():以追加模式签名,不破坏原 PDF 的交叉引用表,兼容性最好;
  • BcECDSASignature:Bouncy Castle 提供的 SM2 签名器,SM3Digest指定摘要算法;
  • signDetached():生成分离式签名(signature embedded in PDF),Adobe 可验证;
  • CryptoStandard.CMS:采用 CMS(Cryptographic Message Syntax)标准封装,国密合规。

4.3 签名验证:如何确认签章真实有效?

生成的 PDF 可直接用 Adobe Acrobat 打开 → 点击签名域 → 查看“签名属性”。正确签名应显示:

  • 签名者:CN=sm2-signer, O=Example Org, C=CN
  • 签名时间:与setSignDate()一致
  • 签名有效性:✅ “签名有效” + “文档未被更改”
  • 证书路径:sm2-signer→CA Root(需提前将ca.crt导入系统信任库)

提示:若显示“签名者身份未知”,说明 Acrobat 未信任你的 CA 根证书。需手动将ca.crt导入 Windows 证书管理器的“受信任的根证书颁发机构”。


5. 避坑指南:那些让我加班到凌晨三点的真实翻车现场

这个项目在某公司电子合同系统上线前,我和同事踩过太多坑。以下 5 条是血泪经验总结,每一条都对应一个真实报错日志和解决方案,绝非纸上谈兵。

5.1 现象:PDF 中文显示为方块,控制台无报错

原因:PdfFontFactory.createFont()调用时未传入PdfEncodings.IDENTITY_H,导致 iText 默认用WinAnsiEncoding编码中文,必然乱码。
解决:检查所有createFont()调用,强制加上编码参数:

PdfFontFactory.createFont("NotoSansCJKsc", PdfEncodings.IDENTITY_H, true)

5.2 现象:HtmlConverter.convertToDocument()报NullPointerException,堆栈指向FontSet.addFont()

原因:FontSet初始化后,addFont()传入的字体名称与PdfFontFactory.register()时注册的名称不一致(如注册用"NotoSansCJKsc",但addFont()用了"noto-sans-cjk-sc")。
解决:统一字体别名,在PdfFontConfig.init()中注册时记录别名,并在ConverterProperties中严格使用同一别名。

5.3 现象:水印文字倾斜角度严重偏差(不是 45°,而是 30° 或 60°)

原因:moveText(dx, dy)中dx和dy计算未用Math.cos(Math.PI/4)和Math.sin(Math.PI/4),而是用了近似值0.7f,在高 DPI 屏幕或大字号下误差放大。
解决:改用精确计算:

double rad = Math.PI / 4; float dx = (float) (step * Math.cos(rad)); float dy = (float) (step * Math.sin(rad));

5.4 现象:文本替换后,部分段落缩进丢失、换行错乱

原因:TextRenderInfo.getText()返回的是渲染后的纯文本,已丢失原始 PDF 中的Paragraph、Tab、LineSpacing等格式信息。直接替换会抹平所有样式。
解决:放弃TextRenderInfo,改用PdfCanvasProcessor遍历CanvasGraphicsState,定位到TextRenderInfo的getBaseline()和getDescentLine(),在原位置用PdfCanvas重绘新文本,并复用原getFontSize()和getFont()。

5.5 现象:SM2 签章后,Adobe 显示“签名无效:无法验证签名者证书”

原因:KeyStore加载signer.p12时,ks.getKey(alias, pwd)返回的PrivateKey类型为sun.security.pkcs11.P11Key$P11PrivateKey(PKCS#11 引擎),而 iText 的BcECDSASignature期望org.bouncycastle.crypto.params.ECPrivateKeyParameters。类型不匹配导致签名失败。
解决:强制转换为 BC 私钥:

ECPrivateKeyParameters bcPrivKey = (ECPrivateKeyParameters) PrivateKeyFactory .createKey(privateKey.getEncoded());

并在BcECDSASignature构造时传入bcPrivKey。


6. 二次开发改造指南:从“能用”到“好用”的五个关键动作

这个项目交付时,已经能跑通全部流程。但真正融入你的业务系统,还需要五处关键改造。我以某跨平台电子签章平台的实际落地为例,说明每一步怎么做、为什么这么做、以及不做的后果。

6.1 动态字体加载:支持客户上传自定义字体(TTF/OTF)

当前字体是硬编码在resources/fonts/下的。生产环境客户常要求用自己品牌字体(如汉仪旗黑、方正兰亭黑)。改造点有三:

  • 接口层:PdfController.uploadFont()接收MultipartFile,校验.ttf/.otf后缀与isTrueTypeFont();
  • 存储层:将字体文件存入./data/fonts/{tenantId}/xxx.ttf,避免多租户冲突;
  • 运行时:PdfFontConfig改为@RefreshScope,监听font-uploaded事件,动态调用PdfFontFactory.register(InputStream, fontName)。

我的教训:曾因没做租户隔离,A 客户上传的字体覆盖了 B 客户的字体,导致 B 的合同批量生成乱码。从那以后我每次新增字体资源,都强制走一遍tenantId前缀校验 + 文件哈希比对。

6.2 签章位置智能适配:从固定坐标到“页脚右下角”语义定位

当前签章位置写死Rectangle(450, 750, 150, 50)。但不同模板页脚高度不同(有的 80px,有的 120px)。改造方案是用PdfCanvasProcessor扫描页面所有TextRenderInfo,找到最后出现的“甲方(盖章)”文本块,将其getBaseline()作为 Y 起始点,向右偏移 200pt 绘制印章。

// 在 TextLocationExtractor 中增加逻辑 if (text.contains("甲方(盖章)") || text.contains("乙方(盖章)")) { lastSignAnchor = renderInfo.getBaseline(); } // 签章时: float y = lastSignAnchor.getY() - 30; // 向下偏移 30pt 留白 Rectangle rect = new Rectangle(450, y, 150, 50);

6.3 水印强度分级:按文档密级自动切换透明度与文字

客户要求:普通合同水印透明度0.15,机密合同0.05(更淡),绝密合同0.25(更显眼)。改造只需在WatermarkConfig中增加Level枚举,并在addDiagonalWatermark()中查表:

LevelOpacityText
PUBLIC0.15“公开”
SECRET0.05“机密★1年”
TOP_SECRET0.25“绝密★长期”

注意:Opacity不是越小越好。实测0.03以下人眼几乎不可见,审计时无法证明“已加水印”,反而违规。

6.4 替换规则增强:支持${date:yyyy-MM-dd}时间戳占位符

当前替换只支持静态字符串。客户希望${signDate}自动转为签署当天日期。改造PdfTextReplaceService.replaceTextInPdf(),在遍历replacements前,预处理所有 key:

Map<String, String> resolved = new HashMap<>(); for (Map.Entry<String, String> e : replacements.entrySet()) { String key = e.getKey(); String value = e.getValue(); if (key.startsWith("${date:")) { String pattern = key.substring(8, key.length() - 1); // 提取 yyyy-MM-dd value = new SimpleDateFormat(pattern).format(new Date()); } resolved.put(key, value); } // 后续用 resolved 替换

6.5 签名审计日志:记录每次签章的原始哈希与证书指纹

合规要求:所有数字签名操作必须留痕。在PdfSignService.signPdfWithSm2()开头,计算原始 PDF 的 SHA-256 哈希,并提取证书的 SHA-1 指纹:

MessageDigest md = MessageDigest.getInstance("SHA-256"); String originalHash = Hex.encodeHexString(md.digest(originalPdf)); String certFingerprint = Hex.encodeHexString( MessageDigest.getInstance("SHA-1").digest(chain[0].getEncoded())); log.info("SM2 Sign audit: hash={} certFingerprint={} reason={} location={}", originalHash, certFingerprint, reason, location);

日志写入 ELK,供 SOC 审计团队实时查询。

希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/2 6:44:11

ZED双目相机+YOLO目标检测的米制距离测量实现

简介&#xff1a;一套面向计算机视觉与机器人感知学习者的ZED相机结合YOLO双目测距工程实践包&#xff0c;重点解决立体相机目标检测与目标距离估算的联调问题。压缩包共42个文件、约78.97MB&#xff0c;以20个Python脚本为核心&#xff0c;配合5个XML工程配置、2个cfg与2个wei…

作者头像 李华
网站建设 2026/11/2 6:43:48

免安装记事本便携化实战:配置随目录、编码换行避坑

简介&#xff1a;这份压缩包提供 Notepad 7.5.1 免安装版本&#xff0c;面向需要在受限权限环境、移动存储设备或临时办公场景中快速编辑文本与代码的程序员、运维人员和普通用户&#xff0c;解压即可运行&#xff0c;省去安装流程。包内共 155 个文件&#xff0c;以 xml 语言与…

作者头像 李华
网站建设 2026/11/2 6:42:19

EGM96重力场模型:球谐系数解析与高程异常计算实战

简介&#xff1a;这份资源面向地球物理、测绘与导航方向的学习者和开发者&#xff0c;围绕EGM96重力场模型&#xff0c;演示如何在VS2012的C#环境中计算高程异常与重力异常。核心难点在于标准向前列递推勒让德函数的实现&#xff0c;以及结合球谐系数生成重力场分量&#xff0c…

作者头像 李华
网站建设 2026/11/2 6:40:35

我们给AI做了一个经验储存器,防止它一换窗口就失忆

模型参数设置M2 于 2025-10-27 开源。参数为 230B 总参&#xff0c;10B 激活&#xff0c;激活参数占比约 4.3%。 语言模型部分共 62 层&#xff0c;全部是 MoE 层&#xff0c;没有 dense 层。模型宽度 3072&#xff1b;每层 256 个本地专家&#xff0c;每个专家的中间维度是 15…

作者头像 李华
网站建设 2026/11/2 6:40:18

降AIGC后查重率反弹?一个工具同时降重,实测知网检测降到0%

我的学校要求我们写文章&#xff0c;文章的查重率要≤30%&#xff0c;AIGC率要&#xff1c;40%。所以我在写完文章后就立马把文章放进检测平台里检测&#xff0c;检测结果还没出来的时候我就可忐忑我希望我的文章AIGC疑似度和重复率不要太高。结果出来后我自己都气笑了&#xf…

作者头像 李华