在 Java 中使用 GroupDocs 添加数字签名 PDF
如果您正在构建处理合同、发票或任何法律文档的 Java 应用程序,您可能已经遇到这样的问题:如何在不从头开始构建的情况下,为 PDF 添加合法有效的数字签名 java?
手动签署文档既慢又容易出错,还会在工作流中形成瓶颈。如果能够只用几行 Java 代码就自动完成整个签署过程,那会怎样?
本指南正是为此而写。使用 GroupDocs.Signature for Java,您将学习如何以编程方式为 PDF、Word 文档以及其他格式添加数字签名——包括可视化签名、证书验证以及完善的异常处理。
您将掌握的内容:
- 在 Maven 或 Gradle 项目中配置 GroupDocs.Signature(仅需 2 分钟)
- 使用数字证书和可选签名图片对文档进行签署
- 处理常见错误并排查证书问题
- 了解何时使用数字签名以及何时使用其他签名类型
- 为生产环境实现安全最佳实践
无论您是构建合同管理系统、自动化发票工作流,还是为 SaaS 产品添加电子签名功能,本教程都能满足需求。让我们开始吧。
快速答案
- 哪个库支持 digital signature PDF java? GroupDocs.Signature for Java。
- 基本的 PDF 签名需要多少行代码? 只需两行:加载文档并调用
sign。 - 生产环境需要许可证吗? 是的,商业许可证可去除水印并解锁全部功能。
- 可以签署 Word、Excel 和 PowerPoint 文件吗? 当然——GroupDocs.Signature 支持 50 多种格式。
- 是否必须进行证书管理? 对于加密签名,必须使用
.pfx证书;请妥善保存。
什么是 digital signature PDF java?
Digital signature PDF java 指的是使用 Java 代码对 PDF 文件应用加密签名的过程。该过程会生成防篡改的封印,可通过签署者的公钥证书进行验证,从而为签署的文档提供法律效力、完整性保护和不可否认性。
digital signature 在 Java 中如何工作?
数字签名使用签署者的私钥对文档生成唯一哈希值,然后将该哈希嵌入签名对象中。拥有相应公钥的任何人都可以重新计算哈希并确认文档未被修改,从而实现法律不可否认性和完整性验证。
为什么选择 GroupDocs.Signature 进行数字签名?
GroupDocs.Signature 支持 50+ 输入和输出格式,可在不将整个文件加载到内存的情况下处理上百页的 PDF,并提供内置的可视化签名渲染。其 API 抽象了格式特定的细节,让您只需编写一套代码即可同时处理 PDF、DOCX、XLSX、PPTX 等多种文档。
前置条件
在开始编码之前,请确保以下必备条件已就绪:
必需的库和依赖
- GroupDocs.Signature for Java 版本 23.12(最新稳定版)
- 数字证书文件(
.pfx格式),用于签署具有法律约束力的文档 - 图片文件(PNG、JPG),用于可视化签名(可选,但推荐用于专业文档)
环境搭建要求
- 已安装并配置 JDK 8 或更高版本
- 您喜欢的 IDE(IntelliJ IDEA、Eclipse 或带有 Java 扩展的 VS Code)
- 使用 Maven 或 Gradle 完成基础项目搭建(后续会演示依赖配置)
知识前置条件
- 基本的 Java 编程 经验(只要会写一个简单类即可)
- 文件 I/O 操作 的理解
- 异常处理(
try-catch块)的熟悉度
即使您不是专家,也无需担心——我们会一步步提供清晰的解释。准备好了吗?让我们先配置 GroupDocs.Signature。
设置 GroupDocs.Signature for Java
将 GroupDocs.Signature 引入项目非常简单。请选择您的构建工具并添加相应依赖:
Maven 配置
在 pom.xml 中加入以下内容:
<dependency>
<groupId>com.groupdocs</groupId>
<artifactId>groupdocs-signature</artifactId>
<version>23.12</version>
</dependency>
Gradle 配置
在 build.gradle 中加入以下内容:
implementation 'com.groupdocs:groupdocs-signature:23.12'
小技巧: 如果您所在的企业网络受限,无法直接访问互联网,可从 GroupDocs.Signature releases page 手动下载 JAR 包并手动加入项目类路径。
许可证获取步骤
GroupDocs.Signature 在生产环境下需要许可证,但您有以下几种选择:
- 免费试用 – 适合测试和概念验证。立即开始,免费体验全部功能。
- 临时许可证 – 在开发和测试期间提供 30 天的完整 API 访问,无水印、无功能限制。
- 商业许可证 – 生产部署必备。请前往 Purchase here 根据需求购买。
基础初始化与配置
添加依赖后,使用下面的快速测试验证环境是否配置成功。该代码会初始化 GroupDocs.Signature 库并确认能够访问文档:
import com.groupdocs.signature.Signature;
public class DocumentSigner {
public static void main(String[] args) {
// Initialize with a sample document path
Signature signature = new Signature("path/to/your/document.docx");
System.out.println("GroupDocs.Signature initialized successfully!");
}
}
定义锚点: Signature 是 GroupDocs.Signature 的核心类,代表待签署的文档。
这段代码在做什么? Signature 类是入口点——它将文档加载到内存并为签名操作做好准备。如果看到成功提示,说明可以继续进行实际签署。
快速排查: 若出现 FileNotFoundException,请检查文档路径。调试阶段建议使用绝对路径,以避免相对路径带来的混淆。
下面我们进入数字签名的实际实现。
实现指南
理解 Java 中的数字签名
在编写代码之前,先明确我们要实现的目标。数字签名 使用加密证书来验证文档的真实性并检测篡改。当您对文档进行数字签名时:
- 证书的私钥对文档生成唯一哈希
- 该哈希被嵌入文档作为签名对象
- 任何人都可以使用公钥进行验证
这与仅放置图片印章不同——数字签名提供法律效力和防篡改能力。因此,需要使用包含私钥的 .pfx 证书文件。
步骤式实现
我们将完整的文档签署工作流拆解为若干易于消化的步骤。
步骤 1:准备环境
首先定义文件路径。请将占位路径替换为实际目录:
final String DOCUMENT_DIRECTORY = "YOUR_DOCUMENT_DIRECTORY";
final String OUTPUT_DIRECTORY = "YOUR_OUTPUT_DIRECTORY";
final String CERTIFICATE_FILE_PATH = "path/to/your/certificate.pfx"; // Your .pfx certificate
final String IMAGE_FILE_PATH = "path/to/your/image.png"; // Optional: signature image
String filePath = DOCUMENT_DIRECTORY + "/sample.docx";
String fileName = new java.io.File(filePath).getName();
String outputFilePath = OUTPUT_DIRECTORY + "/Signed/" + fileName;
为何要分目录? 将原始文档与签署后文档放在不同文件夹,可防止意外覆盖,并便于版本管理。生产环境中,建议在输出文件名中加入时间戳。
步骤 2:初始化 Signature 对象
创建负责所有签署操作的 Signature 对象:
Signature signature = new Signature(filePath);
内部原理: 该对象加载文档并准备进行操作。库会自动检测文档格式(PDF、DOCX、XLSX 等),并使用相应的签署方式。
重要提示: 使用 try‑with‑resources 或手动关闭 Signature 对象,以避免内存泄漏,尤其在循环处理多个文档时。
步骤 3:配置数字签名选项
在这里您可以指定签名的外观和行为:
import com.groupdocs.signature.options.sign.DigitalSignOptions;
DigitalSignOptions options = new DigitalSignOptions(CERTIFICATE_FILE_PATH) {
@Override
public void setImageFilePath(String value) {
super.setImageFilePath(IMAGE_FILE_PATH);
}
};
可定制项包括:
- 证书路径 – 法律有效签名的必填项
- 图片路径 – 可选的可视化签名(如扫描的手写签名)
- 签名位置 – 也可以设置 X/Y 坐标、宽高(后文自定义章节会详细说明)
- 密码保护 – 若
.pfx文件受密码保护,请使用options.setPassword("your_password")设置
常见错误: 忘记为受密码保护的 .pfx 文件设置密码,会抛出难以理解的异常。务必按示例添加密码行。
步骤 4:执行签名并进行错误处理
下面执行签名并优雅地处理可能的异常:
try {
signature.sign(outputFilePath, options);
System.out.println("Document signed successfully: " + outputFilePath);
} catch (com.groupdocs.signature.exception.GroupDocsSignatureException ex) {
System.out.println("GroupDocs Signature Exception: " + ex.getMessage());
// Handle library-specific errors (e.g., invalid certificate, corrupted document)
} catch (Exception ex) {
System.out.println("System Exception: " + ex.getMessage());
// Handle general errors (e.g., file I/O issues, permission problems)
}
为何使用两个 catch 块? 第一个捕获 GroupDocs 特有的错误(如证书验证失败),第二个捕获所有其他异常(如文件系统权限问题),帮助您在开发阶段更快定位问题。
生产环境建议: 用 SLF4J 或 Log4j 替代 System.out.println(),以便更好地记录日志。
高级配置选项
想要更细粒度的控制?以下是常用的可自定义选项:
DigitalSignOptions options = new DigitalSignOptions(CERTIFICATE_FILE_PATH);
// Visual appearance
options.setImageFilePath(IMAGE_FILE_PATH);
options.setLeft(100); // X position in pixels
options.setTop(100); // Y position in pixels
options.setWidth(200); // Signature width
options.setHeight(100); // Signature height
// Certificate settings
options.setPassword("certificate_password"); // If .pfx is password-protected
// Signature metadata
options.setReason("Contract approval"); // Why this document is being signed
options.setContact("john@company.com"); // Signer's contact info
options.setLocation("New York Office"); // Where the signature occurred
实战技巧: 对于合同类文档,务必填写 Reason、Contact、Location 等字段,它们会出现在 PDF 签名属性中,审计时更具可信度。
常见陷阱与解决方案
下面列出最容易让开发者踩坑的问题及对应的解决办法,帮助您省去大量调试时间:
1. 证书无效错误
问题: 抛出 CertificateException 或 “Invalid certificate format”。
解决方案:
- 确认
.pfx文件未损坏:在 Windows 证书管理器中打开或使用keytool -list -keystore certificate.pfx检查。 - 核实密码是否正确。
- 检查证书是否已过期(常被忽视)。
预防措施: 在生产环境中实现证书到期监控,并在到期前 30 天发送告警。
2. 文件路径问题
问题: FileNotFoundException 或 “Access denied”。
解决方案:
- 开发阶段使用绝对路径,例如
C:/projects/docs/sample.pdf。 - 检查文件权限,确保 Java 进程拥有读取输入文件和写入输出目录的权限。
- 在 Windows 上注意反斜杠与正斜杠的区别,推荐使用
File.separator或统一使用正斜杠实现跨平台兼容。
3. 大文档内存问题
问题: 处理大于 50 MB 的 PDF 时应用崩溃或卡顿。
解决方案:
- 增加 JVM 堆内存:例如在运行配置中加入
-Xmx2G。 - 将文档分批处理,而非一次性全部加载。
- 始终关闭
Signature对象:使用 try‑with‑resources 或手动调用dispose()。
// Good: automatic resource management
try (Signature signature = new Signature(filePath)) {
signature.sign(outputFilePath, options);
}
// Signature is automatically closed here
4. PDF 签名位置错误
问题: 签名出现在错误位置或与已有内容重叠。
解决方案:
- PDF 坐标系原点在左下角,而非大多数 UI 系统的左上角。
- 先获取页面尺寸,再根据尺寸计算偏移量。
- 在多个 PDF 查看器(Adobe Acrobat、浏览器内置查看器等)中进行测试,因为渲染可能略有差异。
安全最佳实践
数字签名的安全性取决于实现方式。请遵循以下生产级别的建议:
证书管理
切勿在源码中硬编码证书路径或密码。 推荐做法:
// Bad - hardcoded secrets
String certPath = "/home/user/cert.pfx";
String certPassword = "mypassword123";
// Good - environment variables or secure configuration
String certPath = System.getenv("CERT_PATH");
String certPassword = System.getenv("CERT_PASSWORD");
最佳实践:
- 将证书存放在安全金库(AWS Secrets Manager、Azure Key Vault、HashiCorp Vault)中。
- 对高价值签名操作使用硬件安全模块(HSM)。
- 在证书到期前自动轮换,并实现自动续订。
- 限制证书文件的文件系统权限,仅对运行应用的用户只读。
输入校验
在签署前始终对文档进行校验:
// Check file exists and is readable
File inputFile = new File(filePath);
if (!inputFile.exists() || !inputFile.canRead()) {
throw new IllegalArgumentException("Cannot access input file: " + filePath);
}
// Verify file size is reasonable (prevent DoS attacks)
long maxFileSize = 100 * 1024 * 1024; // 100MB
if (inputFile.length() > maxFileSize) {
throw new IllegalArgumentException("File too large for signing: " + inputFile.length());
}
// Validate file extension matches expected format
String extension = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
if (!Arrays.asList("pdf", "docx", "xlsx").contains(extension)) {
throw new IllegalArgumentException("Unsupported file format: " + extension);
}
审计日志
记录每一次签署操作,以满足合规和排错需求:
// Log signature operations with essential details
logger.info("Signing document: {} by user: {} with certificate: {}",
fileName, userId, certificateThumbprint);
try {
signature.sign(outputFilePath, options);
logger.info("Successfully signed: {} to {}", fileName, outputFilePath);
} catch (Exception ex) {
logger.error("Failed to sign document: {} - Error: {}", fileName, ex.getMessage());
throw ex; // Re-throw after logging
}
日志内容建议: 文档名称及大小、发起签署的用户、证书指纹(不要记录完整证书)、时间戳、成功或失败状态以及错误信息(切勿记录密码或完整证书)。
何时使用不同的签名类型
GroupDocs.Signature 除数字签名外,还支持多种签名类型。以下是各自适用场景:
数字签名(本教程覆盖的)
最佳场景: 合同、财务文件、合规文档
提供: 加密验证、篡改检测、不可否认性
使用时机: 需要法律效力或需证明文档未被修改时
图片签名
最佳场景: 简单的视觉确认、内部审批
提供: 仅视觉呈现(无加密验证)
使用时机: 仅需签名外观且无法律要求的场合(如内部备忘录)
QR 码签名
最佳场景: 移动端验证、跨系统追踪
提供: 嵌入数据 + 易于扫描
使用时机: 需要快速扫描获取元数据(文档 ID、时间戳)时
条形码签名
最佳场景: 库存单据、运输标签、自动化处理
提供: 机器可读的标准化数据
使用时机: 文档将被条码扫描器批量处理时
我的建议: 对于任何具有法律意义的文档(合同、协议、发票),始终使用 数字签名。它是唯一能够提供加密真实性证明的方式。
实际应用案例
以下是企业在生产环境中使用 Java 文档签署的典型方案:
1. 合同管理系统
场景: 律所每日需签署 100+ 客户协议
实现思路:
- 将未签署的合同存放在云存储(S3、Azure Blob)
- 合同审批通过后通过 API 触发签署
- 自动将已签署 PDF 邮件发送给所有相关方
- 将签署后的文档连同审计日志归档
代码集成模式:
// Pseudo-code example
public void processApprovedContract(String contractId) {
Contract contract = contractRepository.findById(contractId);
String unsignedDoc = downloadFromCloudStorage(contract.getStorageKey());
// Sign the document
String signedDoc = signDocument(unsignedDoc, getLegalCertificate());
// Store and notify
uploadToCloudStorage(signedDoc, contract.getStorageKey() + "_signed");
emailService.sendSignedContract(contract.getParties(), signedDoc);
auditLog.recordSigning(contractId, getCurrentUser());
}
2. 发票处理自动化
场景: 财务部门自动签署外发发票
关键需求:
- 发票生成后立即签署
- 添加公司印章图片
- 在签名元数据中写入发票编号、日期、金额
实现技巧: 使用 RabbitMQ、AWS SQS 等消息队列异步执行签署,避免阻塞发票生成流程。
3. 人力资源文档工作流
场景: 签署员工合同、Offer Letter、NDAs
安全考量:
- 为不同文档类型使用不同证书(HR 与 Legal 各自独立)
- 基于角色的访问控制决定谁可以触发签署
- 对已签署文档进行加密备份
4. 与 CRM 系统集成
场景: 当交易关闭时,Salesforce 或 HubSpot 触发文档签署
集成模式:
- CRM 发送 webhook 调用 Java 签署服务
- 使用模板填充交易数据
- 将签署后的文档上传回 CRM
Webhook 处理示例:
@PostMapping("/api/sign-sales-document")
public ResponseEntity<String> signSalesDocument(@RequestBody DealClosedEvent event) {
// Generate document from template
String document = documentGenerator.createFromTemplate(event.getDealId());
// Sign it
String signedDoc = documentSigner.sign(document, getSalesCertificate());
// Upload to CRM
crmClient.uploadDocument(event.getDealId(), signedDoc);
return ResponseEntity.ok("Document signed and uploaded");
}
5. 电商订单确认
场景: 为 B2B 交易的采购订单和发货单签署
性能优化:
- 为常用文档类型预生成已签名模板
- 对同一证书的重复签署进行缓存
- 对批量订单使用批处理签署
实际集成模式
微服务架构
如果您采用微服务,推荐如下结构:
[Order Service] --> [Signing Service] --> [Storage Service]
|
v
[Notification Service]
签署服务职责: 提供 REST API 接收签署请求,管理证书生命周期,处理高并发签署队列,并提供状态回调。
优势: 将签署逻辑解耦,便于证书轮换和独立水平扩展。
批处理模式
针对每日上千甚至上万文档的高吞吐场景:
public class BatchDocumentSigner {
private final BlockingQueue<SigningTask> queue = new LinkedBlockingQueue<>();
private final ExecutorService executorService = Executors.newFixedThreadPool(5);
public void submitForSigning(String documentPath, String certificatePath) {
queue.offer(new SigningTask(documentPath, certificatePath));
}
public void startProcessing() {
for (int i = 0; i < 5; i++) {
executorService.submit(() -> {
while (true) {
try {
SigningTask task = queue.take();
processSigningTask(task);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
});
}
}
private void processSigningTask(SigningTask task) {
// Your signing logic here
}
}
该模式可避免内存占用过高,并提升整体吞吐。
性能考量
签署性能优化
典型签署耗时:
- 小型 PDF(1‑5 页):100‑300 ms
- 中型 PDF(20‑50 页):500‑1000 ms
- 大型 PDF(100+ 页):2‑5 s
- Word 文档:通常比 PDF 更快
优化策略:
- 复用 Signature 对象(如可能)
// Bad - creates new object for each document
for (String doc : documents) {
Signature sig = new Signature(doc);
sig.sign(output, options);
}
// Good - reuse when signing multiple documents with same options
for (String doc : documents) {
try (Signature sig = new Signature(doc)) {
sig.sign(output, options);
}
}
- 批量并行处理 – 使用
CompletableFuture或ParallelStream对独立签署任务并行化:
List<CompletableFuture<Void>> futures = documents.stream()
.map(doc -> CompletableFuture.runAsync(() -> signDocument(doc)))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
- 监控与分析 – 使用 JProfiler、YourKit 等工具定位瓶颈。常见问题包括证书加载(可缓存证书)、图片处理(签名前压缩图片)以及文件 I/O(使用 SSD 或 RAM Disk 作为临时目录)。
资源使用指南
内存需求:
- 基础库约 50 MB 堆
- 每个文档约占 2 倍文档大小的内存(输入 + 输出)
- 对于 100 MB 的 PDF,建议至少分配
-Xmx256m(建议-Xmx1G并监控实际使用情况)
生产建议: 启用 GC 日志,设置堆使用率 > 80% 时报警。
Java 内存管理最佳实践
// Always use try-with-resources
try (Signature signature = new Signature(filePath)) {
signature.sign(outputPath, options);
} // Automatically calls signature.dispose()
// For custom resource management
Signature signature = null;
try {
signature = new Signature(filePath);
signature.sign(outputPath, options);
} finally {
if (signature != null) {
signature.dispose();
}
}
生产监控指标: 堆内存使用趋势、GC 暂停时间、并发签署数、各文档类型的平均签署时长。
结论
您已经掌握了在 Java 中实现专业级数字签名的完整流程。下面回顾一下您现在可以做到的事:
✅ 在任意 Java 项目(Maven 或 Gradle)中配置 GroupDocs.Signature
✅ 使用合法的数字证书以编程方式签署文档
✅ 优雅地处理错误并排查常见问题
✅ 在生产环境中落实安全最佳实践
✅ 根据业务需求选择合适的签名类型
✅ 针对高并发文档处理进行性能优化
核心结论: 自动化数字签名能够每天为团队节省大量手工工作时间,同时提升安全性和合规性。无论是处理 10 份文档还是 10 000 份,本文提供的模式都具备可扩展性。
下一步
想进一步深化实现吗?以下方向值得探索:
- 签名验证工作流 – 学习如何以编程方式验证已签署文档。
- 自定义签名外观 – 使用公司徽标创建品牌化签名块。
- 多签名工作流 – 实现顺序审批链(签署 → 会签 → 最终批准)。
- 云集成 – 与 AWS S3、Google Drive、Dropbox 等存储服务无缝对接。
有疑问吗? GroupDocs 社区论坛活跃且乐于助人——搜索已有帖子或在 forum.groupdocs.com 发帖提问。
FAQ Section
1. 使用 GroupDocs.Signature 可以对哪些文件格式进行数字签名?
您可以签署 PDF、DOCX、XLSX、PPTX 以及超过 50 种其他格式,包括图片文件(PNG、JPG)和纯文本。PDF 是法律签署的首选,因为它在不同平台上能够保持布局不变,但库同样支持电子表格、演示文稿,甚至 CAD 文件,提供统一的 API。
2. 如何在批处理过程中签署多个文档?
使用线程池执行并行签署(参见“批处理模式”章节)。对于 1000+ 文档,建议使用消息队列(RabbitMQ、AWS SQS)将工作分发到多台服务器。务必监控内存使用并实现限流,以防资源耗尽。
3. 能否验证由 GroupDocs.Signature 创建的签名?
可以!调用 signature.verify() 并传入相应的验证选项即可。该方法会检查证书有效性、签名完整性以及文档是否被修改。还可以验证签署时间戳、撤销状态和信任链,以确保符合合规要求。
4. 数字签名和电子签名有什么区别?
数字签名 使用加密证书并提供防篡改验证(本教程所述)。电子签名 是更宽泛的概念,可能仅是键入姓名、点击 “我同意” 或使用数字签名。若需在多数司法管辖区获得法律效力,数字签名是首选。
5. 如何排查 “Invalid certificate” 错误?
首先确认 .pfx 文件能在 Windows 证书管理器或使用 keytool -list 正常打开。常见问题包括:
.pfx的密码错误- 证书已过期(检查有效期)
- 证书文件损坏——重新从 CA 导出
- 文件系统权限不足——确保 Java 进程有读取权限
6. 能否将 GroupDocs.Signature 与 AWS S3 等云存储集成?
完全可以。将文档从 S3 下载到临时目录,完成签署后再上传回 S3。生产环境建议使用预签名 URL 实现安全直接上传,并为 S3 交互实现重试机制以应对瞬时故障。
7. 如何在生产环境管理证书过期?
实现每日检查证书到期日期的自动化任务,并在到期前 30 天发送告警。将证书的到期信息存入数据库,与证书元数据一起管理。部分团队使用企业证书管理系统自动拉取并部署新证书。
8. 能否在签名中加入除图片之外的自定义视觉元素?
可以。您可以自定义位置、尺寸、旋转角度、透明度和边框样式。高级场景下,可将图片签名与文本签名组合,创建复杂的签名块,还可以在签名区域内嵌入 QR 码或条形码以携带额外元数据。
9. 生产使用的许可证费用是多少?
GroupDocs 提供多种定价方案:面向小团队的 per‑developer 许可证、面向大型部署的 server‑based 许可证,以及包含高级支持的 enterprise 版。价格从每位开发者每年几百美元起,随开发者人数和支持级别递增。请联系销售获取基于实际使用量的精准报价。
10. 如何处理不同页面尺寸文档的签名定位?
先通过 signature.getDocumentInfo() 获取页面尺寸,然后以页面宽高的百分比计算签名位置,而不是固定像素。例如,距右边缘 10% 、距底部 5% 的位置可在不同纸张(A4、Letter 等)上保持一致。
Resources
- Documentation – 完整的 API 参考和使用指南
- API Reference – 详细的类和方法文档
- Download – 最新发布和版本历史
- Purchase – 许可证选项和定价
- Free Trial – 免费试用全部功能,无需承诺
- Temporary License – 开发和测试期间的完整访问(30 天)
- Support Forum – 社区帮助和官方支持
最后更新: 2026-06-26
测试版本: GroupDocs.Signature 23.12 for Java
作者: GroupDocs