💡 Ví dụ hoạt động đầy đủ có sẵn trên GitHub:
sign-documents-in-docker-fonts-java
Dịch vụ ký hợp đồng đã hoạt động trong chín tháng
Việc cung cấp phông chữ cho container là bước quyết định liệu một dịch vụ ký Java có hoạt động trong môi trường sản xuất hay chỉ trong các bài kiểm tra mà bạn tình cờ viết. Điều này quan trọng vì lỗi sẽ xuất hiện theo lịch trình: một ảnh JRE cung cấp đủ phông chữ để trông đúng, sau đó giữ lại phần còn lại cho đến khi một tài liệu cụ thể xuất hiện.
Hãy xem xét hình dạng của vấn đề. Một quy trình công việc tài liệu ký hợp đồng, được triển khai trên eclipse-temurin:17-jre, và nó hoạt động. Chín tháng sau, công ty ký khách hàng đầu tiên ở Nhật Bản, tên khách hàng được đưa vào văn bản chữ ký, và công việc thất bại với lỗi Specified font file was not found. Không có gì thay đổi trong dịch vụ. Ảnh này chưa bao giờ có hỗ trợ CJK; không có tài liệu nào yêu cầu nó.
Nguyên nhân kỹ thuật ngắn gọn. eclipse-temurin:17-jre gói sẵn 8 tệp phông DejaVu cho AWT, bao phủ Latin, Greek và Cyrillic. GroupDocs.Signature không thay thế một họ phông chữ bị thiếu, vì vậy yêu cầu một phông chữ hỗ trợ tiếng Nhật sẽ thất bại thay vì giảm cấp, và việc để phông chữ không được đặt cũng không giúp gì vì thư viện sau đó lại yêu cầu Times New Roman, cũng không có.
Tại sao điều này tệ hơn một ảnh không có phông chữ
Các ảnh nền .NET và Python không đi kèm bất kỳ phông chữ nào. Đó là một lỗi tốt hơn: chữ ký đầu tiên ngay lập tức thất bại, trong lần chạy kiểm tra đầu tiên, và ai đó sẽ sửa nó trước khi dịch vụ được đưa ra.
Một ảnh JVM thất bại một phần, đó là phiên bản tốn kém. Lỗi tồn tại trong mã đã đang chạy trong môi trường sản xuất, nó được kích hoạt bởi dữ liệu khách hàng chứ không phải bởi bất kỳ thứ gì trong quá trình triển khai, và người trực trực tiếp thấy lỗi phông chữ từ một dịch vụ mà không ai chạm vào trong nhiều tháng. Chi phí của sự cố không phải là việc sửa - sửa chỉ là một lớp Dockerfile - mà là giờ đồng hồ trước khi ai đó tin rằng phông chữ liên quan.
Sự bất đối xứng này là lý do để xem xét việc bao phủ phông chữ như một điều bạn khẳng định khi khởi động thay vì một điều bạn phát hiện sau.
Nó cũng thay đổi người trả phí. Một ảnh không có phông chữ tốn khoảng 20 phút cho một nhà phát triển trong quá trình thiết lập. Một ảnh chỉ được bao phủ một phần tốn một kỹ sư trực trực một giờ vào thời điểm không thuận lợi, cộng với bất kỳ giá trị hợp đồng bị trì hoãn nào, cộng với việc xem xét sau một sự cố mà không ai có thể gán cho một thay đổi. Sự khác biệt kỹ thuật giữa hai trường hợp chỉ là bốn gói trong Dockerfile.
Chi phí thực tế của việc cung cấp phông chữ
Bốn gói Debian trong giai đoạn runtime:
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/*
Kích thước ảnh thường là lý do phản đối, và chúng ta cần cụ thể: gói CJK là gói lớn, ba gói còn lại là nhỏ, và không gói nào là tùy chọn nếu tài liệu của bạn có thể chứa tên không phải Latin. Hãy cài đặt những gì bộ tài liệu của bạn thực sự cần và xác minh bằng cách đọc lại thay vì cắt giảm theo cảm tính.
fontconfig là bộ giải quyết cộng với fc-list để gỡ lỗi. fonts-dejavu-core sao chép những gì JRE đã gói sẵn, điều này có chủ đích: nó giữ cho ảnh trung thực nếu ảnh nền thay đổi. fonts-liberation quan trọng vì các tài liệu được tạo trên Windows tham chiếu Arial và Times New Roman theo tên và mong đợi việc hiển thị tương thích về mặt metric. fonts-noto-cjk là gói cần thiết cho sự cố đã nêu ở trên.
Xác định một họ thay vì đặt tên một họ
Việc cung cấp một mình không đủ, vì mã vẫn phải đặt tên một họ tồn tại. Cách di động là hỏi thư viện: thử một chữ ký tạm thời cho mỗi ứng cử viên, giữ lại cái đầu tiên không ném ngoại lệ.
for (String candidate : candidates) {
if (tryFamily(sourcePath, candidate) == null) {
return candidate;
}
}
return null;
Quy trình kiểm tra tự thân là một lời gọi ký thông thường vào thư mục tạm, với lỗi được chuyển thành giá trị thay vì ngoại lệ:
SignatureFont font = new SignatureFont();
font.setFamilyName(familyName);
font.setSize(10);
options.setFont(font);
signature.sign(scratch.getAbsolutePath(), options);
return null;
Phát hiện tên tệp là cách tắt gọn trông có vẻ tương đương nhưng không phải. Gói fonts-noto-cjk của Debian cài đặt NotoSansCJK-Regular.ttc, có họ là Noto Sans CJK JP, vì vậy việc khớp tên tệp vừa bỏ lỡ phông chữ vừa báo cáo các họ sẽ không được giải quyết.
Giảm cấp một cách trung thực
Với việc giải quyết đã có, hai lớp lỗi tách biệt rõ ràng. Không có họ Latin nghĩa là ảnh không thể ký bất kỳ gì, nên nên dừng container. Không có họ CJK nghĩa là một chữ ký sẽ bị bỏ qua và quá trình tiếp tục với cảnh báo:
List<SignOptions> options = new ArrayList<>();
options.add(buildTextOptions(LATIN_TEXT, latinFamily, 50));
if (cjkFamily != null) {
options.add(buildTextOptions(CJK_TEXT, cjkFamily, 120));
}
SignResult result = signature.sign(outputPath, options);
Sự khác biệt này quan trọng về mặt vận hành. Một container thoát khi khởi động với thông báo “no usable font family” là một vấn đề triển khai, được bắt bởi người triển khai. Một chữ ký bị thiếu im lặng trong tài liệu đã giao là một vấn đề tuân thủ, được người nhận phát hiện. Gắn trường hợp nghiêm trọng vào mã thoát khác 0 giữ cho các lỗi thuộc loại đầu tiên.
Sau đó đọc lại kết quả, vì một chữ ký CJK được viết mà không có hỗ trợ CJK có thể hiển thị dưới dạng các hộp trống mà không gây ra bất kỳ lỗi nào:
TextSearchOptions options = new TextSearchOptions();
options.setAllPages(true);
List<TextSignature> found = signature.search(TextSignature.class, options);
Điều này để lại gì cho một đội đã triển khai?
Thêm lớp phông chữ, thêm việc giải quyết khi khởi động, và ghi lại cả hai kết quả trên dòng đầu tiên của dịch vụ, nơi kỹ sư tiếp theo sẽ thực sự nhìn thấy chúng. Thay đổi chỉ là một chỉnh sửa Dockerfile cộng với khoảng ba mươi dòng, và nó biến một sự cố do khách hàng kích hoạt thành một container hoặc khởi động với phạm vi bao phủ đã biết hoặc từ chối khởi động. Các tài liệu đã ký hiện có không bị ảnh hưởng; chỉ các tài liệu mới sẽ có đường dẫn CJK.
Kiểm tra một ảnh bạn đã chạy
Trước khi thay đổi bất cứ điều gì, nên biết ảnh hiện tại của bạn có gì. Hai lệnh dưới đây trả lời câu hỏi này từ bên ngoài:
docker run --rm your-image sh -c "ls -R /usr/share/fonts | head"
docker run --rm your-image sh -c "fc-list : family | sort -u | head -20"
Lệnh đầu liệt kê các tệp phông, lệnh thứ hai liệt kê các họ mà bộ giải quyết sẽ trả về, và khoảng cách giữa chúng là lý do tại sao việc khớp tên tệp thất bại. Nếu fc-list thiếu, đó là câu trả lời riêng: fontconfig chưa được cài đặt, và bất kỳ tra cứu họ nào cũng đang chạy mù.
Trong dịch vụ, kiểm tra tương đương nên nằm trong nhật ký khởi động bên cạnh các họ đã được giải quyết. Một dòng như fonts on disk: 8, latin: DejaVu Sans, cjk: (none) cho người tiếp theo biết chính xác container này có thể và không thể ký gì, hữu ích hơn bất kỳ ngoại lệ nào họ có thể đọc vào lúc ba giờ sáng.
Chi tiết JVM mà không ai mong đợi
Một điều nữa gây rắc rối đặc biệt trên Java, và không liên quan đến phông chữ. Artifact Maven của GroupDocs là một jar “fat” có chữ ký. Việc đóng gói lại thành jar “shaded” gây ra NoClassDefFoundError: com/groupdocs/signature/options/search/SearchOptions, và cách khắc phục thường là xóa META-INF/*.SF|RSA|DSA nhưng không đủ: MANIFEST.MF chứa khoảng 19 MB các digest cho từng mục và cũng phải được cắt ngắn lại chỉ còn phần chính. Mẫu này tránh vấn đề bằng cách chạy trên một classpath thuần với thư mục dependency/ thay vì shading bất cứ thứ gì.
Tôi đề cập đến nó vì cả hai vấn đề – việc bao phủ phông chữ một phần và jar có chữ ký – đều có một hình dạng chung: đường dẫn JVM thất bại theo cách trông giống như mã của bạn nhưng thực tế không phải. Cả hai cũng dễ dàng phòng ngừa một khi đã được đặt tên: cố định bố cục classpath bạn biết hoạt động, và khẳng định phạm vi bao phủ phông chữ khi khởi động thay vì tin tưởng vào ảnh nền. Không có gì tốn chi phí thiết kế lại, và cả hai loại bỏ một lớp sự cố mà nếu không sẽ không thể phân biệt được với lỗi ứng dụng.
Kết luận
Một dịch vụ ký Java trong container chỉ cần một lớp Dockerfile và một kiểm tra khởi động để trở nên dự đoán được. Cài đặt fontconfig, DejaVu, Liberation và Noto CJK; giải quyết họ bằng cách dò thay vì giả định; bỏ qua những gì không thể nhúng; xác minh bằng cách đọc lại. Kho lưu trữ mẫu cung cấp cả hai ảnh, vì vậy sự khác biệt giữa có và không có phạm vi bao phủ chỉ mất hai lần xây dựng để thấy thay vì một sự cố để học.