💡 ตัวอย่างการทำงานเต็มที่พร้อมใช้งานบน GitHub:
python-linux-container-pdf-signing

บทนำ

สคริปต์ทำงานได้ในเครื่องของคุณ คุณทำคอนเทนเนอร์ด้วย python:3.11-slim แล้วมันล้มเหลวที่ import groupdocs.signature คุณแก้ไขส่วนนี้แล้วก็ล้มเหลวอีกครั้งที่ลายเซ็นแรก ทั้งสองข้อผิดพลาดไม่ได้บอกว่าอะไรหายไปจริง ๆ

การเซ็นคอนเทนเนอร์ด้วย Python เป็นเวิร์กโฟลว์ของ GroupDocs.Signature ที่ต้องการสองชั้นการจัดหาแทนหนึ่งชั้น: ไลบรารีรันไทม์ .NET ที่ไบน์ดิงสร้างขึ้น, และฟอนต์ที่ลายเซ็นข้อความทุกอันต้องใช้ในการเรนเดอร์ บทเรียนนี้จะสร้างทั้งสองชั้น, แล้วสคริปต์ที่ค้นหาครอบครัวฟอนต์ในเวลารันแทนการกำหนดค่าแบบคงที่, เพื่อให้โค้ดเดียวทำงานได้ทั้งในคอนเทนเนอร์และบนเครื่องที่คุณเขียนมัน

ทำไมทั้งสองชั้นจึงสำคัญ

GroupDocs.Signature สำหรับ Python เป็นไบน์ดิงของ .NET, ดังนั้น libicu และไลบรารีที่เข้ากันได้กับ OpenSSL 1.1 ต้องมีอยู่ก่อนที่การ import ใด ๆ จะสำเร็จ นั่นคือชั้นที่หนึ่งและได้รับการอธิบายไว้อย่างละเอียดใน Running in Docker

เหตุผลที่สองชั้นถูกผสานเข้าด้วยกันคือทั้งสองล้มเหลวในช่วงที่เกี่ยวข้องกับการ import และไม่มีข้อผิดพลาดใดบอกสาเหตุ libssl1.1 ที่หายไปจะให้ข้อผิดพลาดโหลดเกี่ยวกับ shared object; ฟอนต์ที่หายไปจะให้ข้อผิดพลาดการเซ็นที่ห่อหุ้มด้วย proxy exception ทั้งสองไม่ได้บอกว่า “ภาพฐานของคุณเล็กเกินไป”, ซึ่งเป็นสิ่งที่จริง ๆ แล้วหมายถึง

ชั้นที่สองคือฟอนต์, และเป็นชั้นที่ทำให้คนหลายคนประหลาดใจ 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, ดังนั้นจึงต้องดึงจาก 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 date ทำให้การสร้างสามารถทำซ้ำได้เมื่อ archive มีการเปลี่ยนแปลง

ขั้นตอนที่ 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 มีฟอนต์พื้นฐานสำหรับ Latin, Greek และ Cyrillic fonts-liberation รองรับเอกสารที่อ้างอิง Arial หรือ Times New Roman ตามชื่อ fonts-noto-cjk ครอบคลุม Chinese, Japanese และ Korean

ขั้นตอนที่ 3 - ถามไลบรารีว่าครอบครัวฟอนต์ใดที่สามารถใช้ได้

การสแกน /usr/share/fonts เพื่อหาไฟล์ชื่อดูเหมือนจะเทียบเท่าแต่จริง ๆ แล้วไม่ใช่: fonts-noto-cjk ติดตั้ง NotoSansCJK-Regular.ttc ซึ่งชื่อครอบครัวคือ Noto Sans CJK JP วิธีที่พกพาได้คือการทำ probe – ลายเซ็นจริงลงไฟล์ชั่วคราว – แล้วแปลงความล้มเหลวเป็นค่า:

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 อย่างใกล้ชิด ไบน์ดิงแปลงขนาดเป็น .NET float และปฏิเสธ int ด้วยข้อความ numeric argument expected, got 'int' เนื่องจากเกิดขึ้นภายใน probe, ทุกครอบครัวที่เป็นไปได้ล้วนล้มเหลวและผลลัพธ์ดูเหมือนภาพที่ไม่มีฟอนต์เลย ฉันได้เพิ่มสามแพ็กเกจฟอนต์ลงในภาพที่มีทั้งหมดแล้วก่อนจะสังเกตว่ามีการระบุค่าแบบตัวอักษร

การแก้ไขจึงเป็นลูป:

for candidate in candidates:
    if try_family(source_path, candidate) is None:
        return candidate
return None

ขั้นตอนที่ 4 - เซ็นสิ่งที่ได้แก้ไขแล้ว, ตรวจสอบสิ่งที่คุณเซ็น

ครอบครัว Latin จำเป็น, ครอบครัว 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 ระบุแพ็กเกจ Python ที่พร้อมใช้งานบน Linux แต่ไม่ได้รวม Signature ไว้ ใน groupdocs-signature-net==26.1 ตัวอย่างนี้เซ็นและตรวจสอบได้ภายใน python:3.11-slim, รวม CJK ด้วย, เมื่อทั้งสองชั้นถูกติดตั้ง ถือว่ารายการนั้นล้าสมัยแล้วและควรตรวจสอบด้วยเวอร์ชันของคุณเองก่อนทำการปรับใช้

การประยุกต์ใช้ในโลกจริง

บริการออกใบแจ้งหนี้ที่ต้องประทับบรรทัดการอนุมัติลงบน PDF ที่สร้างขึ้นต้องการสิ่งนี้อย่างแม่นยำ: ชั้น .NET, ฟอนต์ Latin หนึ่งตัว, และการตรวจสอบการแก้ไขขณะเริ่มต้น การตรวจสอบนี้คือสิ่งที่ทำให้การปรับใช้ที่ไม่ดีกลายเป็นคอนเทนเนอร์ที่ไม่สามารถเริ่มทำงานได้, แทนที่จะเป็นคิวใบแจ้งหนี้ที่ล้มเหลวแบบเงียบ ๆ พอร์ทัลเอกสารที่รับชื่อลูกค้าในสคริปต์ใด ๆ ก็ต้องการแพ็กเกจ CJK ด้วย, พร้อมขั้นตอนการตรวจสอบ, เพราะนี่คือสิ่งเดียวที่แยกความแตกต่างระหว่างกล่องว่างที่เรนเดอร์และชื่อที่เซ็นแล้ว

ที่ที่การตรวจสอบการแก้ไขควรอยู่

ใส่ไว้ในส่วนที่ทำงานหนึ่งครั้งต่อกระบวนการ: การเรียกระดับโมดูล, ตัวจัดการ FastAPI lifespan, AppConfig.ready ของ Django, หรือบรรทัดแรกของ worker หลัก จะได้ค่าที่ออกมาสองค่า, ครอบครัว Latin และครอบครัว CJK, ทั้งสองควรบันทึกในล็อกเริ่มต้นข้างนับจำนวนฟอนต์

การวางตำแหน่งนี้ทำมากกว่าการประหยัดเวลา probe; มันย้ายความล้มเหลวจากการจัดการคำขอ (ซึ่งเป็นปัญหาของลูกค้าคนเดียวและสแตกเทรซที่ไม่มีใครอ่าน) ไปยังการเริ่มต้น, ซึ่งเป็นการปรับใช้ที่ไม่ขึ้นและมีคนกำลังเฝ้าดูอยู่ คอนเทนเนอร์ที่ออกด้วยข้อความ “no usable font family, install fonts-dejavu-core” ไม่ต้องการการดีบักใด ๆ เลย

การแก้ไขปัญหาที่พบบ่อย

import groupdocs.signature fails
ชั้น .NET หายไปหรือ repository ของ snapshot ไม่สามารถเข้าถึงได้ระหว่างการสร้าง นี่คือชั้นที่หนึ่งและไม่มีส่วนเกี่ยวข้องกับฟอนต์ ตรวจสอบบันทึกการสร้างสำหรับขั้นตอน apt ก่อนจะไปแก้ไขโค้ดการเซ็น, เพราะการดึง snapshot ที่ล้มเหลวไม่ได้หยุดการสร้างอิมเมจ

Every candidate font fails, but fc-list shows fonts
ตรวจสอบว่า font.size ถูกตั้งเป็น int ก่อนเพิ่มแพ็กเกจเพิ่มเติม

The signature is there but the CJK text is boxes
fonts-noto-cjk หายไป ลายเซ็นถูกเขียนด้วยครอบครัวที่ไม่มี glyph สำหรับโค้ดพอยท์เหล่านั้น, ซึ่งเป็นเหตุผลที่ขั้นตอนการตรวจสอบมีอยู่: มันจะล้มเหลวในกรณีนี้โดยเฉพาะ, แม้ว่าการเซ็นจะรายงานว่าประสบความสำเร็จ

สิ่งที่สองภาพจริง ๆ พิมพ์ออกมา

รันทั้งสองและอ่านสี่บรรทัดแรก ภาพที่ไม่มีฟอนต์จะแสดง font files on disk: 0, ทั้งสองบรรทัดการแก้ไขเป็น (none), ข้อผิดพลาดฟอนต์หายโดยเจตนา, แล้วออกด้วยรหัส 3 พร้อมพิมพ์วิธีแก้ไขขั้นต่ำ ภาพที่จัดเตรียมไว้จะแสดงจำนวนฟอนต์ที่ไม่เป็นศูนย์, DejaVu Sans สำหรับ Latin และ Noto Sans CJK JP สำหรับ CJK, มีลายเซ็นสองอันและข้อความทั้งสองได้รับการตรวจสอบ

ผลลัพธ์สองชุดนี้เป็นศิลปวัตถุที่ควรเก็บไว้ คัดลอกลงในบันทึกการปรับใช้ของคุณและคนต่อไปที่เปลี่ยนภาพฐานจะมีอ้างอิงว่าคอนเทนเนอร์ที่สุขภาพดีควรเป็นอย่างไร, โดยไม่ต้องเข้าใจ fontconfig เลย

สรุป

สองชั้นและหนึ่ง probe. ติดตั้ง dependencies ของ .NET, ติดตั้งอย่างน้อย fontconfig และ DejaVu, แก้ไขครอบครัวโดยการถามแทนการสันนิษฐาน, และตรวจสอบผลลัพธ์ก่อนสรุปงาน ไม่มีโค้ดมาก, ทั้งหมดเป็นสิ่งที่ดูชัดเจนเมื่อมองย้อนกลับและมองไม่เห็นในสแตกเทรซ Repository ตัวอย่างส่งมอบ Dockerfile ทั้งสอง, ดังนั้นความแตกต่างระหว่างอิมเมจที่ทำงานและที่เสียหายคือการสร้างหนึ่งขั้นตอน

แหล่งข้อมูลเพิ่มเติม