在 Java 中為 PDF 添加條碼 – 使用 GroupDocs 進行簽署與驗證
如果您需要一種快速、直觀的方式為內部工作流程的文件加上標籤,於 Java 中為 PDF 添加條碼是一個完美的解決方案。使用 GroupDocs.Signature,您可以嵌入、驗證、搜尋、更新與刪除條碼簽章,且不需要完整 PKI‑基礎的數位簽章所帶來的負擔。本教學將逐步說明從環境設定到上線最佳實踐的每個步驟。
快速回答
- 什麼函式庫可在 Java 中為 PDF 添加條碼? GroupDocs.Signature for Java。
- 我可以簽署 PDF、Word 和圖像嗎? 可以 – API 支援超過 30 種格式。
- 開發是否需要授權? 可免費取得 30 天的臨時授權;正式上線需購買完整授權。
- 簽署 PDF 需要多少行程式碼? 只要兩行:建立
Signature物件並呼叫sign。 - 條碼驗證是否區分大小寫? 是 – 文字必須完全相符,包含大小寫與空白。
什麼是 Java 中的 PDF 條碼添加?
add barcode pdf java 指的是使用 Java 程式碼將條碼(例如 Code128、QR 或 Data Matrix)嵌入 PDF 檔案的過程。此技術提供可機器讀取的標籤,能被掃描或程式化驗證,從而加速內部系統的文件追蹤。
為什麼使用條碼簽章而不是完整的數位簽章?
條碼簽章產生與驗證的速度比 PKI‑基礎的數位簽章快 30‑50 %,且在列印‑掃描循環後仍能可靠運作。它不需憑證管理,非常適合大量、內部的工作流程,對於不需要加密證明的情境尤為理想。
先決條件
- Java 開發工具包 (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 版本發布 取得 JAR,並加入 classpath。
取得授權
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‑基礎的數位簽章。
設定 GroupDocs.Signature for Java
核心類別定義
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
警告: 驗證區分大小寫與空白,簽署前請先修剪與正規化資料。
如何在文件中搜尋條碼簽章?
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 個字元 | 極小空間、工業標籤 | 極小 |
建議: 先使用 Code128 來處理簡單 ID;若需嵌入 URL 或較大負載,改用 QR;僅在舊版整合時才考慮 Code39。
常見問題與解決方案
問題:「驗證時未找到條碼」
症狀: 即使條碼可見,驗證仍回傳 false。
常見原因
- 大小寫不符(例如 “John Smith” vs. “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
生產環境最佳實踐
效能最佳化
- Reuse Signature Instances – 為每份文件建立單一
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(/* ... */);
}
}
- Search Specific Pages Only – 僅限制搜尋範圍至預期出現條碼的頁面。
// 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
- Choose the Simplest Barcode Type – 較簡單的條碼產生速度更快;除非真的需要大量資料,否則避免使用 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);
安全性考量
- Never Encode Sensitive Personal Data – 條碼易於被讀取,請避免放入個人身份資訊(如身分證號、密碼)。
// 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
- Validate 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;
}
- Add Timestamps – 在條碼載荷中加入時間戳,可防止重放攻擊。
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;
}
錯誤處理模式
- Always Use 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);
}
- Validate File Access 在嘗試簽署前先確認檔案可存取。
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);
}
}
- Synchronize Access 若多執行緒可能同時操作同一檔案,請做好同步機制。
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 參考文件 – 詳細的方法簽名與參數說明。
常見問答
Q: 可以在 Java 17 上使用 GroupDocs.Signature 嗎?
A: 可以 – 此函式庫完全相容於 JDK 8、 11 與 17。
Q: 條碼在列印‑掃描循環後仍能辨識嗎?
A: 使用 Code128 或 QR,且尺寸與對比度足夠時,條碼在列印與掃描後仍可被掃描。
Q: 單一文件最多能容納多少條條碼?
A: 雖無硬性上限,但為了效能建議每份文件的條碼總數不超過 200。
Q: 開發版需要授權嗎?
A: 臨時授權可移除評估浮水印;正式上線必須購買完整授權。
Q: 能簽署受密碼保護的 PDF 嗎?
A: 能 – 在建立 Signature 物件時提供密碼,API 會在內部解鎖檔案。
最後更新: 2026-07-15
測試環境: GroupDocs.Signature 23.9 for Java
作者: GroupDocs