如何在 C# 中從 Kaizen.io 遷移到 IronPDF
從 Kaizen.io HTML-to-PDF 轉換到 IronPDF,可將您的 .NET PDF 工作流程從具有網路延遲和資料隱私疑慮的雲端服務,轉換為可將資料保存在基礎架構內的本機處理中資料庫。 本指南為專業的 .NET 開發人員提供了一個全面、循序漸進的遷移路徑,消除了外部 API 的依賴性和按需求定價的問題。
為什麼要從 Kaizen.io 轉移到 IronPDF?
雲端 API 的挑戰
Kaizen.io HTML-to-PDF 和其他基於雲端的 PDF 服務一樣,會引入影響生產應用程式的限制:
1.對雲端的依賴:需要持續的網路連線和外部服務的可用性。 如果 Kaizen.io 服務發生停機,您的應用程式的 PDF 生成將停止運作。
2.資料隱私問題:敏感的 HTML 內容(包括客戶資料、財務報告和機密文件)必須傳輸到第三方伺服器進行處理。
3.網路延遲:每次產生 PDF 都會產生 100-500 毫秒或更長的網路往返延遲,這會為應用程式的回應時間帶來顯著的開銷。
4.依請求定價:成本與使用量成正比,因此大批量產生 PDF 的成本越來越高。
5.速率限制:在高流量期間限制 API 速率可能會導致 PDF 產生失敗或延遲,而這正是您最需要它們的時候。
6.供應商鎖定: API 變更或服務終止風險使您的應用程式容易受到外部業務決策的影響。
Kaizen.io vsIronPDF比較
| 特點 | Kaizen.io | IronPDF |
|---|---|---|
| 處理 | 雲端(外部伺服器) | 本地 (處理中) |
| 資料隱私 | 外部傳輸的資料 | 資料永不離開您的基礎架構 |
| 延遲 | 網路往返 (100-500ms+) | 本地處理 (50-200ms) |
| 可用性 | 依賴外部服務 | 100% 由您掌控 |
| 定價 | 按要求或訂閱 | 一次性或年度授權 |
| 離線模式 | 不可能 | 完整功能 |
| 費率限制 | API 節流 | 無限制 |
| JavaScript | 有限支援 | 完整的 Chromium 執行 |
對於計劃在 2025 年和 2026 年之前採用 .NET 10 和 C# 14 的團隊而言,IronPDF 提供了一個面向未來的基礎,其本地處理功能消除了外部服務的依賴性。
遷移複雜性評估
按功能估計的工作量
| 特點 | 遷移複雜性 |
|---|---|
| 基本 HTML 至 PDF | 非常低 |
| HTML 檔案轉 PDF | 非常低 |
| URL 至 PDF | 非常低 |
| 頁首/頁尾 | 低 |
| 頁面設定 | 非常低 |
| API 金鑰管理 | 低 |
範式轉移
Kaizen.io這次遷移的根本轉變在於從雲端API呼叫轉向本地進程內渲染:
Kaizen.io: HtmlToPdfConverter → Convert(html) → byte[] (透過網路)
IronPDF: ChromePdfRenderer → RenderHtmlAsPdf(html) → PdfDocument (local)
開始之前
先決條件
- .NET環境: .NET Framework 4.6.2+ 或.NET Core 3.1+ / .NET 5/6/7/8/9+
- NuGet存取權限:能夠安裝NuGet套件
- IronPDF許可證:請從IronPDF取得您的許可證密鑰。
NuGet 套件變更
# Remove Kaizen.io package
dotnet remove package Kaizen.HtmlToPdf
dotnet remove package Kaizen.IO.HtmlToPdf
# Install IronPDF
dotnet add package IronPdf
# Remove Kaizen.io package
dotnet remove package Kaizen.HtmlToPdf
dotnet remove package Kaizen.IO.HtmlToPdf
# Install IronPDF
dotnet add package IronPdf
授權組態
// Add at application startup (Program.cs or Startup.cs)
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
// Add at application startup (Program.cs or Startup.cs)
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
' Add at application startup (Program.vb or Startup.vb)
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY"
辨識 Kaizen.io 的用法
# Find all Kaizen.io references
grep -r "using Kaizen\|HtmlToPdfConverter\|ConversionOptions" --include="*.cs" .
grep -r "ConvertUrl\|ConvertHtml\|Kaizen" --include="*.cs" .
# Find all Kaizen.io references
grep -r "using Kaizen\|HtmlToPdfConverter\|ConversionOptions" --include="*.cs" .
grep -r "ConvertUrl\|ConvertHtml\|Kaizen" --include="*.cs" .
完整的 API 參考資料
類別對應
| Kaizen.io 類別 | IronPDF 同等級產品 |
|---|---|
HtmlToPdfConverter |
ChromePdfRenderer |
ConversionOptions |
ChromePdfRenderOptions |
HeaderOptions |
HtmlHeaderFooter 或 TextHeaderFooter |
FooterOptions |
HtmlHeaderFooter 或 TextHeaderFooter |
PageSize |
PdfPaperSize |
Orientation |
PdfPaperOrientation |
方法對應
| Kaizen.io 方法 | IronPDF 同等級產品 |
|---|---|
converter.Convert(html) |
renderer.RenderHtmlAsPdf(html) |
converter.ConvertUrl(url) |
renderer.RenderUrlAsPdf(url) |
File.WriteAllBytes(path, bytes) |
pdf.SaveAs(path) |
轉換選項屬性對應
| Kaizen.io 財產 | IronPDF 同等級產品 |
|---|---|
PageSize |
RenderingOptions.PaperSize |
Orientation |
RenderingOptions.PaperOrientation |
MarginTop |
RenderingOptions.MarginTop |
MarginBottom |
RenderingOptions.MarginBottom |
Header.HtmlContent |
RenderingOptions.HtmlHeader.HtmlFragment |
Footer.HtmlContent |
RenderingOptions.HtmlFooter.HtmlFragment |
占位符對應
| Kaizen.io 占位符 | IronPDF 占位符 |
|---|---|
{page} |
{page} |
{total} |
{total-pages} |
{date} |
{date} |
{title} |
{html-title} |
程式碼遷移範例
範例 1:基本 HTML 到 PDF
之前 (Kaizen.io):
using Kaizen.IO;
using System.IO;
class Program
{
static void Main()
{
var converter = new HtmlToPdfConverter();
var html = "<html><body><h1>Hello World</h1></body></html>";
var pdfBytes = converter.Convert(html);
File.WriteAllBytes("output.pdf", pdfBytes);
}
}
using Kaizen.IO;
using System.IO;
class Program
{
static void Main()
{
var converter = new HtmlToPdfConverter();
var html = "<html><body><h1>Hello World</h1></body></html>";
var pdfBytes = converter.Convert(html);
File.WriteAllBytes("output.pdf", pdfBytes);
}
}
Imports Kaizen.IO
Imports System.IO
Class Program
Shared Sub Main()
Dim converter = New HtmlToPdfConverter()
Dim html = "<html><body><h1>Hello World</h1></body></html>"
Dim pdfBytes = converter.Convert(html)
File.WriteAllBytes("output.pdf", pdfBytes)
End Sub
End Class
After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System.IO;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello World</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System.IO;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
var html = "<html><body><h1>Hello World</h1></body></html>";
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf");
}
}
Imports IronPdf
Imports System.IO
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
Dim html = "<html><body><h1>Hello World</h1></body></html>"
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf")
End Sub
End Class
Kaizen.io 方法建立 HtmlToPdfConverter,呼叫 Convert() 取得位元組數組,然後使用 File.WriteAllBytes() 手動將位元組寫入檔案。 這涉及到 Kaizen.io 雲端服務的網路往返。
IronPDF 的 ChromePdfRenderer 將在本機處理所有內容。 RenderHtmlAsPdf() 方法傳回一個 PdfDocument 對象,該物件具有方便的 SaveAs() 方法-無需手動處理位元組數組,也沒有網路延遲。 請參閱 HTML to PDF 文件,以瞭解其他渲染選項。
範例 2:使用頁面設定將 HTML 檔案轉換為 PDF
之前 (Kaizen.io):
using Kaizen.IO;
using System;
using System.IO;
class Program
{
static void Main()
{
var converter = new HtmlToPdfConverter();
var htmlContent = File.ReadAllText("input.html");
var options = new ConversionOptions
{
PageSize = PageSize.A4,
Orientation = Orientation.Portrait
};
var pdfBytes = converter.Convert(htmlContent, options);
File.WriteAllBytes("document.pdf", pdfBytes);
}
}
using Kaizen.IO;
using System;
using System.IO;
class Program
{
static void Main()
{
var converter = new HtmlToPdfConverter();
var htmlContent = File.ReadAllText("input.html");
var options = new ConversionOptions
{
PageSize = PageSize.A4,
Orientation = Orientation.Portrait
};
var pdfBytes = converter.Convert(htmlContent, options);
File.WriteAllBytes("document.pdf", pdfBytes);
}
}
Imports Kaizen.IO
Imports System
Imports System.IO
Class Program
Shared Sub Main()
Dim converter = New HtmlToPdfConverter()
Dim htmlContent = File.ReadAllText("input.html")
Dim options = New ConversionOptions With {
.PageSize = PageSize.A4,
.Orientation = Orientation.Portrait
}
Dim pdfBytes = converter.Convert(htmlContent, options)
File.WriteAllBytes("document.pdf", pdfBytes)
End Sub
End Class
After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.IO;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Portrait;
var pdf = renderer.RenderHtmlFileAsPdf("input.html");
pdf.SaveAs("document.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.IO;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Portrait;
var pdf = renderer.RenderHtmlFileAsPdf("input.html");
pdf.SaveAs("document.pdf");
}
}
Imports IronPdf
Imports System
Imports System.IO
Class Program
Shared Sub Main()
Dim renderer = New ChromePdfRenderer()
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4
renderer.RenderingOptions.PaperOrientation = PdfPaperOrientation.Portrait
Dim pdf = renderer.RenderHtmlFileAsPdf("input.html")
pdf.SaveAs("document.pdf")
End Sub
End Class
Kaizen.io 方法需要手動讀取 HTML 檔案內容(File.ReadAllText()),建立一個單獨的物件(ConversionOptions),將兩者傳遞給方法(Convert()),然後手動將結果位元組寫入檔案。
IronPDF 提供了一種專用的 RenderHtmlFileAsPdf() 方法,可以直接讀取檔案—無需手動讀取檔案。 配置設定在渲染器的 RenderingOptions 屬性中,將所有設定集中在一個地方。 PdfPaperSize.A4 和 PdfPaperOrientation.Portrait 枚舉直接對應自 Kaizen.io 的等效枚舉。
範例 3:將 URL 轉換為帶有頁首和頁尾的 PDF 文件
之前 (Kaizen.io):
using Kaizen.IO;
using System;
using System.IO;
class Program
{
static void Main()
{
var converter = new HtmlToPdfConverter();
var options = new ConversionOptions
{
Header = new HeaderOptions { HtmlContent = "<div style='text-align:center'>Company Header</div>" },
Footer = new FooterOptions { HtmlContent = "<div style='text-align:center'>Page {page} of {total}</div>" },
MarginTop = 20,
MarginBottom = 20
};
var pdfBytes = converter.ConvertUrl("https://example.com", options);
File.WriteAllBytes("webpage.pdf", pdfBytes);
}
}
using Kaizen.IO;
using System;
using System.IO;
class Program
{
static void Main()
{
var converter = new HtmlToPdfConverter();
var options = new ConversionOptions
{
Header = new HeaderOptions { HtmlContent = "<div style='text-align:center'>Company Header</div>" },
Footer = new FooterOptions { HtmlContent = "<div style='text-align:center'>Page {page} of {total}</div>" },
MarginTop = 20,
MarginBottom = 20
};
var pdfBytes = converter.ConvertUrl("https://example.com", options);
File.WriteAllBytes("webpage.pdf", pdfBytes);
}
}
Imports Kaizen.IO
Imports System
Imports System.IO
Module Program
Sub Main()
Dim converter As New HtmlToPdfConverter()
Dim options As New ConversionOptions With {
.Header = New HeaderOptions With {.HtmlContent = "<div style='text-align:center'>Company Header</div>"},
.Footer = New FooterOptions With {.HtmlContent = "<div style='text-align:center'>Page {page} of {total}</div>"},
.MarginTop = 20,
.MarginBottom = 20
}
Dim pdfBytes = converter.ConvertUrl("https://example.com", options)
File.WriteAllBytes("webpage.pdf", pdfBytes)
End Sub
End Module
After (IronPDF):
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.IO;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextHeader.CenterText = "Company Header";
renderer.RenderingOptions.TextFooter.CenterText = "Page {page} of {total-pages}";
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
}
}
// NuGet: Install-Package IronPdf
using IronPdf;
using System;
using System.IO;
class Program
{
static void Main()
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.TextHeader.CenterText = "Company Header";
renderer.RenderingOptions.TextFooter.CenterText = "Page {page} of {total-pages}";
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
var pdf = renderer.RenderUrlAsPdf("https://example.com");
pdf.SaveAs("webpage.pdf");
}
}
Imports IronPdf
Imports System
Imports System.IO
Module Program
Sub Main()
Dim renderer As New ChromePdfRenderer()
renderer.RenderingOptions.TextHeader.CenterText = "Company Header"
renderer.RenderingOptions.TextFooter.CenterText = "Page {page} of {total-pages}"
renderer.RenderingOptions.MarginTop = 20
renderer.RenderingOptions.MarginBottom = 20
Dim pdf = renderer.RenderUrlAsPdf("https://example.com")
pdf.SaveAs("webpage.pdf")
End Sub
End Module
本範例展示了幾個關鍵的轉換差異。 Kaizen.io 要求在 HeaderOptions 和 FooterOptions 對像中嵌套 ConversionOptions 對象,每個對像都有一個 HtmlContent 屬性。IronPDF提供更簡潔的 TextHeader 和 TextFooter 配置,以及專用的 CenterText、LeftText 和 RightText 屬性。
重要提示:佔位符語法有所不同! Kaizen.io 使用 {total} 表示總頁數,而IronPDF使用 {total-pages} 表示總頁數。 這是最常見的遷移問題——在您的程式碼庫中搜尋 {total},並將其替換為 {total-pages}。
IronPDF 的 RenderUrlAsPdf() 方法可以直接透過 Chromium 引擎渲染任何 URL 並執行完整的JavaScript操作——無需任何變通方法。 進一步瞭解 URL 至 PDF 轉換 和 標頭和頁尾。
關鍵遷移注意事項
占位符語法變更
遷移頁首和頁尾時,最重要的變更是佔位符語法:
// Kaizen.io placeholders:
"Page {page} of {total}"
//IronPDFplaceholders:
"Page {page} of {total-pages}"
// Kaizen.io placeholders:
"Page {page} of {total}"
//IronPDFplaceholders:
"Page {page} of {total-pages}"
' Kaizen.io placeholders:
"Page {page} of {total}"
' IronPDF placeholders:
"Page {page} of {total-pages}"
完整的占位符映射:
{page}→{page}(同上){total}→{total-pages}(不同!){title}→{html-title}(不同!){date}→{date}(同上){time}→{time}(同上)
返回類型變更
Kaizen.io 直接回傳 byte[]。IronPDF回傳一個 PdfDocument 物件:
// Kaizen.io returns byte[]
byte[] pdfBytes = converter.Convert(html);
File.WriteAllBytes("output.pdf", pdfBytes);
//IronPDFreturns PdfDocument
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf"); // Direct save
byte[] bytes = pdf.BinaryData; // Or get bytes if needed
// Kaizen.io returns byte[]
byte[] pdfBytes = converter.Convert(html);
File.WriteAllBytes("output.pdf", pdfBytes);
//IronPDFreturns PdfDocument
var pdf = renderer.RenderHtmlAsPdf(html);
pdf.SaveAs("output.pdf"); // Direct save
byte[] bytes = pdf.BinaryData; // Or get bytes if needed
' Kaizen.io returns byte()
Dim pdfBytes As Byte() = converter.Convert(html)
File.WriteAllBytes("output.pdf", pdfBytes)
' IronPDF returns PdfDocument
Dim pdf = renderer.RenderHtmlAsPdf(html)
pdf.SaveAs("output.pdf") ' Direct save
Dim bytes As Byte() = pdf.BinaryData ' Or get bytes if needed
移除 API 金鑰管理
Kaizen.io 需要按要求進行 API 金鑰驗證。IronPDF使用在應用程式啟動時設定一次的授權金鑰:
// DELETE this Kaizen.io pattern:
var converter = new HtmlToPdfConverter("YOUR_API_KEY");
// IronPDF: Set once at startup
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var renderer = new ChromePdfRenderer(); // No API key needed
// DELETE this Kaizen.io pattern:
var converter = new HtmlToPdfConverter("YOUR_API_KEY");
// IronPDF: Set once at startup
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY";
var renderer = new ChromePdfRenderer(); // No API key needed
' DELETE this Kaizen.io pattern:
Dim converter = New HtmlToPdfConverter("YOUR_API_KEY")
' IronPDF: Set once at startup
IronPdf.License.LicenseKey = "YOUR-LICENSE-KEY"
Dim renderer = New ChromePdfRenderer() ' No API key needed
刪除網路錯誤處理
移除重試邏輯、速率限制處理和網路超時程式碼 -IronPDF在本機處理:
// DELETE this Kaizen.io pattern:
int retries = 3;
while (retries > 0)
{
try
{
return converter.Convert(html);
}
catch (RateLimitException)
{
retries--;
Thread.Sleep(1000);
}
}
// IronPDF: Just call the method
return renderer.RenderHtmlAsPdf(html).BinaryData;
// DELETE this Kaizen.io pattern:
int retries = 3;
while (retries > 0)
{
try
{
return converter.Convert(html);
}
catch (RateLimitException)
{
retries--;
Thread.Sleep(1000);
}
}
// IronPDF: Just call the method
return renderer.RenderHtmlAsPdf(html).BinaryData;
' DELETE this Kaizen.io pattern:
Dim retries As Integer = 3
While retries > 0
Try
Return converter.Convert(html)
Catch ex As RateLimitException
retries -= 1
Thread.Sleep(1000)
End Try
End While
' IronPDF: Just call the method
Return renderer.RenderHtmlAsPdf(html).BinaryData
疑難排解
問題 1:HtmlToPdfConverter 未找到
問題:IronPDF中不存在 HtmlToPdfConverter 類別。
解:替換為 ChromePdfRenderer:
// Kaizen.io
var converter = new HtmlToPdfConverter();
// IronPDF
var renderer = new ChromePdfRenderer();
// Kaizen.io
var converter = new HtmlToPdfConverter();
// IronPDF
var renderer = new ChromePdfRenderer();
' Kaizen.io
Dim converter As New HtmlToPdfConverter()
' IronPDF
Dim renderer As New ChromePdfRenderer()
問題 2:未找到轉換選項
問題:IronPDF中不存在 ConversionOptions 類別。
解決方案:在渲染器中使用 RenderingOptions:
// Kaizen.io
var options = new ConversionOptions { PageSize = PageSize.A4 };
converter.Convert(html, options);
// IronPDF
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderHtmlAsPdf(html);
// Kaizen.io
var options = new ConversionOptions { PageSize = PageSize.A4 };
converter.Convert(html, options);
// IronPDF
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4;
renderer.RenderHtmlAsPdf(html);
' Kaizen.io
Dim options As New ConversionOptions With {.PageSize = PageSize.A4}
converter.Convert(html, options)
' IronPDF
renderer.RenderingOptions.PaperSize = PdfPaperSize.A4
renderer.RenderHtmlAsPdf(html)
問題 3:頁碼無法正常運作
問題:頁腳顯示的是字面值 {total},而非頁數。
解決方案:更新佔位符語法:
// Kaizen.io syntax (won't work)
"Page {page} of {total}"
//IronPDFsyntax
"Page {page} of {total-pages}"
// Kaizen.io syntax (won't work)
"Page {page} of {total}"
//IronPDFsyntax
"Page {page} of {total-pages}"
問題 4:未找到轉換方法
問題: Convert() 方法在 ChromePdfRenderer 上不存在。
解:使用 RenderHtmlAsPdf():
// Kaizen.io
var pdfBytes = converter.Convert(html);
// IronPDF
var pdf = renderer.RenderHtmlAsPdf(html);
var pdfBytes = pdf.BinaryData;
// Kaizen.io
var pdfBytes = converter.Convert(html);
// IronPDF
var pdf = renderer.RenderHtmlAsPdf(html);
var pdfBytes = pdf.BinaryData;
' Kaizen.io
Dim pdfBytes = converter.Convert(html)
' IronPDF
Dim pdf = renderer.RenderHtmlAsPdf(html)
Dim pdfBytes = pdf.BinaryData
問題 5:首次渲染緩慢
問題:首次產生 PDF 檔案需要 1-3 秒。
解決方案:IronPDF會在首次使用時初始化 Chromium。 在應用程式啟動時進行暖機:
// In Program.cs or Startup.cs:
new ChromePdfRenderer().RenderHtmlAsPdf("<html></html>");
// In Program.cs or Startup.cs:
new ChromePdfRenderer().RenderHtmlAsPdf("<html></html>");
' In Program.vb or Startup.vb:
Call New ChromePdfRenderer().RenderHtmlAsPdf("<html></html>")
遷移清單
預遷移
- 辨識所有 Kaizen.io
using語句 - 文檔
ConversionOptions設定 - 注意頁首/頁尾模板和占位符
- 列出 API 金鑰位置(要刪除)
- 檢查重試/速率限制邏輯(以刪除)
- 取得IronPDF許可證密鑰
套件變更
- 刪除
Kaizen.HtmlToPdf套件 安裝IronPdfNuGet 套件:dotnet add package IronPdf - 更新命名空間匯入
程式碼變更
- 在啟動時新增許可證金鑰配置
- 將
HtmlToPdfConverter替換為ChromePdfRenderer - 將
ConversionOptions轉換為RenderingOptions - 將
Convert()更新為RenderHtmlAsPdf() - 將
ConvertUrl()更新為RenderUrlAsPdf() - 更新佔位語法(
{total}→{total-pages}) - 將
File.WriteAllBytes()替換為pdf.SaveAs() - 移除 API 金鑰配置
- 刪除重試/速率限制邏輯
- 移除 API 呼叫的網路錯誤處理
測試
- 測試所有 PDF 生成路徑
- 驗證頁首/頁尾渲染效果
- 檢查佔位符渲染
- 驗證頁邊距和頁面尺寸
- 測試離線功能(新功能!)
- 基準性能改進
後遷移
- 從設定中移除 Kaizen.io API 金鑰
- 更新環境變數
- 移除速率限製配置
- 更新監控/警報

