R2 封存¶
R2Archival 子系統透過 Cloudflare R2 相容的 S3 API 將 PDF 封存至物件儲存,使用 AWS Signature Version 4(SigV4)認證,並提供預簽名 URL 生成能力,讓使用者在不暴露憑證的情況下取得時效性下載連結。
PHP Compatibility
This example uses PHP 8.5 syntax. If your environment runs PHP 8.1 or 7.4, use NextPDF Backport for a backward-compatible build.
R2 設定¶
<?php
declare(strict_types=1);
use NextPDF\Cloudflare\R2\R2Client;
use NextPDF\Cloudflare\R2\Config\R2Config;
$r2 = R2Client::create(
config: R2Config::create(
accountId: $_ENV['CF_ACCOUNT_ID'],
accessKeyId: $_ENV['R2_ACCESS_KEY_ID'],
secretAccessKey: $_ENV['R2_SECRET_ACCESS_KEY'],
bucketName: 'nextpdf-archive',
region: 'auto', // R2 使用 'auto' region
endpoint: "https://{$_ENV['CF_ACCOUNT_ID']}.r2.cloudflarestorage.com",
),
);
安全須知:
accessKeyId與secretAccessKey應從環境變數或 Cloudflare Workers Secrets 讀取,絕不可硬編碼至原始碼。
封存 PDF¶
use NextPDF\Cloudflare\R2\R2Client;
use NextPDF\Cloudflare\R2\StorageKey;
// 生成 PDF
$pdfBytes = $renderer->render(function (\NextPDF\Core\Document $doc): void {
$doc->addPage()->text('Archived Document', x: 20, y: 30);
});
// 封存至 R2
$key = StorageKey::generate(
prefix: 'invoices/',
identifier: "invoice-{$invoiceId}",
extension: 'pdf',
timestamp: true, // 加入時間戳避免覆蓋:invoices/2026/03/04/invoice-42.pdf
);
$result = $r2->put(
key: $key->toString(),
body: $pdfBytes,
metadata: [
'invoice-id' => (string) $invoiceId,
'generated-at' => date('c'),
'content-type' => 'application/pdf',
],
);
AWS Signature V4 認證¶
R2Client 內部使用 AWS SigV4 演算法簽署每個 API 請求,確保請求的真實性與防重放攻擊:
Request = HTTP Method + URI + Headers + Body
Canonical String = 標準化請求格式
String to Sign = "AWS4-HMAC-SHA256" + Date + Scope + Hash(Canonical String)
Signature = HMAC-SHA256(SigningKey, StringToSign)
Authorization = "AWS4-HMAC-SHA256 Credential=... Signature=..."
R2Client 自動處理以下 SigV4 步驟:
- 建立 Canonical Request(含
x-amz-content-sha256標頭) - 建立 String to Sign(使用
us-east-1scope,R2 相容 S3 v4) - 衍生簽名金鑰(HMAC 鏈:Date → Region → Service →
aws4_request) - 計算最終簽名並注入
Authorization標頭
預簽名 URL¶
預簽名 URL 允許持有 URL 的任何使用者在指定時間內下載 PDF,無需暴露 R2 憑證:
use NextPDF\Cloudflare\R2\PresignedUrlGenerator;
$generator = PresignedUrlGenerator::create(r2Client: $r2);
$presignedUrl = $generator->getObject(
key: 'invoices/2026/03/04/invoice-42.pdf',
expiresInSeconds: 3600, // URL 有效期 1 小時
responseContentDisposition: 'attachment; filename="invoice-42.pdf"',
responseContentType: 'application/pdf',
);
// 回傳給使用者(格式:https://...r2.cloudflarestorage.com/...?X-Amz-Signature=...)
echo $presignedUrl->toString();
echo $presignedUrl->expiresAt()->toIso8601String();
列出物件¶
// 列出特定前綴下的所有物件
$objects = $r2->listObjects(
prefix: 'invoices/2026/03/',
maxKeys: 1000,
);
foreach ($objects as $object) {
echo $object->key(); // non-empty-string
echo $object->size(); // positive-int (bytes)
echo $object->lastModified(); // DateTimeImmutable
echo $object->etag(); // non-empty-string
}
刪除與生命週期¶
// 刪除單一物件
$r2->delete(key: 'invoices/2026/03/04/invoice-42.pdf');
// 批次刪除(最多 1000 個)
$r2->deleteMultiple(keys: [
'invoices/2026/01/report.pdf',
'invoices/2026/02/report.pdf',
]);
透過 Cloudflare Dashboard 或 API 設定 R2 生命週期規則,自動刪除超過保留期限的物件(推薦取代手動刪除)。
例外處理¶
| 例外類別 | 觸發條件 |
|---|---|
R2AuthException | SigV4 簽名驗證失敗、憑證錯誤 |
R2BucketNotFoundException | Bucket 不存在或無存取權限 |
R2KeyNotFoundException | 嘗試存取不存在的物件 |
R2NetworkException | HTTP 請求失敗(PSR-18 層級) |
R2ThrottleException | 請求速率超過 R2 API 上限 |
參見¶
- Cloudflare 套件總覽 — 套件架構概覽
- Edge 渲染 — EdgeRenderer 生成 PDF bytes
- API 保護 — 保護 PDF 生成端點