ส่งมอบ PDF ที่สร้างขึ้นผ่าน URL ที่ลงนามและหมดอายุ
ภาพรวมโดยสังเขป
หัวข้อที่มีชื่อว่า “ภาพรวมโดยสังเขป”คุณสร้างไฟล์ Portable Document Format (PDF) และต้องส่งมอบให้ไคลเอนต์ เส้นทางที่ง่ายที่สุดคือสตรีมไบต์ตรงผ่านคอนโทรลเลอร์ แต่นั่นผูก worker ของแอปพลิเคชันไว้ตลอดการดาวน์โหลด รันทราฟฟิกผ่านเซิร์ฟเวอร์ของคุณ และเปิดเผยไฟล์ให้ทุกคนที่เข้าถึงเส้นทางนั้นได้ รูปแบบการส่งมอบในหน้านี้ทำตรงกันข้าม: สร้าง PDF, เก็บไบต์ไว้ใน object storage แล้วคืน Uniform Resource Locator (URL) ที่ลงนามและอายุสั้นซึ่งไคลเอนต์ดึงตรงจาก storage แอปของคุณส่งคืน payload JavaScript Object Notation (JSON) เล็ก ๆ พร้อม URL ส่วน storage ให้บริการไบต์
ฝั่ง NextPDF คือการเรียกหนึ่งครั้ง: getPdfData() บนเอกสารคืนไบนารี PDF ดิบเป็นสตริง ทุกอย่างหลังจากนั้น — การวางอ็อบเจกต์และการสร้างลิงก์ที่ลงนามและจำกัดเวลา — เป็นงานของเฟรมเวิร์กหรือผู้ให้บริการคลาวด์ของคุณ primitive สำหรับการลงนามเป็น API จริงที่มีเอกสารกำกับ: Laravel Storage::temporaryUrl() และ URL::temporarySignedRoute(), Symfony UriSigner และการดำเนินการ presigned-URL ของ Amazon Simple Storage Service (S3) หรือ Google Cloud Storage (GCS) ใน software development kit (SDK) ของพวกเขา NextPDF ไม่ได้กำหนดตัวช่วย URL ของตัวเอง อย่ามองหามัน
ตรวจสอบชิ้นส่วนเหล่านี้ก่อน:
- ติดตั้ง core ของ NextPDF แล้ว และคุณสร้างเอกสารได้
- คุณมี object storage ที่เฟรมเวิร์กลงนามให้ได้: bucket S3 หรือที่เข้ากันได้กับ S3, bucket GCS หรือ Laravel disk ที่ไดรเวอร์รองรับ temporary URL
- credential อยู่ในตัวแปรสภาพแวดล้อมหรือ secret manager ไม่เคยอยู่ในไฟล์กำหนดค่าที่ commit
นี่คือคู่มือวิธีทำ โดยถือว่าคุณรู้วิธีกำหนดเส้นทางคำขอไปยังคอนโทรลเลอร์อยู่แล้ว สำหรับการคืนไบต์โดยตรงแทน โปรดอ่านส่งคืน PDF ที่สร้างจากคอนโทรลเลอร์
ภาพรวมเชิงแนวคิด
หัวข้อที่มีชื่อว่า “ภาพรวมเชิงแนวคิด”รูปแบบนี้มีสามขั้นตอน และมีเพียงขั้นแรกที่แตะ NextPDF:
- สร้าง สร้างเอกสารแล้วเรียก
getPdfData()เพื่อรับไบต์ - เก็บ เขียนไบต์เหล่านั้นไปยังคีย์ของ object-storage (
reports/2026/r-42.pdf) - ลงนาม ขอ URL ที่ลงนามไปยังคีย์นั้นจากเฟรมเวิร์กหรือ cloud SDK พร้อมเวลาหมดอายุ แล้วคืน URL ให้ไคลเอนต์
เหตุใดจึงเก็บและลงนามแทนการ proxy ไบต์:
- ลดภาระแบนด์วิดท์ object storage (หรือ edge ของ content-delivery-network) ให้บริการการดาวน์โหลด worker ของแอปพลิเคชันคืน JSON ไม่กี่ร้อยไบต์และว่างทันที แทนที่จะถูกจองตลอดความยาวของการถ่ายโอนหลายเมกะไบต์
- จำกัดขอบเขตการเข้าถึง URL ที่ลงนามให้สิทธิ์เข้าถึง อ็อบเจกต์เดียว ในช่วง เวลาที่กำหนด ตัว bucket เองยังคงเป็นส่วนตัว ไม่มีเส้นทางสาธารณะให้ brute force และไม่มีการให้สิทธิ์อ่าน bucket แบบกว้าง
- เวลาหมดอายุ ลายเซ็นฝัง timestamp หมดอายุไว้ หลังจากผ่านไปแล้ว ลิงก์จะตาย URL ที่รั่วไหลจะหยุดทำงานเอง ซึ่งจำกัดรัศมีความเสียหายของการแชร์โดยไม่ตั้งใจ
มีโมเดลการลงนามสองแบบที่แตกต่างกัน และต่างกันที่ สิ่งใด ที่ถูกลงนาม:
- presigned URL ของ object-storage (S3, GCS หรือ
temporaryUrl()ของ Laravel บน disk S3/GCS) ชี้ไปยัง อ็อบเจกต์ของ storage โดยตรง การดาวน์โหลดไม่ไปถึงแอปของคุณเลย - เส้นทางที่ลงนามของแอปพลิเคชัน (Laravel
URL::temporarySignedRoute(), SymfonyUriSigner) ชี้ไปยัง เส้นทางของคุณเอง คำขอยังคงเข้าถึงแอปของคุณ ซึ่งตรวจสอบลายเซ็น แล้วสตรีมหรือ redirect ไปยังอ็อบเจกต์ ใช้สิ่งเหล่านี้เมื่อคุณต้องรันการอนุญาต การบันทึก หรือการนับในแต่ละการดาวน์โหลด หรือเมื่อ storage ของคุณ presign ไม่ได้
พื้นผิว API
หัวข้อที่มีชื่อว่า “พื้นผิว API”| Concern | NextPDF | Laravel | Symfony |
|---|---|---|---|
| รับไบต์ PDF | NextPDF\Core\Document::getPdfData(): string | same | same |
| เก็บไบต์ | — | Storage::disk($d)->put($key, $bytes) | Filesystem::dumpFile($path, $bytes) or Flysystem write() |
| presigned storage URL | — | Storage::disk($d)->temporaryUrl($key, $expiresAt) | AWS/GCS SDK presigner (below) |
| เส้นทางแอปที่ลงนาม | — | URL::temporarySignedRoute($name, $expiresAt, $params) | UriSigner::sign($url) |
| ตรวจสอบเส้นทางแอปที่ลงนาม | — | signed route middleware / $request->hasValidSignature() | UriSigner::check() / checkRequest() |
การเรียกเอนจิน NextPDF เพียงอย่างเดียวที่รูปแบบการส่งมอบนี้ต้องการคือ getPdfData() ส่วนตัวเอกสารเองถูกสร้างด้วยวิธีที่แอปของคุณสร้างเอกสารอยู่แล้ว (เช่น DocumentFactoryInterface ที่ฉีดเข้า / PdfFactory ของ Symfony) getPdfData() ถูกประกาศใน trait HasOutput บน NextPDF\Core\Document มันเรียกตัวเขียนหนึ่งครั้งและคืน PDF ทั้งฉบับเป็นสตริง พี่น้องของมัน save(string $path): void เขียนไบต์เดียวกันลงดิสก์ผ่านตัวเขียนแบบ atomic ให้ใช้มันก็ต่อเมื่อ storage ของคุณเป็น path ไฟล์ระบบในเครื่องจริง สำหรับ object storage ให้เลือกใช้ getPdfData() แล้วให้ storage SDK เป็นเจ้าของการถ่ายโอน
เอกสารถูกสร้างเมื่อคุณเรียก
getPdfData()(หรือsave()) และ build ไม่เป็น idempotent เรียกมัน ครั้งเดียว ต่อเอกสาร เก็บสตริงไว้ แล้วนำสตริงนั้นกลับมาใช้ซ้ำทั้งสำหรับการอัปโหลดและขนาดหรือ checksum ใดที่คุณคำนวณ
ตัวอย่างโค้ด — temporary URL ของ Laravel
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — temporary URL ของ Laravel”นามธรรมไฟล์ระบบของ Laravel ลงนามให้คุณ บน disk S3 (หรือที่เข้ากันได้กับ S3) Storage::temporaryUrl() คืน presigned URL ตรงไปยังอ็อบเจกต์ ไคลเอนต์ดาวน์โหลดจาก storage ส่วนแอ็กชันของคุณคืนเฉพาะ JSON
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use Illuminate\Http\JsonResponse;use Illuminate\Support\Facades\Storage;use NextPDF\Contracts\DocumentFactoryInterface;use Psr\Log\LoggerInterface;use Throwable;
final class ReportDeliveryController extends Controller{ public function __construct( private readonly DocumentFactoryInterface $documents, private readonly LoggerInterface $logger, ) {}
public function store(int $reportId): JsonResponse { try { // 1. Generate. Build once; getPdfData() returns the raw bytes. $document = $this->documents->create(); $document->addPage(); $document->cell(0, 10, "Report #{$reportId}", newLine: true); $bytes = $document->getPdfData();
// 2. Store under a non-guessable key on a private disk. $key = sprintf('reports/%d/%s.pdf', $reportId, bin2hex(random_bytes(16))); Storage::disk('s3')->put($key, $bytes, ['visibility' => 'private']);
// 3. Sign. A presigned URL straight to the object, valid 10 minutes. $url = Storage::disk('s3')->temporaryUrl($key, now()->addMinutes(10));
return new JsonResponse(['download_url' => $url], 201); } catch (Throwable $exception) { // Log the class, never the message or trace, so detail does not leak. $this->logger->error('Report PDF delivery failed', [ 'report_id' => $reportId, 'exception' => $exception::class, ]);
return new JsonResponse(['error' => 'Could not prepare the report.'], 500); } }}disk ต้องเป็นตัวที่ไดรเวอร์รองรับ temporary URL — ไดรเวอร์ s3 ที่มากับชุดรองรับ การเรียก temporaryUrl() บนไดรเวอร์ local จะ throw เว้นแต่คุณลงทะเบียน generator ให้มัน เพราะ disk ในเครื่องไม่มีอะไรให้ presign
เมื่อคุณต้องการให้การดาวน์โหลดอยู่บนเส้นทางของคุณเอง — เพื่อรันการอนุญาตต่อคำขอหรือบันทึกการเข้าถึงแต่ละครั้ง — ให้ลงนามเส้นทางแทนด้วย URL::temporarySignedRoute() middleware signed ของเส้นทางจะปฏิเสธลิงก์ที่ถูกแก้ไขหรือหมดอายุก่อนแอ็กชันของคุณจะรัน
<?php
declare(strict_types=1);
use Illuminate\Support\Facades\Route;
// Mint the link elsewhere:// URL::temporarySignedRoute('reports.download', now()->addMinutes(10),// ['report' => $reportId]);Route::get('/reports/{report}/download', DownloadReportController::class) ->name('reports.download') ->middleware('signed');ตัวอย่างโค้ด — UriSigner ของ Symfony
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — UriSigner ของ Symfony”Symfony ไม่มี storage facade แบบ Laravel ดังนั้นคุณลงนาม เส้นทางของคุณเอง ด้วย Symfony\Component\HttpFoundation\UriSigner ของเฟรมเวิร์ก แล้วให้เส้นทางนั้น redirect ไปยัง presigned storage URL (หรือสตรีมอ็อบเจกต์) UriSigner::sign() ต่อท้ายแฮชที่ใช้คีย์ ส่วน checkRequest() ปฏิเสธลิงก์ที่ถูกแก้ไข เพื่อให้ตัวอย่างพกพาข้ามเวอร์ชัน Symfony ได้ ให้ฝังพารามิเตอร์ query expires ของคุณเอง (Unix timestamp ที่ห่างออกไปไม่กี่นาที) ก่อน ลงนาม แล้วตรวจสอบพารามิเตอร์นั้นด้วยตัวเองในเส้นทาง download หลังลายเซ็นผ่าน วิธีนี้ทำงานได้บนทุกเวอร์ชัน Symfony เพราะ UriSigner::sign(string $uri) รับเฉพาะ URL
<?php
declare(strict_types=1);
namespace App\Controller;
use NextPDF\Symfony\Service\PdfFactory;use Symfony\Component\HttpFoundation\JsonResponse;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\HttpFoundation\Response;use Symfony\Component\HttpFoundation\UriSigner;use Symfony\Component\Routing\Attribute\Route;use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
final class ReportDeliveryController{ // 1 + 2 + sign: build, store, and return a signed URL to our own route. #[Route('/reports/{reportId}', name: 'report_prepare', methods: ['POST'])] public function prepare( int $reportId, PdfFactory $pdf, UriSigner $signer, UrlGeneratorInterface $urls, ReportStorage $storage, // your storage adapter ): JsonResponse { $document = $pdf->create(); $document->addPage(); $document->cell(0, 10, "Report #{$reportId}", newLine: true);
$key = $storage->put($reportId, $document->getPdfData());
$url = $urls->generate( 'report_download', ['reportId' => $reportId, 'key' => $key], UrlGeneratorInterface::ABSOLUTE_URL, );
// Embed our own expiry (a Unix timestamp 10 minutes out), then sign the // URL only. UriSigner::sign(string $uri) is portable across all versions. $url .= (str_contains($url, '?') ? '&' : '?') . 'expires=' . ((new \DateTimeImmutable('+10 minutes'))->getTimestamp());
return new JsonResponse(['download_url' => $signer->sign($url)]); }
// verify: the signed route. checkRequest() rejects a tampered link; then we // enforce the embedded expiry ourselves. #[Route('/reports/{reportId}/download', name: 'report_download', methods: ['GET'])] public function download( Request $request, UriSigner $signer, ReportStorage $storage, ): Response { if (!$signer->checkRequest($request)) { return new Response('Link invalid.', 403); }
// Enforce the embedded expiry: reject once the timestamp is in the past. $expires = (int) $request->query->get('expires'); if ($expires < time()) { return new Response('Link expired.', 410); }
// Redirect to a presigned storage URL, or stream the object here. return new Response('', 302, ['Location' => $storage->presign( (string) $request->query->get('key'), )]); }}UriSigner ถูกสร้างด้วย secret (Symfony autowire มันจากพารามิเตอร์ %kernel.secret% / APP_SECRET) ตัวอย่างด้านบนคือเส้นทางที่พกพาได้: UriSigner::sign(string $uri) ลงนามเฉพาะ URL และมีอยู่ในทุกเวอร์ชัน Symfony ดังนั้นเวลาหมดอายุจึงเดินทางเป็นพารามิเตอร์ query expires ของคุณเอง ลายเซ็นครอบคลุมพารามิเตอร์นั้น จึงไม่สามารถถูกแก้ไขได้ — และหลังจาก checkRequest() ผ่าน เส้นทาง download บังคับใช้มันโดยเทียบ timestamp กับเวลาปัจจุบันและคืน 410 Gone เมื่อมันอยู่ในอดีต
บนเวอร์ชัน Symfony ที่
UriSigner::sign()รับอาร์กิวเมนต์DateTimeInterfaceสำหรับเวลาหมดอายุ คุณส่งเวลาหมดอายุได้โดยตรง —$signer->sign($url, new \DateTimeImmutable('+10 minutes'))— แล้วให้checkRequest()ปฏิเสธลิงก์ที่หมดอายุให้คุณ โดยตัดพารามิเตอร์expiresที่ทำเองและการตรวจสอบของมันออก ยืนยันลายเซ็นของUriSigner::sign()ใน Symfony ที่ติดตั้งของคุณก่อนพึ่งพามัน รูปแบบที่พกพาได้ด้านบนทำงานได้ไม่ว่าจะกรณีใด
ตัวอย่างโค้ด — presigned URL ของ Cloud SDK
หัวข้อที่มีชื่อว่า “ตัวอย่างโค้ด — presigned URL ของ Cloud SDK”หากคุณลงนามด้วย cloud SDK โดยตรงแทนการผ่าน framework disk รูปร่างจะเหมือนกัน: วางอ็อบเจกต์ แล้วขอให้ SDK presign GET ให้มัน นี่คือ S3 ล้วน (โฟลว์ GCS สะท้อนมัน: รับอ็อบเจกต์ด้วย $bucket->object($key) แล้วเรียก $object->signedUrl($expiresAt, [...]))
<?php
declare(strict_types=1);
use Aws\S3\S3Client;use NextPDF\Core\Document;
/** @var Document $document Already built by your generation code. */$bytes = $document->getPdfData(); // NextPDF: the only engine call.
$s3 = new S3Client(['region' => 'eu-central-1', 'version' => 'latest']);$key = 'reports/' . bin2hex(random_bytes(16)) . '.pdf';
// Store the object privately.$s3->putObject([ 'Bucket' => 'my-private-reports', 'Key' => $key, 'Body' => $bytes, 'ContentType' => 'application/pdf',]);
// Presign a GET valid for 10 minutes. The returned URI is the signed URL.$command = $s3->getCommand('GetObject', [ 'Bucket' => 'my-private-reports', 'Key' => $key,]);$signedUrl = (string) $s3->createPresignedRequest($command, '+10 minutes')->getUri();สำหรับ GCS ให้สร้างไบต์ด้วยวิธีเดียวกันด้วย getPdfData() อัปโหลดอ็อบเจกต์ด้วยไคลเอนต์ Cloud Storage แล้วรับ storage object ด้วย $bucket->object($key) และเรียก $object->signedUrl($expiresAt, [...]) ด้วยเวลาหมดอายุแบบ Carbon/DateTime เพื่อสร้างลิงก์ที่เทียบเท่า เวลาหมดอายุของ signed URL บนผู้ให้บริการทั้งสองถูกจำกัดด้วยชนิด credential ปรึกษาเอกสารของผู้ให้บริการสำหรับอายุสูงสุดที่ credential ของคุณอนุญาต
กรณีขอบและจุดที่ควรระวัง
หัวข้อที่มีชื่อว่า “กรณีขอบและจุดที่ควรระวัง”- สร้างเอกสารเพียงครั้งเดียวพอดี
getPdfData()กระตุ้น build และ build ไม่เป็น idempotent เรียกมันครั้งเดียว เก็บสตริงไว้ แล้วนำกลับมาใช้ซ้ำทั้งสำหรับการอัปโหลดและContent-Length, checksum หรือETagใดที่คุณคำนวณ อย่าเรียกมันอีกเพื่อ “อ่านซ้ำ” ไบต์ temporaryUrl()ต้องการไดรเวอร์ที่ presign ได้ ไดรเวอร์s3ของ Laravel presign ได้ ส่วนไดรเวอร์localจะ throw บนtemporaryUrl()เว้นแต่คุณลงทะเบียน generator ที่กำหนดเองด้วยStorage::disk('local')->buildTemporaryUrlsUsing(...)เลือก disk ที่ลงนามได้ หรือลงนามเส้นทางแอปแทน- ตั้ง content type ของอ็อบเจกต์ เก็บด้วย
Content-Type: application/pdf(ตัวเลือกอัปโหลดContentTypeหรือเมทาดาตาของ disk) เพื่อให้เบราว์เซอร์เปิด presigned link เป็น PDF แทนการดาวน์โหลดเป็นoctet-stream - เวลาหมดอายุที่สั้นอาจตายก่อนไคลเอนต์ที่ช้า หากผู้ใช้คลิกลิงก์หลังจากคุณสร้างมันไปนาน หน้าต่าง 60 วินาทีอาจตายไปแล้ว ให้กำหนดขนาดเวลาหมดอายุให้พอดีกับช่องว่างที่เป็นจริงระหว่างการสร้างและไบต์แรก — เป็นนาที ไม่ใช่วินาที — แล้วสร้างใหม่ตามต้องการ แทนการยืดมันออกไปเป็นชั่วโมง
- URL ที่ลงนามคือการเข้าถึงแบบ bearer ใครก็ตามที่ถือ URL ก่อนมันหมดอายุดาวน์โหลดอ็อบเจกต์ได้ ให้เวลาหมดอายุสั้น เลือกขอบเขตอ็อบเจกต์เดียว และไม่เคย log URL ที่ลงนามฉบับเต็ม — ลายเซ็นเป็น token โดยพฤตินัย
- อย่าฝังอินพุตผู้ใช้ในคีย์ของอ็อบเจกต์โดยไม่ทำความสะอาด สร้างคีย์จากค่าที่คุณควบคุมบวกไบต์สุ่ม (
bin2hex(random_bytes(16))) คีย์ที่คาดเดาได้เชื้อเชิญการ enumerate เมื่อ bucket ถูกเปิดเผยแม้เพียงบางส่วน
ประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ประสิทธิภาพ”รูปแบบนี้แลกการถ่ายโอนแบบ synchronous หนึ่งครั้งกับการอัปโหลดหนึ่งครั้งบวกการตอบสนอง JSON เล็ก ๆ worker ของแอปพลิเคชันถูกจองเฉพาะสำหรับ build PDF และการอัปโหลดไปยัง storage ไม่ใช่สำหรับการดาวน์โหลดทั้งหมดของไคลเอนต์ การดาวน์โหลดเองรันระหว่างไคลเอนต์และ storage (หรือ edge ของมัน) จึงไม่กิน app worker เลย
build ยังคงเป็น synchronous และยังครองงบสำหรับเอกสารขนาดใหญ่หรือหลายหน้า — getPdfData() สร้าง PDF ทั้งฉบับในหน่วยความจำก่อนที่คุณจะอัปโหลดได้ สำหรับเอกสารหนัก ให้ย้ายการสร้างและอัปโหลดไปยัง queued job แล้วส่งมอบ URL ที่ลงนามนอกแถบ (ตัวอย่างเช่น โดยแจ้งไคลเอนต์เมื่ออ็อบเจกต์พร้อม) ดูสร้าง PDF ใน queued job
หมายเหตุด้านความปลอดภัย
หัวข้อที่มีชื่อว่า “หมายเหตุด้านความปลอดภัย”- เก็บ bucket ไว้เป็นส่วนตัว ให้ลายเซ็นเป็นผู้ให้สิทธิ์ ไม่เคยทำให้อ็อบเจกต์อ่านได้แบบสาธารณะเพื่อ “ทำให้การส่งมอบง่ายขึ้น” จุดประสงค์ทั้งหมดคือการเข้าถึงไหลผ่านลายเซ็นอายุสั้นเท่านั้น
- เวลาหมดอายุที่สั้นและจำกัดขอบเขต ลงนามสำหรับหน้าต่างเล็กที่สุดที่พอดีกับโฟลว์ของคุณ และจำกัดขอบเขตแต่ละ URL ไว้ที่อ็อบเจกต์เดียว ลิงก์ที่รั่วไหลจะหมดอายุเองและไม่เปิดเผยสิ่งอื่นใด
- secret จากสภาพแวดล้อม credential S3/GCS และ
APP_SECRETของ Symfony ที่หนุนUriSignerมาจากตัวแปรสภาพแวดล้อมหรือ secret manager ไม่เคยอยู่ในไฟล์กำหนดค่าที่ commit การหมุน signing secret ทำให้ทุกเส้นทางที่ลงนามที่ค้างอยู่เป็นโมฆะทันที - ตรวจสอบก่อนให้บริการบนเส้นทางที่ลงนามของแอป เมื่อการดาวน์โหลดข้ามแอปของคุณ (Laravel
signedmiddleware, SymfonyUriSigner::checkRequest()) ให้ตรวจสอบลายเซ็น ก่อน การเข้าถึง storage หรือการอนุญาตใด ๆ ปฏิเสธลิงก์ที่ถูกแก้ไขหรือหมดอายุด้วยสถานะที่กำหนดไว้ - ไม่เคย log URL ที่ลงนามฉบับเต็ม ลายเซ็นเป็น credential แบบ bearer ให้ log คีย์ของอ็อบเจกต์และตัวระบุ correlation ไม่ใช่ URL ที่ลงนาม และ log คลาส exception เมื่อล้มเหลว — ไม่เคย log ข้อความหรือ stack trace
- ไม่มี
catchว่าง ทุกตัวอย่าง log คลาสความล้มเหลวและคืนการตอบสนองข้อผิดพลาดที่กำหนดไว้
ความสอดคล้องตามมาตรฐาน
หัวข้อที่มีชื่อว่า “ความสอดคล้องตามมาตรฐาน”คู่มือนี้ไม่อ้างมาตรฐานเชิงบรรทัดฐานใด การเรียกเอนจิน NextPDF เพียงอย่างเดียวที่รูปแบบการส่งมอบนี้ต้องการคือ NextPDF\Core\Document::getPdfData() ซึ่งเป็นเมธอดสาธารณะที่ตรวจยืนยันแล้วและคืนไบนารี PDF ดิบ ส่วนตัวเอกสารเองถูกสร้างด้วยวิธีที่แอปของคุณสร้างเอกสารอยู่แล้ว (เช่น DocumentFactoryInterface ที่ฉีดเข้า / PdfFactory ของ Symfony) primitive สำหรับการลงนามเป็น API ของเฟรมเวิร์กและคลาวด์ที่มีเอกสารกำกับ — Laravel Storage::temporaryUrl() และ URL::temporarySignedRoute(), Symfony UriSigner และการดำเนินการ presigned-URL SDK ของ S3/GCS — และลายเซ็นที่แน่นอน ไดรเวอร์ที่รองรับ และหน้าต่างเวลาหมดอายุสูงสุดถูกควบคุมโดยโปรเจกต์ต้นน้ำเหล่านั้น ปรึกษาเอกสารของพวกเขาสำหรับสัญญาที่เชื่อถือได้บนแต่ละแพลตฟอร์ม
ดูเพิ่มเติม
หัวข้อที่มีชื่อว่า “ดูเพิ่มเติม”- ส่งคืน PDF ที่สร้างจากคอนโทรลเลอร์ — สตรีมไบต์โดยตรงเมื่อคุณไม่ต้องการ object storage ในวง
- สตรีม PDF ขนาดใหญ่ที่สร้างขึ้นเป็นการตอบสนอง HTTP — โมเดลหน่วยความจำแบบ buffered-vs-streamed เบื้องหลัง
getPdfData() - เรนเดอร์ที่ edge ด้วย Cloudflare — รูปแบบ signed-URL และ edge-render เฉพาะ R2 ของรูปแบบนี้
- สร้าง PDF ใน queued job — ย้าย build และอัปโหลดออกจาก thread ของคำขอ