💡 完整的工作範例可在 GitHub 上取得:
python-linux-container-pdf-signing
介紹
此腳本在本機可以正常執行。將它容器化於 python:3.11-slim 時,會在 import groupdocs.signature 失敗。修正後,第一個簽章仍會失敗。兩個錯誤都沒有說明實際缺少了什麼。
使用 Python 進行容器簽章是 GroupDocs.Signature 的工作流程,需要兩個佈建層而非一層:綁定所依賴的 .NET 執行時函式庫,以及每個文字簽章必須使用的字型。此教學同時建構這兩個層,並提供在執行時解析字型家族的腳本(而非硬編碼),讓相同程式碼能在容器與開發機上皆能運作。
為何兩層都很重要
GroupDocs.Signature for Python 是 .NET 綁定,因此在任何匯入成功之前,必須先有 libicu 與相容 OpenSSL 1.1 的函式庫。這是第一層,相關說明可見於 Running in Docker。
兩層常被混為一談的原因是,兩者都會在匯入相關時失敗,且錯誤訊息都未指明原因。缺少 libssl1.1 會出現共享物件的載入錯誤;缺少字型則會出現被代理例外包住的簽章錯誤。兩者都不會說「你的基礎映像太小」,而這正是實際情況。
第二層是字型,這層最常讓人感到意外。python:3.11-slim 完全不含任何字型檔案。GroupDocs.Signature 不會自動替代缺失的字型家族——指定未安裝的字型會拋出例外,且不會寫入任何內容——而清除字型也不是解法,因為函式庫會再度要求預設字型,結果仍然失敗。在沒有字型的映像中,文字簽章根本無法完成。
前置需求
- Python 3.11(wheel 最高支援 CPython 3.14 以下)
groupdocs-signature-net==26.1- 若想自行觀察兩次失敗的過程,需安裝 Docker(大約十分鐘即可完成測試)。
安裝
pip install groupdocs-signature-net==26.1
步驟 1 ─ 建置 .NET 層
libssl1.1 在 bookworm 中不存在,必須從固定的 Debian 快照取得:
ENV SNAPSHOT_DATE=20220328T000000Z
RUN echo "deb [trusted=yes] http://snapshot.debian.org/archive/debian/${SNAPSHOT_DATE} bullseye main" \
> /etc/apt/sources.list.d/debian-archive.list \
&& apt-get -o Acquire::Check-Valid-Until=false update \
&& apt-get install -y --no-install-recommends \
libicu67 \
libssl1.1 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
重點說明:
- 此層僅讓匯入成功,與字型無關。
- 固定快照日期可確保在套件庫變動時仍能重現相同建置。
步驟 2 ─ 建置字型層
以下四個套件作為獨立層,以便在需要重現失敗時可將其註解掉:
RUN apt-get update && apt-get install -y --no-install-recommends \
fontconfig \
fonts-dejavu-core \
fonts-liberation \
fonts-noto-cjk \
&& fc-cache -f \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
fontconfig為字型解析器,提供fc-list。fonts-dejavu-core提供拉丁、希臘與西里爾字型的最小集合。fonts-liberation用於文件中以名稱指定 Arial 或 Times New Roman 的情況。fonts-noto-cjk包含中文、日文與韓文字型。
步驟 3 ─ 詢問函式庫可使用的字型家族
直接掃描 /usr/share/fonts 取得檔名看似可行,實際上並非如此:fonts-noto-cjk 會安裝 NotoSansCJK-Regular.ttc,其家族名稱為 Noto Sans CJK JP。可移植的做法是以真實簽章寫入暫存檔的方式探測,並將失敗結果轉為回傳值:
with signature.Signature(source_path) as sign:
options = TextSignOptions()
options.text = "probe"
options.left = 10
options.top = 10
options.width = 60
options.height = 20
font = SignatureFont()
font.family_name = family_name
font.size = 10.0
options.font = font
sign.sign(scratch, [options])
return None
請特別注意 font.size = 10.0。綁定會將 size 轉為 .NET 的 float,若傳入 int 會拋出「numeric argument expected, got ‘int’」的錯誤。因為此錯誤發生在探測階段,所有候選家族都會失敗,最終呈現的結果與字型缺失的映像相同。最初我在已安裝全部字型的映像中加入了三個字型套件,才發現這個問題。
解決方式是使用迴圈:
for candidate in candidates:
if try_family(source_path, candidate) is None:
return candidate
return None
步驟 4 ─ 使用解析出的字型簽章,並驗證簽章結果
拉丁字型是必須的,CJK 字型則為可選:
with signature.Signature(source_path) as sign:
options = [build_text_options(LATIN_TEXT, latin_family, 50)]
if cjk_family:
options.append(build_text_options(CJK_TEXT, cjk_family, 120))
result = sign.sign(output_path, options)
return len(result.succeeded)
接著驗證,因為 CJK 若渲染成空白方塊不會拋出例外:
options = TextVerifyOptions()
options.text = expected_text
options.match_type = gsd.TextMatchType.CONTAINS
options.all_pages = True
result = sign.verify(options)
CONTAINS 為刻意使用的比對模式:在評估模式下,函式庫會在頁面加入測試文字,若使用完全相等的比對,會把本應通過的文件誤判為失敗。
為何文件上寫著 Python 僅支援有限的 Linux?
Running in Docker 頁面列出已支援 Linux 的 Python 套件,卻未提及 Signature。使用 groupdocs-signature-net==26.1 時,此範例在 python:3.11-slim 內即可完成簽章與驗證(包括 CJK),前提是兩個層皆已安裝。請將此清單視為過時資訊,而非阻礙,並以自己的版本自行驗證後再投入部署。
真實案例
一個開立發票的服務需要在產生的 PDF 上蓋上批准行,正好需要這兩層:.NET 層、至少一個拉丁字型,以及啟動時的解析檢查。此檢查可將「壞的部署」轉變為「容器啟動失敗」,避免發票一張張靜默失敗。另一個接受任意文字腳本(包括中文、日文、韓文)的文件入口網站,同樣需要 CJK 套件與驗證步驟,因為這是防止渲染成方塊卻仍被視為簽章成功的唯一機制。
解析檢查的放置位置
將檢查放在每個程序只會執行一次的地方:模組層級呼叫、FastAPI 的 lifespan handler、Django 的 AppConfig.ready,或是 worker 主程式的前幾行。檢查會回傳兩個值:拉丁字型家族與 CJK 字型家族,並將它們寫入啟動日誌,與字型數量一起列出。
這樣的放置方式不僅節省探測時間,還能把失敗從請求處理(客戶問題、沒人看的堆疊追蹤)移到啟動階段(部署未成功、有人在監控)。若容器因「找不到可用的字型家族,請安裝 fonts-dejavu-core」而退出,根本不需要再除錯。
常見問題排除
import groupdocs.signature 失敗
.NET 層缺失或建置時快照倉庫無法取得。這是第一層問題,與字型無關。請先檢查 apt 步驟的建置日誌,因為快照取得失敗不會阻止映像繼續建置。
所有候選字型都失敗,但 fc-list 顯示有字型
檢查 font.size 是否傳入了 int,再加入其他套件前先修正。
簽章已寫入,但 CJK 文字顯示為方塊
缺少 fonts-noto-cjk。簽章使用的字型家族沒有對應的字形,這正是驗證步驟存在的原因:它會在此情況下失敗。
兩個映像實際輸出內容
執行兩個映像,觀察前四行輸出。字型缺失的映像會顯示 font files on disk: 0、兩個解析行皆為 (none)、缺字型的錯誤訊息,然後以代碼 3 結束並印出最小修正建議。已佈建的映像則會顯示非零字型數量、拉丁字型為 DejaVu Sans、CJK 為 Noto Sans CJK JP,兩個簽章皆已套用,且兩段文字皆驗證通過。
將這對輸出貼到部署說明中,之後有人更換基礎映像時,只要比對這個參考,即可快速判斷容器是否健康,無需深入了解 fontconfig。
結論
兩層加一次探測。先安裝 .NET 相依套件,再安裝至少 fontconfig 與 DejaVu,透過詢問而非假設取得可用字型家族,最後在完成工作前驗證輸出。程式碼量不多,但這些步驟在事後回顧時顯而易見,卻在追蹤錯誤時幾乎不可見。範例倉庫同時提供兩個 Dockerfile,讓「可用映像」與「壞的映像」只差一步建置。