💡 نمونهٔ کامل قابل اجرا در گیتهاب موجود است:
python-linux-container-pdf-signing
مقدمه
اسکریپت بهصورت محلی کار میکند. شما آن را روی python:3.11-slim درونکانتینر میکنید و در زمان import groupdocs.signature با خطا مواجه میشوید. این خطا را رفع میکنید، اما دوباره در اولین امضا خطا میدهد. هیچیک از این خطاها بهطور واضح نشان نمیدهند چه چیزی واقعاً کم است.
امضای درونکانتینر با پایتون یک جریان کاری GroupDocs.Signature است که به دو لایهٔ فراهمسازی بهجای یک لایه نیاز دارد: کتابخانههای زمان اجرا .NET که بایندینگ بر پایهٔ آنها ساخته شده و فونتهایی که هر امضای متنی باید با آنها رندر شود. این آموزش هر دو لایه را میسازد، سپس اسکریپتی که خانوادهٔ فونت را در زمان اجرا پیدا میکند بهجای سختکد کردن یک فونت، طوری که همان کد هم در کانتینر و هم روی ماشینی که آن را نوشتید کار کند.
چرا هر دو لایه مهماند
GroupDocs.Signature برای پایتون یک بایندینگ .NET است، بنابراین libicu و یک کتابخانهٔ سازگار با OpenSSL 1.1 باید قبل از هر import موفق وجود داشته باشند. این لایهٔ اول است و بهخوبی در Running in Docker مستند شده است.
دلیل ترکیب این دو لایه این است که هر دو در لحظات نزدیک به import شکست میخورند و هیچیک از خطاها دلیل را نام نمیبرند. یک libssl1.1 گمشده خطای لودر دربارهٔ یک شیء مشترک میدهد؛ یک فونت گمشده خطای امضا را در یک استثنای پروکسی میپیچاند. هیچیک نمیگوید «تصویر پایهتان خیلی کوچک است»، که در واقع همان معنای واقعی هر دو است.
لایهٔ دوم فونتها هستند و همان چیزی است که مردم را شگفتزده میکند. python:3.11-slim هیچ فایل فونتی ندارد. GroupDocs.Signature جایگزینی برای یک خانوادهٔ گمشده انجام نمیدهد – نامگذاری یک خانوادهای که نصب نشده باشد باعث خطا میشود و هیچچیزی نوشته نمیشود – و پاکسازی فونت نیز راهحل نیست، زیرا کتابخانه سپس سعی میکند از پیشفرض خود استفاده کند و بهطور یکسان شکست میخورد. در یک تصویر بدون فونت، یک امضای متنی بهسادگی امکانپذیر نیست.
پیشنیازها
پایتون 3.11 (چرخدندهٔ چرخدنده زیر CPython 3.14) و groupdocs-signature-net==26.1. Docker اگر میخواهید هر دو خطا را عمداً ببینید، که ارزش حدود ده دقیقه زمان را دارد.
نصب
pip install groupdocs-signature-net==26.1
گام ۱ - ساخت لایهٔ .NET
libssl1.1 در bookworm موجود نیست، بنابراین از یک snapshot ثابت 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/*
نکات کلیدی:
- این لایه فقط باعث میشود
importکار کند؛ دربارهٔ فونتها چیزی نمیگوید. - ثابت کردن تاریخ snapshot باعث میشود ساخت قابل تکرار بماند وقتی که آرشیو بهروز میشود.
گام ۲ - ساخت لایهٔ فونت
چهار بسته، بهعنوان لایهٔ جداگانه نگه داشته میشوند تا بتوان آن را برای بازتولید خطا کامنتگذاری کرد:
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 برای چینی، ژاپنی و کرهای است.
گام ۳ - پرسیدن از کتابخانهٔ کدام خانوادهٔ فونت قابل استفاده است
اسکن /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. بایندینگ اندازه را به یک float .NET تبدیل میکند و یک int را با خطای «numeric argument expected, got ‘int’» رد میکند. چون این داخل آزمون رخ میدهد، هر خانوادهٔ کاندیدا شکست میخورد و خروجی دقیقاً شبیه یک تصویر بدون فونت بهنظر میرسد. من سه بستهٔ فونت را به یک تصویر اضافه کردم که قبلاً همهٔ آنها را داشت، قبل از اینکه این نکتهٔ واضح را ببینم.
راهحل سپس یک حلقه است:
for candidate in candidates:
if try_family(source_path, candidate) is None:
return candidate
return None
گام ۴ - امضای مواردی که شناسایی شد، و تأیید آنچه امضا کردید
خانوادهٔ لاتین الزامی است، خانوادهٔ 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 عمداً انتخاب شده است: در حالت ارزیابی کتابخانه متن آزمایشی را به صفحه اضافه میکند و یک تطبیق دقیق باعث میشود سندی که کاملاً سالم است بهعنوان شکست گزارش شود.
دربارهٔ مستنداتی که میگویند پایتون پشتیبانی محدودی از لینوکس دارد چه میگویید؟
صفحهٔ Running in Docker بستههای پایتون آماده برای لینوکس را فهرست میکند و Signature را حذف کرده است. در groupdocs-signature-net==26.1 این نمونه داخل python:3.11-slim امضا و تأیید شد، حتی CJK، با هر دو لایه نصب شده. این فهرست را بهعنوان منسوخ در نظر بگیرید نه بهعنوان مانعی، و قبل از تعهد به استقرار، نسخهٔ خود را تأیید کنید.
کاربردهای دنیای واقعی
یک سرویس صدور فاکتور که خط تأیید را روی PDFهای تولید شده میچسباند دقیقاً به این موارد نیاز دارد: لایهٔ .NET، یک فونت لاتین، و یک بررسی حل مسئلهٔ راهاندازی. این بررسی همان چیزی است که یک استقرار بد را به یک کانتینری تبدیل میکند که از راهاندازی خودداری میکند، نه صفی از فاکتورهایی که بهصورت ساکن یکبهیک شکست میخورند. یک پورتال اسناد که نامهای مشتریان را بههر اسکریپتی میپذیرد، به بستهٔ CJK نیز نیاز دارد، بهعلاوه گام تأیید، زیرا این تنها چیزی است که بین یک جعبهٔ رندر شده و یک نام امضا شده قرار دارد.
مکان مناسب برای بررسی حل مسئله
آن را در هر جایی که یکبار برای هر پردازش اجرا میشود قرار دهید: یک فراخوانی در سطح ماژول، یک هندلر lifespan در FastAPI، یک AppConfig.ready در Django، یا خطوط اول تابع اصلی یک worker. دو مقدار از آن برمیگردد، خانوادهٔ لاتین و خانوادهٔ CJK، و هر دو باید در لاگ راهاندازی کنار شمارش فونتها ثبت شوند.
این مکانگذاری بیش از صرفهجویی در زمان آزمون است. شکست را از زمان پردازش درخواست (که مشکل یک مشتری است و هیچکس استکتریسی را نمیخواند) به زمان راهاندازی میبرد، جایی که یک استقرار که بالا نیامده است و کسی در حال نظارت است، بهسرعت متوجه میشود. یک کانتینری که با پیام «no usable font family, install fonts-dejavu-core» خارج میشود، نیازی به دیباگ ندارد.
عیبیابی مشکلات رایج
import groupdocs.signature شکست میخورد
لایهٔ .NET گم شده یا مخزن snapshot در زمان ساخت در دسترس نبوده است. این لایهٔ اول است و ربطی به فونتها ندارد. قبل از دستکاری کد امضا، لاگ ساخت را برای مرحلهٔ apt بررسی کنید، زیرا یک fetch snapshot ناموفق باعث نمیشود تصویر ساخت متوقف شود.
هر فونت کاندیدا شکست میخورد، اما fc-list فونتها را نشان میدهد
قبل از افزودن بستههای بیشتر، font.size را برای وجود یک int بررسی کنید.
امضا وجود دارد اما متن CJK بهصورت جعبه است
fonts-noto-cjk گم شده است. امضا با یک خانوادهای نوشته شده که گلیفهای مربوط به آن نقاط کد را ندارد؛ به همین دلیل گام تأیید وجود دارد: در این حالت دقیقاً شکست میکند، در حالی که امضا موفقیت را گزارش میداد.
آنچه دو تصویر واقعاً چاپ میکنند
هر دو را اجرا کنید و چهار خط اول را بخوانید. تصویر بدون فونت گزارش میدهد font files on disk: 0، هر دو خط حل مسئله بهصورت (none)، خطای عمدی «missing-font»، و سپس با کد خروج ۳ همراه با حداقل اصلاح چاپ میشود. تصویر فراهمشده شمارش فونت غیر صفر، DejaVu Sans برای لاتین و Noto Sans CJK JP برای CJK، دو امضا اعمالشده، و هر دو متن تأیید شده را نشان میدهد.
این جفت خروجی، مدرکی است که ارزش نگهداری دارد. آن را در یادداشتهای استقرار خود بچسبانید و شخص بعدی که تصویر پایه را تغییر میدهد، مرجعی برای اینکه یک کانتینر سالم چگونه باید باشد، بدون نیاز به درک fontconfig داشته باشد.
نتیجهگیری
دو لایه و یک آزمون. وابستگیهای .NET را نصب کنید، حداقل fontconfig و DejaVu را نصب کنید، خانواده را با پرسیدن بهجای فرض کردن شناسایی کنید، و خروجی را قبل از اعلام اتمام کار تأیید کنید. هیچیک از اینها کد زیادی نیست و همهٔ آنها چیزهایی هستند که پس از وقوع واضح میشوند و در یک traceback نامرئیاند. مخزن نمونه هر دو Dockerfile را ارائه میدهد، بنابراین تفاوت بین یک تصویر کارآمد و یک تصویر خراب فقط یک ساخت جداگانه است.