在 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 並非免費供正式上線使用,但提供彈性授權選項:

專業提示: 開發階段先使用臨時授權;切換為永久金鑰後即可移除浮水印。

快速環境檢查

加入相依後,執行簡單的測試程式:

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。

常見原因

  1. 大小寫不符(例如 “John Smith” vs. “john smith”)。
  2. 編碼文字多了額外空白。
  3. 驗證選項指定了錯誤的條碼類型。
  4. 搜尋了錯誤的頁碼。

解決方案: 在簽署與驗證前先正規化文字,並確保 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

生產環境最佳實踐

效能最佳化

  1. 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(/* ... */);
    }
}
  1. 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
  1. 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);

安全性考量

  1. 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
  1. 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;
}
  1. 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;
}

其他資源

常見問答

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

相關教學