添加条形码 PDF Java – 使用 GroupDocs 进行签名和验证
如果您需要一种快速、直观的方式在内部工作流中标记文档,在 Java 中向 PDF 添加条形码是完美的解决方案。借助 GroupDocs.Signature,您可以嵌入、验证、搜索、更新和删除条形码签名,而无需完整基于 PKI 的数字签名的开销。本教程将手把手带您完成从环境搭建到生产就绪最佳实践的每一步。
快速回答
- 哪个库可以在 Java 中向 PDF 添加条形码? GroupDocs.Signature for Java。
- 我可以签署 PDF、Word 和图片吗? 可以——API 支持 30 多种格式。
- 开发阶段需要许可证吗? 30 天的临时许可证免费;生产环境需要正式许可证。
- 签署 PDF 需要多少行代码? 只需两行:创建
Signature对象并调用sign。 - 条形码验证是否区分大小写? 是的——文本必须完全匹配,包括大小写和空格。
什么是 add barcode pdf java?
add barcode pdf java 指的是使用 Java 代码将条形码(如 Code128、QR 或 Data Matrix)嵌入 PDF 文件的过程。该技术提供了可机器读取的标签,可扫描或通过程序验证,从而实现内部系统的快速文档跟踪。
为什么使用条形码签名而不是完整的数字签名?
条形码签名的生成和验证速度比基于 PKI 的数字签名快 30‑50 %,且在打印‑扫描循环后仍能可靠工作。它们不需要证书管理,非常适合对加密证明要求不高的高吞吐量内部工作流。
前置条件
- Java Development Kit (JDK) 8+ – 推荐使用 Java 11 或 17,以获得更佳性能。
- IDE – IntelliJ IDEA 或 Eclipse(示例使用 IntelliJ 语法)。
- 构建工具 – Maven 或 Gradle 用于依赖管理。
将 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'
直接下载 JAR – 如果您更喜欢手动设置,可从 GroupDocs.Signature releases 下载 JAR 并加入类路径。
获取许可证
GroupDocs.Signature 在生产环境中不是免费使用的,但您有灵活的选择:
- 免费试用 – 从 GroupDocs 下载页面 下载进行功能测试(会添加评估水印)。
- 临时许可证 – 通过 GroupDocs 临时许可证页面 获取 30 天的完整访问权限——非常适合概念验证。
- 正式许可证 – 生产部署请查看 GroupDocs 购买选项。
专业提示: 开发阶段先使用临时许可证,切换到永久密钥后即可去除水印。
快速环境检查
添加依赖后,运行一个简单的冒烟测试:
import com.groupdocs.signature.Signature;
public class SignatureTest {
public static void main(String[] args) {
try {
Signature signature = new Signature("test.pdf");
System.out.println("GroupDocs.Signature initialized successfully!");
signature.dispose();
} catch (Exception e) {
System.err.println("Setup issue: " + e.getMessage());
}
}
}
如果程序能够正常运行且没有错误,说明环境已就绪。若出现问题,请检查 JDK 版本以及您添加的库版本是否匹配。
何时使用条形码签名
条形码签名在以下场景中表现出色:
- 内部文档工作流 – 在发票、采购订单或备忘录的审批链中进行追踪。
- 高吞吐量处理 – 能快速为成千上万的文档签名;条形码生成速度比完整数字签名快 2 倍。
- 打印‑扫描桥接 – 条形码能够在打印‑扫描循环后仍然可读,适合纸质‑数字混合流程。
- 遗留系统集成 – 现有的条形码扫描器即可读取,无需额外软件。
- 审计追踪 – 嵌入交易 ID 或时间戳,审计员可即时验证。
请避免在法律合同、高安全性文档或需要外部合作方的场景中使用条形码签名——此时应使用基于 PKI 的数字签名。
为 Java 配置 GroupDocs.Signature
核心类定义
Signature 类是 GroupDocs.Signature 中进行所有签名、验证、搜索、更新和删除操作的入口。
import com.groupdocs.signature.Signature;
基本初始化
创建指向目标文件的 Signature 对象:
Signature signature = new Signature("path/to/your/document.pdf");
重要说明
- 路径可以是绝对路径或相对路径;使用
System.getProperty("user.home")可实现跨平台兼容。 - GroupDocs 支持 30+ 输入和输出格式,包括 PDF、DOCX、XLSX、PPTX 和 PNG。
- 必须使用
signature.dispose()或 try‑with‑resources 语句释放资源:
try (Signature signature = new Signature("path/to/your/document.pdf")) {
// Your signing operations here
} catch (Exception e) {
System.err.println("Error working with document: " + e.getMessage());
}
现在您可以开始添加条形码了。
如何在 Java 中向 PDF 添加条形码?
使用 new Signature("input.pdf") 加载源 PDF,配置 BarcodeSignature 对象(例如 Code128,文本为 “John Smith”),然后调用 sign 生成签名文件——整个过程仅需三行代码。此方式在保持原始文档布局不变的同时嵌入机器可读的标签,且适用于所有受支持的格式,而不仅限于 PDF。
步骤实现
1. 定义文件路径
设置源文件和输出文件的位置:
String filePath = "YOUR_DOCUMENT_DIRECTORY/sample.docx";
String outputFilePath = "YOUR_OUTPUT_DIRECTORY/signed_sample.docx";
2. 创建签名处理器
使用源文档初始化处理器:
Signature signature = new Signature(filePath);
3. 配置条形码选项
BarcodeSignature 对象定义了要嵌入的条形码的可视化和数据属性。设置条形码类型、编码文本、位置、大小、颜色以及可选的人类可读字体:
BarcodeSignOptions signOptions = new BarcodeSignOptions("John Smith", BarcodeTypes.Code128);
signOptions.setVerticalAlignment(VerticalAlignment.Top);
signOptions.setHorizontalAlignment(HorizontalAlignment.Center);
signOptions.setWidth(100);
signOptions.setHeight(40);
signOptions.setMargin(new java.awt.Rectangle(20, 20, 0, 0));
signOptions.setForeColor(Color.RED);
SignatureFont signatureFont = new SignatureFont();
signatureFont.setSize(12);
signatureFont.setFamilyName("Comic Sans MS");
signOptions.setFont(signatureFont);
专业提示: 若编码字符串超过 20 个字符,请增大条形码宽度以避免扫描错误。
4. 应用签名
执行签名操作并生成输出文件:
signature.sign(outputFilePath, signOptions);
5. 实际案例
在发票审批系统中,您可能会嵌入审批人的员工编号和时间戳:
String invoiceId = "INV-2025-0042";
String approver = "john.smith@company.com";
String timestamp = LocalDateTime.now().toString();
// Combine into barcode data
String barcodeData = invoiceId + "|" + approver + "|" + timestamp;
BarcodeSignOptions signOptions = new BarcodeSignOptions(barcodeData, BarcodeTypes.QR);
signOptions.setVerticalAlignment(VerticalAlignment.Bottom);
signOptions.setHorizontalAlignment(HorizontalAlignment.Right);
signature.sign(outputFilePath, signOptions);
生成的 PDF 现在携带了可扫描的条形码,编码了所有必要的审批元数据。
如何在 Java 文档中验证条形码签名?
Signature.verify 方法根据提供的选项检查文档中是否存在匹配的条形码签名,返回一个布尔值指示预期条形码是否存在。验证在需要在后续操作前确认文档已被正确方处理的自动化工作流中非常有用。
验证步骤
1. 加载已签名文档
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
2. 设置验证条件
定义您期望的文本、条形码格式和页码:
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setAllPages(false); // Only check the first page
verifyOptions.setPageNumber(1);
verifyOptions.setEncodeType(BarcodeTypes.Code128);
verifyOptions.setText("John Smith");
3. 运行验证
执行检查并处理结果:
boolean isValid = signature.verify(verifyOptions) != null;
if (isValid) {
System.out.println("Document signature verified successfully!");
} else {
System.out.println("Warning: Signature verification failed!");
}
常见模式: 若只需确认某种类型的条形码是否存在,可省略 setText 过滤:
BarcodeVerifyOptions flexibleOptions = new BarcodeVerifyOptions();
flexibleOptions.setAllPages(true);
flexibleOptions.setEncodeType(BarcodeTypes.Code128);
// No setText() call = accept any text with this barcode type
boolean hasAnySignature = signature.verify(flexibleOptions) != null;
或仅验证条形码格式而不关心内容:
BarcodeVerifyOptions formatOnly = new BarcodeVerifyOptions();
formatOnly.setEncodeType(BarcodeTypes.QR);
// Confirms a QR code exists, regardless of content
警告: 验证区分大小写和空格;签名前请始终对数据进行 trim 和标准化。
如何在文档中搜索条形码签名?
Signature.search 方法会扫描文档中符合 BarcodeSearchOptions 的条形码签名,返回包含每个条形码位置、页码和编码值的集合。此功能可在无需手动打开每个文件的情况下批量提取元数据。
搜索工作流
1. 初始化搜索
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
2. 配置搜索参数
指定页码范围、条形码类型或文本过滤:
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true); // Search the entire document
可选地缩小搜索范围以提升性能:
BarcodeSearchOptions narrowSearch = new BarcodeSearchOptions();
narrowSearch.setAllPages(false);
narrowSearch.setPageNumber(1); // Only search page 1
narrowSearch.setEncodeType(BarcodeTypes.Code128); // Only find Code128 barcodes
3. 执行搜索
java.util.List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
System.out.println("Found " + signatures.size() + " barcode signature(s)");
for (BarcodeSignature bcSignature : signatures) {
System.out.println("Barcode Type: " + bcSignature.getEncodeType().getTypeName());
System.out.println("Text: " + bcSignature.getText());
System.out.println("Position: (" + bcSignature.getLeft() + ", " + bcSignature.getTop() + ")");
System.out.println("Size: " + bcSignature.getWidth() + "x" + bcSignature.getHeight());
System.out.println("---");
}
4. 提取数据示例
从发票批次中提取每个条形码的审批数据:
List<BarcodeSignature> foundSignatures = signature.search(BarcodeSignature.class, searchOptions);
for (BarcodeSignature sig : foundSignatures) {
String barcodeData = sig.getText();
// Parse your custom format (remember our invoice example?)
String[] parts = barcodeData.split("\\|");
if (parts.length == 3) {
String invoiceId = parts[0];
String approver = parts[1];
String timestamp = parts[2];
System.out.println("Invoice " + invoiceId + " approved by " + approver + " at " + timestamp);
}
}
性能提示: 对于超过 100 页的文档,始终将搜索限制在实际包含条形码的页面;这样可将运行时间缩短最多 70 %。
如何更新已有的条形码签名?
Signature.update 方法可修改已有条形码签名的可视属性——如位置、大小或颜色——而保持原始编码数据不变。当文档已签名后需要调整布局时,此功能非常实用。
更新流程
1. 查找待更新的签名
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true);
List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
2. 修改所需属性
List<BarcodeSignature> signaturesToUpdate = new ArrayList<>();
if (!signatures.isEmpty()) {
BarcodeSignature bcSignature = signatures.get(0); // Update the first found signature
// Move it 100px right and 100px down
bcSignature.setLeft(bcSignature.getLeft() + 100);
bcSignature.setTop(bcSignature.getTop() + 100);
// Make it bigger
bcSignature.setWidth(200);
bcSignature.setHeight(50);
signaturesToUpdate.add(bcSignature);
}
您可以一次性更改多个属性:
bcSignature.setLeft(50);
bcSignature.setTop(50);
bcSignature.setWidth(150);
bcSignature.setHeight(45);
// Note: You can't change the encoded text or barcode type with update
// For that, you'd need to delete and re-sign
3. 保存更改
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
signature.update(outputStream, signaturesToUpdate);
// If you want to save to a file instead:
// signature.update("path/to/updated_document.docx", signaturesToUpdate);
4. 实际场景
将所有签名重新定位,以避免与新添加的公司徽标重叠:
List<BarcodeSignature> allSignatures = signature.search(BarcodeSignature.class, searchOptions);
List<BarcodeSignature> toUpdate = new ArrayList<>();
for (BarcodeSignature sig : allSignatures) {
// Move all signatures down by 50px to clear space for new logo
if (sig.getTop() < 100) {
sig.setTop(sig.getTop() + 50);
toUpdate.add(sig);
}
}
if (!toUpdate.isEmpty()) {
signature.update(outputStream, toUpdate);
System.out.println("Repositioned " + toUpdate.size() + " signature(s)");
}
限制: 若要更改编码文本,需要先删除原签名再重新插入。
如何删除文档中的条形码签名?
Signature.delete 方法会永久移除文档中选定的条形码签名,删除其可视元素和编码数据。当需要清理测试签名或条形码不再相关时使用此操作。
删除步骤
1. 定位签名
Signature signature = new Signature("YOUR_OUTPUT_DIRECTORY/signed_sample.docx");
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true);
List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
2. 删除选中的签名
List<BarcodeSignature> signaturesToDelete = new ArrayList<>();
// Delete all Code128 signatures (for example)
for (BarcodeSignature sig : signatures) {
if (sig.getEncodeType().equals(BarcodeTypes.Code128)) {
signaturesToDelete.add(sig);
}
}
if (!signaturesToDelete.isEmpty()) {
boolean result = signature.delete(signaturesToDelete);
if (result) {
System.out.println("Successfully deleted " + signaturesToDelete.size() + " signature(s)");
} else {
System.err.println("Failed to delete signatures");
}
}
3. 条件删除示例
仅删除时间戳早于特定时间的条形码(假设时间戳是编码文本的一部分):
LocalDateTime cutoffDate = LocalDateTime.now().minusDays(30);
List<BarcodeSignature> outdatedSignatures = new ArrayList<>();
for (BarcodeSignature sig : signatures) {
try {
String text = sig.getText();
// Assuming format like "data|timestamp"
String[] parts = text.split("\\|");
if (parts.length > 1) {
LocalDateTime sigDate = LocalDateTime.parse(parts[1]);
if (sigDate.isBefore(cutoffDate)) {
outdatedSignatures.add(sig);
}
}
} catch (Exception e) {
// Skip signatures that don't match our format
continue;
}
}
if (!outdatedSignatures.isEmpty()) {
signature.delete(outdatedSignatures);
System.out.println("Removed " + outdatedSignatures.size() + " outdated signature(s)");
}
注意: 删除操作不可撤销;测试时请始终在生产文件的副本上操作。
4. 批量删除模式
在发布前清理测试签名:
// Remove all signatures containing "TEST" in their text
List<BarcodeSignature> testSignatures = signatures.stream()
.filter(sig -> sig.getText().toUpperCase().contains("TEST"))
.collect(Collectors.toList());
if (!testSignatures.isEmpty()) {
signature.delete(testSignatures);
System.out.println("Cleaned up " + testSignatures.size() + " test signature(s)");
}
条形码类型:实用指南
选择合适的条形码会影响扫描可靠性、数据容量和打印机兼容性。
Code128(最常用)
- 适用场景: 字母数字数据、高密度、打印文档。
- 优点: 紧凑、兼容标准扫描器、在小尺寸下仍能清晰打印。
- 缺点: 仅限 ASCII,错误纠正能力不如 2‑D 条码。
示例:
BarcodeSignOptions code128 = new BarcodeSignOptions("INV-2025-042", BarcodeTypes.Code128);
QR Code(移动端最佳)
- 适用场景: 手机扫描、大数据负载(URL、JSON 等)、可能受损的文档。
- 优点: 支持约 4 000 字符,内置错误纠正,手机友好。
- 缺点: 可视占用较大,需更高分辨率才能在小尺寸下保持可读。
示例:
String jsonData = "{\"invoice\":\"INV-042\",\"amount\":1500.00,\"approver\":\"john@company.com\"}";
BarcodeSignOptions qrCode = new BarcodeSignOptions(jsonData, BarcodeTypes.QR);
Code39(老牌可靠)
- 适用场景: 传统扫描器环境、需要人类可读文本的场合。
- 优点: 兼容性广,格式简单,无需校验位。
- 缺点: 数据密度低,字符集受限,占用空间较大。
Data Matrix(紧凑强大)
- 适用场景: 极限空间、标记微小物品、高密度数据。
- 优点: 极其紧凑,错误纠正强大,可在弧面上使用。
- 缺点: 需要高质量打印,扫描器支持度相对较低。
快速对比
| 条形码类型 | 数据容量 | 最佳用途 | 常见尺寸 |
|---|---|---|---|
| Code128 | ~100 字符 | 通用标签 | 小 |
| QR Code | ~4 000 字符 | 移动扫描、丰富数据 | 中 |
| Code39 | ~43 字符 | 传统硬件 | 大 |
| Data Matrix | ~3 000 字符 | 极小空间、工业标签 | 极小 |
推荐: 对于简单 ID 建议首选 Code128;需要嵌入 URL 或大容量数据时切换到 QR;仅在必须兼容旧系统时使用 Code39。
常见问题与解决方案
问题:“验证时未找到条形码”
症状: 即使条形码可见,验证仍返回 false。
常见原因
- 大小写不匹配(如 “John Smith” 与 “john smith”)。
- 编码文本中存在多余空格。
- 验证选项中指定了错误的条形码类型。
- 搜索的页码不正确。
解决方案: 在签名和验证前对文本进行标准化,并确保 setEncodeType 与原始类型一致。
// Always normalize your data
String signatureText = originalText.trim().toLowerCase();
// When signing:
BarcodeSignOptions signOptions = new BarcodeSignOptions(signatureText, BarcodeTypes.Code128);
// When verifying:
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setText(signatureText); // Use the same normalized text
verifyOptions.setEncodeType(BarcodeTypes.Code128);
问题:“条形码模糊或无法读取”
症状: 打印后的条形码无法扫描。
原因
- 条形码尺寸对当前数据量来说过小。
- 打印机分辨率过低。
- 条形码颜色与背景对比度不足。
解决方案: 增大条形码宽高,使用高对比度颜色(黑底白),并将打印机 DPI 设置至至少 300 dpi。
// Increase size for longer strings
int dataLength = barcodeText.length();
int minWidth = Math.max(100, dataLength * 10); // 10px per character minimum
BarcodeSignOptions signOptions = new BarcodeSignOptions(barcodeText, BarcodeTypes.Code128);
signOptions.setWidth(minWidth);
signOptions.setHeight(60); // Taller = easier to scan
// Use high-contrast colors
signOptions.setForeColor(Color.BLACK); // Black on white is most reliable
signOptions.setBackColor(Color.WHITE);
问题:“处理大文档时出现 OutOfMemoryError”
症状: 处理上百页的 PDF 时应用崩溃。
原因: 库一次性将整个文档加载到内存。
解决方案: 启用流式模式,按页增量处理。
// Process pages in batches instead of all at once
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(false);
List<BarcodeSignature> allSignatures = new ArrayList<>();
for (int page = 1; page <= totalPages; page++) {
searchOptions.setPageNumber(page);
List<BarcodeSignature> pageSignatures = signature.search(BarcodeSignature.class, searchOptions);
allSignatures.addAll(pageSignatures);
// Process and clear as you go
if (page % 10 == 0) {
System.gc(); // Suggest garbage collection every 10 pages
}
}
问题:“不同文档类型的签名位置不一致”
症状: 在 PDF 与 Word 文档中条形码出现位置偏差。
原因: PDF 使用左下角坐标系,Word 使用左上角坐标系。
解决方案: 检测文档类型并对坐标进行相应转换。
// Use relative positioning instead of absolute
signOptions.setVerticalAlignment(VerticalAlignment.Bottom);
signOptions.setHorizontalAlignment(HorizontalAlignment.Right);
// Then use margins for fine-tuning
Padding margin = new Padding();
margin.setRight(50); // 50px from right edge
margin.setBottom(50); // 50px from bottom
signOptions.setMargin(margin);
// This works consistently across PDF, DOCX, XLSX, etc.
问题:“更新签名后格式丢失”
症状: 调用 update 后条形码的颜色或字体意外改变。
原因: 并非所有可视属性在更新操作中都会被持久化。
解决方案: 在更新调用后重新设置所需的视觉属性。
// When updating, explicitly set all visual properties
bcSignature.setLeft(newLeft);
bcSignature.setTop(newTop);
bcSignature.setWidth(originalWidth); // Don't forget these!
bcSignature.setHeight(originalHeight);
// Note: Color and font can't be updated - you'd need to delete and re-sign
生产环境最佳实践
性能优化
- 复用 Signature 实例 – 对同一文档创建单个
Signature对象并在多次操作中复用。
// Bad - Creates new instance each time
public void processDocument(String path) {
Signature sig1 = new Signature(path);
sig1.search(/* ... */);
Signature sig2 = new Signature(path); // Unnecessary reload
sig2.verify(/* ... */);
}
// Good - Reuse the same instance
public void processDocument(String path) {
try (Signature signature = new Signature(path)) {
signature.search(/* ... */);
signature.verify(/* ... */);
signature.update(/* ... */);
}
}
- 仅搜索特定页面 – 将页码范围限制在预期出现条形码的页面。
// Slow for 100+ page documents
BarcodeSearchOptions slowSearch = new BarcodeSearchOptions();
slowSearch.setAllPages(true);
// Fast - only check where signatures actually are
BarcodeSearchOptions fastSearch = new BarcodeSearchOptions();
fastSearch.setAllPages(false);
fastSearch.setPageNumber(1); // Only check cover page
- 选择最简条形码类型 – 简单条形码生成更快;仅在确实需要更大容量时才使用 QR 或 Data Matrix。
// Overkill for simple IDs
BarcodeSignOptions slow = new BarcodeSignOptions("12345", BarcodeTypes.QR);
// Faster and more appropriate
BarcodeSignOptions fast = new BarcodeSignOptions("12345", BarcodeTypes.Code128);
安全注意事项
- 切勿编码敏感个人数据 – 条形码易于读取,避免存放 SSN、密码等 PII。
// Bad - exposes sensitive data
String barcodeData = "SSN:123-45-6789|Password:hunter2";
// Good - use reference IDs
String barcodeData = "USER-REF-7721X"; // Look up sensitive data server-side
- 服务器端验证 – 不要仅依赖客户端验证,务必在可信后端重新验证。
// Client scans barcode and extracts "APPROVED-BY-ADMIN"
// Always verify server-side:
public boolean verifyApproval(String documentId, String scannedData) {
// Load document from secure storage
Signature signature = new Signature(getSecureDocument(documentId));
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setText(scannedData);
boolean isValid = signature.verify(verifyOptions) != null;
// Also check against your database
boolean isInDatabase = checkApprovalDatabase(documentId, scannedData);
return isValid && isInDatabase;
}
- 添加时间戳 – 在条形码负载中加入时间戳,以防止重放攻击。
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
String timestamp = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).toString();
String barcodeData = "APPROVAL|" + userId + "|" + timestamp;
// When verifying, check the timestamp isn't too old
public boolean isSignatureRecent(String barcodeData, int maxAgeHours) {
String[] parts = barcodeData.split("\\|");
if (parts.length < 3) return false;
LocalDateTime signatureTime = LocalDateTime.parse(parts[2]);
LocalDateTime now = LocalDateTime.now();
long hoursSince = ChronoUnit.HOURS.between(signatureTime, now);
return hoursSince <= maxAgeHours;
}
错误处理模式
- 始终使用 Try‑With‑Resources 确保
Signature对象被正确释放。
try (Signature signature = new Signature(documentPath)) {
// Your operations here
signature.sign(outputPath, signOptions);
} catch (Exception e) {
logger.error("Failed to sign document: " + documentPath, e);
throw new DocumentSigningException("Could not process document", e);
}
- 在尝试签名前验证文件访问权限。
public void signDocument(String inputPath, String outputPath) {
File inputFile = new File(inputPath);
if (!inputFile.exists()) {
throw new IllegalArgumentException("Input file not found: " + inputPath);
}
if (!inputFile.canRead()) {
throw new SecurityException("Cannot read input file: " + inputPath);
}
File outputDir = new File(outputPath).getParentFile();
if (outputDir != null && !outputDir.exists()) {
outputDir.mkdirs();
}
try (Signature signature = new Signature(inputPath)) {
signature.sign(outputPath, signOptions);
}
}
- 若多线程可能操作同一文件,请同步访问。
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentHashMap;
public class SignatureManager {
private final ConcurrentHashMap<String, ReentrantLock> documentLocks = new ConcurrentHashMap<>();
public void signDocument(String documentPath, BarcodeSignOptions options) {
ReentrantLock lock = documentLocks.computeIfAbsent(documentPath, k -> new ReentrantLock());
lock.lock();
try (Signature signature = new Signature(documentPath)) {
signature.sign(documentPath + ".signed", options);
} finally {
lock.unlock();
}
}
}
实施测试
单元测试模板
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.*;
public class BarcodeSignatureTest {
@TempDir
Path tempDir;
@Test
public void testSignAndVerify() throws Exception {
// Setup
String testDoc = "test-document.pdf";
String outputDoc = tempDir.resolve("signed-test.pdf").toString();
String testData = "TEST-USER-12345";
// Sign
try (Signature signature = new Signature(testDoc)) {
BarcodeSignOptions signOptions = new BarcodeSignOptions(testData, BarcodeTypes.Code128);
signature.sign(outputDoc, signOptions);
}
// Verify
try (Signature signature = new Signature(outputDoc)) {
BarcodeVerifyOptions verifyOptions = new BarcodeVerifyOptions();
verifyOptions.setText(testData);
verifyOptions.setEncodeType(BarcodeTypes.Code128);
boolean isValid = signature.verify(verifyOptions) != null;
assertTrue(isValid, "Signature should be valid");
}
}
@Test
public void testSearchFindsSignature() throws Exception {
String signedDoc = "signed-document.pdf";
try (Signature signature = new Signature(signedDoc)) {
BarcodeSearchOptions searchOptions = new BarcodeSearchOptions();
searchOptions.setAllPages(true);
List<BarcodeSignature> signatures = signature.search(BarcodeSignature.class, searchOptions);
assertFalse(signatures.isEmpty(), "Should find at least one signature");
assertEquals("TEST-USER-12345", signatures.get(0).getText());
}
}
}
集成检查清单
- ✅ 测试所有支持的格式(PDF、DOCX、XLSX、PPTX、PNG)。
- ✅ 验证条形码在打印‑扫描循环后仍可读取。
- ✅ 使用最大长度数据字符串进行压力测试。
- ✅ 确认在 A4、Letter 以及自定义页面尺寸上的定位准确。
- ✅ 对多个文档进行并发签名。
- ✅ 监控 > 500 页文档的内存使用情况。
- ✅ 确保条形码扫描仪能够可靠读取输出。
日志与监控
在每个操作前后添加有意义的日志:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DocumentSigningService {
private static final Logger logger = LoggerFactory.getLogger(DocumentSigningService.class);
public void signDocument(String docPath, String barcodeData) {
logger.info("Starting signature process for document: {}", docPath);
logger.debug("Barcode data length: {} characters", barcodeData.length());
try (Signature signature = new Signature(docPath)) {
BarcodeSignOptions options = new BarcodeSignOptions(barcodeData, BarcodeTypes.Code128);
long startTime = System.currentTimeMillis();
signature.sign(docPath + ".signed", options);
long duration = System.currentTimeMillis() - startTime;
logger.info("Successfully signed document in {}ms", duration);
} catch (Exception e) {
logger.error("Failed to sign document: {}", docPath, e);
throw new RuntimeException("Signature operation failed", e);
}
}
}
跟踪关键指标,如处理时间、内存消耗和错误率:
// Track success/failure rates
metricsService.incrementCounter("signature.sign.success");
metricsService.incrementCounter("signature.verify.failure");
// Track performance
metricsService.recordTimer("signature.sign.duration", duration);
// Track document sizes
metricsService.recordHistogram("signature.document.pages", pageCount);
来自真实项目的专业技巧
批量处理策略
处理数百个文件时,按批次进行并报告进度:
public void signBatch(List<String> documents, String barcodePrefix) {
int total = documents.size();
int completed = 0;
int failed = 0;
for (String docPath : documents) {
try {
String barcodeData = barcodePrefix + "-" + UUID.randomUUID().toString();
signDocument(docPath, barcodeData);
completed++;
if (completed % 10 == 0) {
logger.info("Progress: {}/{} documents signed", completed, total);
}
} catch (Exception e) {
failed++;
logger.warn("Failed to sign: {}", docPath, e);
}
}
logger.info("Batch complete: {} succeeded, {} failed", completed, failed);
}
外部化配置
将条形码设置(类型、尺寸、颜色)存放在 properties 文件中,便于在不重新编译的情况下调优:
public class BarcodeConfig {
private int defaultWidth = 100;
private int defaultHeight = 40;
private String defaultBarcodeType = "Code128";
private String defaultColor = "BLACK";
// Load from properties file or environment variables
public BarcodeConfig() {
this.defaultWidth = Integer.parseInt(
System.getProperty("barcode.width", "100")
);
// ... load other properties
}
public BarcodeSignOptions createDefaultOptions(String text) {
BarcodeSignOptions options = new BarcodeSignOptions(text, getBarcodeType());
options.setWidth(defaultWidth);
options.setHeight(defaultHeight);
options.setForeColor(getColor());
return options;
}
}
统一条形码负载格式
使用 EMPID|TIMESTAMP|DOCID 之类的分隔格式,便于解析且保持一致:
public class BarcodeDataBuilder {
private String userId;
private String action;
private LocalDateTime timestamp;
private String documentId;
public BarcodeDataBuilder setUserId(String userId) {
this.userId = userId;
return this;
}
public BarcodeDataBuilder setAction(String action) {
this.action = action;
return this;
}
public BarcodeDataBuilder setDocumentId(String documentId) {
this.documentId = documentId;
return this;
}
public String build() {
this.timestamp = LocalDateTime.now();
// Format: VERSION|USER|ACTION|DOC_ID|TIMESTAMP|CHECKSUM
String data = String.join("|",
"v1",
userId,
action,
documentId,
timestamp.toString()
);
// Add simple checksum
int checksum = data.hashCode();
return data + "|" + checksum;
}
public static ParsedBarcodeData parse(String barcodeData) {
String[] parts = barcodeData.split("\\|");
if (parts.length != 6 || !parts[0].equals("v1")) {
throw new IllegalArgumentException("Invalid barcode format");
}
return new ParsedBarcodeData(parts[1], parts[2], parts[3],
LocalDateTime.parse(parts[4]));
}
}
// Usage:
String barcodeData = new BarcodeDataBuilder()
.setUserId("john.smith")
.setAction("APPROVED")
.setDocumentId("INV-2025-042")
.build();
自动检测文档类型
根据目标是 PDF、Word 还是图片,动态调整条形码尺寸:
public BarcodeSignOptions getOptimalOptions(String documentPath, String text) {
String extension = documentPath.substring(documentPath.lastIndexOf(".") + 1).toLowerCase();
BarcodeSignOptions options = new BarcodeSignOptions(text, BarcodeTypes.Code128);
switch (extension) {
case "pdf":
// PDFs can handle larger, detailed barcodes
options.setWidth(150);
options.setHeight(50);
break;
case "docx":
case "doc":
// Word docs need compact signatures that don't disrupt flow
options.setWidth(100);
options.setHeight(35);
options.setVerticalAlignment(VerticalAlignment.Bottom);
break;
case "xlsx":
case "xls":
// Excel needs signatures that don't obscure data
options.setWidth(80);
options.setHeight(30);
options.setHorizontalAlignment(HorizontalAlignment.Right);
break;
default:
// Safe defaults
options.setWidth(100);
options.setHeight(40);
}
return options;
}
其他资源
- GroupDocs.Signature 文档 – 完整 API 指南和使用示例。
- GroupDocs 支持论坛 – 社区帮助与故障排查。
- API 参考 – 详细的方法签名和参数说明。
常见问答
问:可以在 Java 17 上使用 GroupDocs.Signature 吗?
答:可以——该库完全兼容 JDK 8、 11 和 17。
问:条形码能在打印‑扫描循环后仍然可用吗?
答:使用足够尺寸和对比度的 Code128 或 QR 时,条形码在打印并再次扫描后仍可读取。
问:单个文档最多能容纳多少条形码?
答:没有硬性上限,但为获得最佳性能,建议每份文档的条形码总数保持在 200 以下。
问:开发构建是否需要许可证?
答:临时许可证可去除评估水印;正式生产部署必须使用正式许可证。
问:可以签署受密码保护的 PDF 吗?
答:可以——在创建 Signature 对象时提供密码,API 会在内部解锁文件。
最后更新: 2026-07-15
测试环境: GroupDocs.Signature 23.9 for Java
作者: GroupDocs