Bỏ qua để đến nội dung

Chuyển đổi tài liệu Office sang PDF bằng Gotenberg

Cầu nối Gotenberg dùng để chuyển đổi tài liệu Office sang PDF. Nó gửi tài liệu đến một microservice Gotenberg qua HTTPS và trả về các byte PDF. Bạn mô tả dịch vụ bằng một GotenbergConfig bất biến, kết nối một client PSR-18 và các factory PSR-17 vào GotenbergBridge, kiểm tra tình trạng dịch vụ, rồi chuyển đổi một tệp từ đĩa hoặc các byte trong bộ nhớ. Hướng dẫn này bao gồm việc phát hiện định dạng theo phần mở rộng, kiểm tra tình trạng, hợp đồng lỗi có kiểu và bước chuyển giao sang xử lý hậu kỳ của NextPDF.

Các điều kiện tiên quyết cần nêu rõ ngay từ đầu:

  • Đã cài đặt NextPDF core và nextpdf/gotenberg.
  • Một dịch vụ Gotenberg có thể truy cập qua HTTPS. Cầu nối từ chối một URL http:// thuần trước khi tiến trình gửi bất kỳ yêu cầu nào ra ngoài.
  • Đã cài đặt một client PSR-18 cùng các factory request và stream PSR-17. Để ghim DNS và TLS, bạn cũng cung cấp một factory response PSR-17.
  • Đầu vào phải là một trong sáu định dạng Office được nhận diện: .docx, .xlsx, .pptx, .odt, .ods hoặc .odp. Cầu nối từ chối mọi phần mở rộng khác bằng một ValueError.

Đây là hướng dẫn thực hành. Để có một chương trình hoàn chỉnh, chạy được, hãy đọc phần khởi đầu nhanh Gotenberg.

Cài đặt cầu nối, client PSR-18 và các factory PSR-17.

Terminal window
composer require nextpdf/gotenberg guzzlehttp/guzzle

Chạy một dịch vụ Gotenberg có thể truy cập qua HTTPS. Lấy mọi bearer token từ trình quản lý bí mật hoặc từ giá trị môi trường được tiêm vào. Cầu nối không bao giờ đọc biến môi trường và không bao giờ tạo HTTP client; bạn cung cấp cả hai.

GotenbergBridge::convertFile() nhận một đường dẫn trên đĩa. Nó chuẩn hóa đường dẫn để ngăn duyệt thư mục, ánh xạ phần mở rộng tệp sang một định dạng được hỗ trợ, kiểm tra kích thước và tên tệp, rồi gửi một yêu cầu multipart đến <apiUrl>/forms/libreoffice/convert. convertString() đi theo cùng đường dẫn với các byte bạn đã có sẵn; nó dùng tên tệp gốc để có thể phát hiện phần mở rộng.

Việc phát hiện định dạng dựa trên phần mở rộng. Cầu nối ánh xạ .docx, .xlsx, .pptx, .odt, .ods.odp sang các định dạng tương ứng và từ chối mọi thứ khác bằng một ValueError trước khi phát sinh bất kỳ lưu lượng mạng nào. Đối tượng kết quả cung cấp định dạng nguồn đã phát hiện dưới dạng một giá trị enum.

Cầu nối thực hiện một lượt HTTP đồng bộ, được bao quanh bởi các bước kiểm tra hợp lệ. Nó không thử lại, xếp hàng, lưu vào bộ nhớ đệm hay giới hạn tốc độ; những cơ chế đó thuộc về ứng dụng bao quanh cầu nối. Hãy coi mỗi lần chuyển đổi là một lệnh gọi từ xa đến một dịch vụ mà bạn vận hành nhưng không kiểm soát bên trong tiến trình, và thiết kế để chịu được độ trễ cùng các chế độ lỗi của dịch vụ đó.

Cầu nối báo cáo lỗi dưới dạng các exception có kiểu và không bao giờ trả về kết quả một phần hoặc chưa được kiểm tra hợp lệ:

  • Trạng thái khác 200, Content-Type không có application/pdf, hoặc body không bắt đầu bằng %PDF sẽ ném ra GotenbergConvertException. Cầu nối chỉ trả về kết quả khi cả ba lần kiểm tra đều đạt.
  • Lỗi của client PSR-18, bao gồm lỗi mạng hoặc hết thời gian chờ, được bao bọc thành GotenbergConvertException với exception gốc làm nguyên nhân.
  • Các lỗi kiểm tra hợp lệ (URL không phải HTTPS, địa chỉ riêng tư hoặc dành riêng, đầu vào quá lớn, tên tệp không an toàn) ném ra RuntimeException trước khi phát sinh bất kỳ lưu lượng mạng nào.
  • Phần mở rộng tệp không được nhận diện ném ra ValueError trước khi phát sinh bất kỳ lưu lượng mạng nào.
// Configuration (final readonly):
new GotenbergConfig(
string $apiUrl, // required, must be HTTPS
int $timeout = 30, // hard transfer timeout, seconds
int $maxFileSize = 52_428_800, // 50 MiB
string $apiKey = '', // #[SensitiveParameter]; Bearer when non-empty
list<string> $pinnedPublicKeys = [], // sha256/<base64>
list<string> $backupPublicKeys = [],
)
GotenbergConfig::fromArray(array $config): self
GotenbergConfig::isValid(): bool
// The bridge:
new GotenbergBridge(
GotenbergConfig $config,
ClientInterface $httpClient, // PSR-18
RequestFactoryInterface $requestFactory, // PSR-17
StreamFactoryInterface $streamFactory, // PSR-17
?LoggerInterface $logger = null, // PSR-3
?HtmlSecurityPolicyInterface $htmlSecurityPolicy = null,
?ResponseFactoryInterface $responseFactory = null, // enables pinned transport
)
GotenbergBridge::isAvailable(): bool
GotenbergBridge::convertFile(string $path): GotenbergConvertResult
GotenbergBridge::convertString(string $bytes, string $originalFilename): GotenbergConvertResult

Đối tượng kết quả cung cấp pdfData, enum sourceFormat, isValid() (true khi body không rỗng và bắt đầu bằng %PDF) và size(). Để xem tham chiếu đầy đủ các trường, bản đồ khóa của fromArray() và các quy tắc chọn transport, hãy xem trang cấu hình Gotenberg được liên kết trong mục Xem thêm.

Mô tả dịch vụ, kết nối cầu nối, thăm dò dịch vụ rồi chuyển đổi một tệp.

convert-quickstart.php
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use NextPDF\Gotenberg\GotenbergBridge;
use NextPDF\Gotenberg\GotenbergConfig;
use NextPDF\Gotenberg\GotenbergConvertException;
$config = new GotenbergConfig(
apiUrl: 'https://gotenberg.example.com',
timeout: 60,
apiKey: getenv('GOTENBERG_TOKEN') ?: '',
);
$bridge = new GotenbergBridge(
config: $config,
httpClient: $httpClient, // your PSR-18 client
requestFactory: $requestFactory, // your PSR-17 factory
streamFactory: $streamFactory, // your PSR-17 factory
responseFactory: $responseFactory, // enables the pinned transport
);
// Probe before converting. The probe validates the URL with no network
// traffic, then sends a HEAD to <apiUrl>/health.
if (!$bridge->isAvailable()) {
throw new RuntimeException('Gotenberg is not reachable.');
}
try {
$result = $bridge->convertFile('/path/to/report.docx');
} catch (GotenbergConvertException $exception) {
// Bad config, HTTP failure, non-200, wrong Content-Type, or non-PDF body.
throw $exception;
}
if (!$result->isValid()) {
throw new RuntimeException('Result is not a valid PDF.');
}
file_put_contents('/path/to/report.pdf', $result->pdfData);

Lớp này là NextPDF\Gotenberg\GotenbergConfig (dòng trên dùng đúng namespace mà mã của bạn phải import). isAvailable() trả về false và không bao giờ ném lỗi với một URL rỗng, không phải HTTPS, có địa chỉ riêng tư, hoặc với bất kỳ lỗi mạng nào; trạng thái dưới 500 từ /health nghĩa là dịch vụ sẵn sàng.

Một quy trình chuyển đổi cấp sản xuất bắt riêng từng loại lỗi, chỉ thử lại khi điều kiện phù hợp và giới hạn tính đồng thời ở phía gọi. Thứ tự catch bên dưới là đầy đủ.

OfficeConverter.php
<?php
declare(strict_types=1);
use NextPDF\Gotenberg\GotenbergBridge;
use NextPDF\Gotenberg\GotenbergConvertException;
use Psr\Log\LoggerInterface;
use RuntimeException;
use ValueError;
final readonly class OfficeConverter
{
public function __construct(
private GotenbergBridge $bridge,
private LoggerInterface $logger,
) {}
public function convert(string $path): string
{
try {
$result = $this->bridge->convertFile($path);
} catch (GotenbergConvertException $exception) {
// Transport, non-200, wrong Content-Type, or non-PDF body.
// Retry only on transport-level or 502/503/504 causes, with
// bounded exponential backoff and jitter — never blind retries.
$this->logger->error('gotenberg.convert.failed', [
'path' => basename($path),
'exception' => $exception::class,
]);
throw $exception;
} catch (ValueError $exception) {
// Extension is not one of the six recognized Office formats.
$this->logger->warning('gotenberg.convert.unsupported_format', [
'path' => basename($path),
]);
throw $exception;
} catch (RuntimeException $exception) {
// Non-HTTPS URL, private address, oversized input, or unsafe name.
$this->logger->error('gotenberg.convert.rejected', [
'path' => basename($path),
'exception' => $exception::class,
]);
throw $exception;
}
if (!$result->isValid()) {
throw new RuntimeException('Gotenberg returned an invalid PDF body.');
}
return $result->pdfData;
}
}

Chỉ thử lại đối với một GotenbergConvertException ở cấp transport (một exception client PSR-18 được bao bọc) và đối với các lỗi máy chủ lũy đẳng (502, 503, 504). Một phản hồi thuộc nhóm 400 thường có nghĩa là đầu vào sai, nên việc thử lại sẽ thất bại theo cùng một cách. Giới hạn tổng số lần thử và tổng thời gian thực tế. Giới hạn số lần chuyển đổi đang diễn ra theo khả năng mà cụm triển khai Gotenberg của bạn có thể duy trì. Bản thân cầu nối là phi trạng thái và an toàn để dùng từ nhiều worker, nhưng dịch vụ có khả năng chuyển đổi hữu hạn.

  • Việc phát hiện định dạng dựa trên phần mở rộng. Một .docx đổi tên thành .txt bị từ chối bằng ValueError; một .txt đổi tên thành .docx được gửi đến Gotenberg và thất bại ở đó. Khi tiếp nhận tệp tải lên, hãy xác minh định dạng thực, đừng tin vào tên.
  • fromArray() được thiết kế để khoan dung. Với đầu vào không hợp lệ, nó âm thầm thay thế bằng các giá trị mặc định. Hãy kiểm tra hợp lệ mảng nguồn trong luồng khởi động của bạn để một URL bị thiếu hiện ra sớm dưới dạng lỗi cấu hình, chứ không phải dưới dạng một exception ở mỗi lần chuyển đổi.
  • Giới hạn kích thước được thực thi trong tiến trình. maxFileSize (mặc định 50 MiB) được kiểm tra trước khi yêu cầu được gửi, nên một tệp quá lớn không bao giờ tiêu tốn khả năng của dịch vụ. Hãy hạ giới hạn để khớp với nhu cầu tài liệu của bạn; một giới hạn nhỏ hơn là một biện pháp kiểm soát từ chối dịch vụ rẻ hơn.
  • Việc thăm dò không miễn phí. Hãy gọi isAvailable() từ một endpoint kiểm tra sẵn sàng hoặc tình trạng, chứ không phải trước mỗi lần chuyển đổi. Chạy nó ở mỗi lần chuyển đổi sẽ nhân đôi tần suất yêu cầu của bạn đến dịch vụ mà không mang lại lợi ích nào.
  • Không có bộ nhớ đệm trong tiến trình. Nếu cùng một tài liệu được chuyển đổi lặp đi lặp lại, hãy lưu đệm PDF kết quả trong ứng dụng của bạn, dùng khóa là một hash nội dung của đầu vào.
  • renderTimeMs do bạn thiết lập. Trường thời gian của kết quả là 0.0 trừ khi tích hợp của bạn đo và thiết lập nó. Hãy tự đo thời lượng lệnh gọi nếu bạn cần con số đó.

Trong suốt thời lượng của yêu cầu, một lần chuyển đổi giữ một kết nối và một worker LibreOffice ở phía Gotenberg, và việc chuyển đổi Office mất thời gian. Hãy đặt timeout dựa trên độ trễ chuyển đổi đo được cho các tài liệu thực của bạn, có khoảng đệm. Hãy giữ nó thấp hơn mọi gateway thượng nguồn hoặc max_execution_time của PHP, để cầu nối hết thời gian chờ trước và bạn nhận được một exception có kiểu thay vì một tiến trình bị buộc dừng. Hãy giới hạn tính đồng thời bằng một hàng đợi, semaphore hoặc một worker pool được định kích thước theo khả năng của dịch vụ. Không có bộ nhớ đệm trong tiến trình; hãy thêm một bộ trong ứng dụng của bạn nếu bạn chuyển đổi cùng một đầu vào lặp đi lặp lại.

  • Sàng lọc HTTPS và địa chỉ trước khi gửi. Cầu nối từ chối một URL không phải HTTPS và một đích phân giải vào không gian địa chỉ riêng tư hoặc dành riêng trước khi tiến trình gửi bất kỳ yêu cầu nào ra ngoài. Mỗi lệnh gọi được thử lại sẽ chạy lại bước kiểm tra hợp lệ đó, nên một lần thử lại không thể vượt qua biện pháp chống SSRF.
  • Transport được ghim theo yêu cầu. Khi bạn cung cấp một factory response và các pin (hoặc có một tập IP đã phân giải), cầu nối ràng buộc kết nối vào các địa chỉ đã phân giải, thực thi việc ghim SPKI, xác minh peer và host, áp dụng thời gian chờ và vô hiệu hóa theo chuyển hướng. Hãy cấu hình một pin dự phòng trước khi xoay vòng chứng chỉ.
  • Đừng tin vào loại nội dung được khai báo của một tệp tải lên. Khi tiếp nhận tệp người dùng tải lên, hãy tự kiểm tra hợp lệ loại tệp thực; bản đồ phần-mở-rộng-sang-định-dạng là một quyết định định tuyến, không phải bước kiểm tra tính xác thực.
  • Các bí mật được che giấu và bất biến. apiKey mang #[SensitiveParameter], và config là final readonly. Hãy lấy token từ trình quản lý bí mật; không bao giờ commit nó. Bản ghi nhật ký chuyển đổi mang theo URL, tên tệp, định dạng và độ dài nội dung — không bao giờ là nội dung tệp hay token.
  • Đừng bao giờ viết một khối catch rỗng. Mỗi ví dụ bắt đúng kiểu cụ thể và ghi nhật ký kèm ngữ cảnh.

Để xem mô hình bảo mật và triển khai đầy đủ, hãy xem trang bảo mật và vận hành Gotenberg. Hợp đồng transport PSR-18 và hướng dẫn không tin vào content-type được neo theo các điều khoản tương ứng trên trang sử dụng cấp sản xuất thượng nguồn.

Hướng dẫn này không tự đưa ra tuyên bố tuân thủ tiêu chuẩn quy phạm nào. Hành vi transport PSR-18 của cầu nối (một client chỉ ném lỗi khi không thể gửi hoặc phân tích một phản hồi; một 4xx/5xx là một giá trị trả về bình thường), hướng dẫn kiểm tra hợp lệ tệp tải lên và mô hình ghim TLS được neo theo PSR-18, OWASP và RFC 7469 trên các trang sử dụng cấp sản xuất và cấu hình Gotenberg thượng nguồn. Trang cookbook này trình bày lại cách sử dụng và chuyển những trích dẫn đó sang các trang ấy. Cầu nối tạo ra các byte PDF rồi dừng lại. Việc ký, các hồ sơ PDF/A và đóng hình mờ là các mối quan tâm xử lý hậu kỳ của NextPDF và là một khả năng của phiên bản thương mại, không phải một phần của cầu nối này.