摘要
面向 Windows 平臺,使用 ASP.NET Core Web API 結合 Ghostscript.NET 庫,實現 PLT(HPGL)→PDF 的純庫調用轉換,無需外部進程。支持同步與異步模式,采用 JWT+RBAC 進行權限治理,任務狀態存儲于 Redis,后臺服務并行處理并通過 SSE/WebSocket 或輪詢實時推送進度。集成 iText7 對生成 PDF 添加半透明水印,輸出可插拔至本地、S3、Azure Blob 等存儲。容器化部署于 Windows Containers 或 Kubernetes,結合 HPA 彈性伸縮與 Serilog+Prometheus+Grafana 可觀測性,滿足高并發、安全、可運維的文件轉換需求。
關鍵字
- Ghostscript.NET
- 異步轉換
- 權限治理
- 進度推送
- Redis 存儲
- Kubernetes 部署
一、組件安裝與環境準備
- 操作系統:Windows 10/11 或 Windows Server 2019/2022
- .NET 環境:安裝 .NET 7.0 SDK 與對應 ASP.NET Core Runtime
- Ghostscript(含 PLT/HPGL 支持)
- 安裝 Ghostscript for Windows,確保包含
gsdll32.dll
/gsdll64.dll
- 將安裝目錄(例如
C:\Program Files\gs\gs9.xxx\bin
)加入系統 PATH
- 安裝 Ghostscript for Windows,確保包含
- NuGet 依賴:
Install-Package Ghostscript.NET Install-Package Ghostscript.NET.Rasterizer # 可選:預覽 Install-Package StackExchange.Redis Install-Package itext7 # 或 PdfSharpCore Install-Package AWSSDK.S3 # 可選:S3 存儲 Install-Package Azure.Storage.Blobs # 可選:Azure Blob Install-Package Hangfire.Core Hangfire.AspNetCore # 可選:任務調度
- Redis 服務:安裝 Memurai 或社區版 Redis for Windows,啟動并確認
localhost:6379
可訪問 - 開發工具:Visual Studio 2022 或 VS Code + C# 擴展
二、核心 HTTP 接口
-
POST
/plt/upload
- 參數:
IFormFile file
、string projectId
、string mode = "async"
- 返回:sync→
{ downloadUrl }
;async→{ taskId }
- 參數:
-
GET
/plt/status/{taskId}
- 返回:
{ taskId, status, progress, outputName, message }
- 返回:
-
GET
/plt/download/{fileName}
- 下載生成的 PDF
-
GET
/plt/list
- 參數:
projectId, page, size
- 返回:歷史轉換記錄列表
- 參數:
-
POST
/plt/uploadConverted
- 上傳外部已轉換 PDF,返回
{ url }
- 上傳外部已轉換 PDF,返回
-
GET
/auth/check
- 校驗當前用戶對項目的 convert/download 權限
三、同步與異步模式
-
同步(sync)
- 上傳后在當前請求里調用 Ghostscript.NET 直接轉換并水印
- 阻塞等待完成,返回下載鏈接
-
異步(async)
- 上傳后立即返回
taskId
- 后臺由
BackgroundService
或 Hangfire 從隊列取任務執行 - 前端通過輪詢或 SSE/WebSocket 獲取實時進度
- 上傳后立即返回
四、權限治理
- 鑒權:JWT + OAuth2 公鑰驗證
- 授權:Policy-Based Authorization + RBAC + 項目域校驗
- 中間件/過濾器解析 Token,校驗用戶角色、projectId 與操作類型
- 未授權訪問返回 HTTP 403
五、任務狀態管理與存儲
-
領域模型
TaskStatusDto
:TaskId
、Status
(PENDING/PROCESSING/DONE/FAILED)Progress
(0–100)、FileName
、OutputName
、Message
-
接口
ITaskStatusStore
:Task CreateAsync(TaskStatusDto status); Task UpdateAsync(string taskId, Action<TaskStatusDto> update); Task<TaskStatusDto?> GetAsync(string taskId); ValueTask<(string taskId, string inputPath, string outputPath)> DequeueAsync(CancellationToken ct);
-
Redis 實現:
StackExchange.Redis
保存 JSON,設置 TTL 自動清理 -
存儲輸出:本地目錄 / AWS S3 / Azure Blob / MinIO 插件化替換
六、PLT → PDF 轉換與水印(無外部進程)
1. 轉換接口定義
public interface IPltToPdfConverter
{Task ConvertAsync(string inputPath,string outputPath,IProgress<(int percent, string message)> progress,CancellationToken cancellationToken);
}
2. Ghostscript.NET 實現
using Ghostscript.NET;
using Ghostscript.NET.GhostscriptProcessing;public class GhostscriptNetConverter : IPltToPdfConverter
{private readonly GhostscriptVersionInfo _versionInfo;public GhostscriptNetConverter(){_versionInfo = GhostscriptVersionInfo.GetLastInstalledVersion()?? throw new InvalidOperationException("未檢測到 Ghostscript 安裝");}public Task ConvertAsync(string inputPath,string outputPath,IProgress<(int, string)> progress,CancellationToken cancellationToken) =>Task.Run(() =>{progress?.Report((10, "初始化 Ghostscript"));var switches = new[]{"-q", "-dNOPAUSE", "-dBATCH", "-dSAFER","-sDEVICE=pdfwrite",$"-sOutputFile={outputPath}",inputPath};using var processor = new GhostscriptProcessor(_versionInfo, true);processor.StartProcessing(switches,new GhostscriptProcessorTextDelegate(line =>{progress?.Report((50, "轉換進行中…"));}));progress?.Report((100, "轉換完成"));}, cancellationToken);
}
3. PDF 水印服務
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Canvas;public class PdfWatermarkService
{public void ApplyWatermark(string pdfPath, string watermarkText){var tmpPath = pdfPath + ".tmp";using var reader = new PdfReader(pdfPath);using var writer = new PdfWriter(tmpPath);using var pdf = new PdfDocument(reader, writer);for (int i = 1; i <= pdf.GetNumberOfPages(); i++){var canvas = new PdfCanvas(pdf.GetPage(i));canvas.SetFontAndSize(PdfFontFactory.CreateFont(), 36).SetFillColorGray(0.5f).BeginText().MoveText(200, 400).ShowText(watermarkText).EndText();}pdf.Close();File.Replace(tmpPath, pdfPath, null);}
}
七、后臺任務與進度推送
public class PltConversionJob : BackgroundService
{private readonly IPltToPdfConverter _converter;private readonly ITaskStatusStore _store;private readonly PdfWatermarkService _watermarker;public PltConversionJob(IPltToPdfConverter converter,ITaskStatusStore store,PdfWatermarkService watermarker){_converter = converter;_store = store;_watermarker = watermarker;}protected override async Task ExecuteAsync(CancellationToken stoppingToken){while (!stoppingToken.IsCancellationRequested){var (taskId, input, output) = await _store.DequeueAsync(stoppingToken);await _store.UpdateAsync(taskId, s => { s.Status = "PROCESSING"; s.Progress = 0; s.Message = "開始轉換"; });try{await _converter.ConvertAsync(input, output,new Progress<(int, string)>(p =>_store.UpdateAsync(taskId, s => { s.Progress = p.Item1; s.Message = p.Item2; })),stoppingToken);_watermarker.ApplyWatermark(output, "CONFIDENTIAL");await _store.UpdateAsync(taskId, s =>{s.Status = "DONE";s.Progress = 100;s.OutputName = Path.GetFileName(output);s.Message = "完成";});}catch (Exception ex){await _store.UpdateAsync(taskId, s => { s.Status = "FAILED"; s.Message = ex.Message; });}}}
}
八、配置示例(appsettings.json)
{"Plt": {"TempDirectory": "C:\\plt\\tmp","OutputDirectory": "C:\\plt\\out"},"Storage": {"Type": "Local","Local": { "RootPath": "C:\\plt\\out" }},"Redis": { "Configuration": "localhost:6379" },"Async": { "MaxConcurrentTasks": 8, "QueueCapacity": 200 },"Jwt": { "Authority": "https://auth.example.com", "Audience": "plt-api" },"Watermark": { "Enabled": true, "Text": "CONFIDENTIAL", "Opacity": 0.15, "FontSize": 36 },"Security": { "RateLimitQps": 50, "MaxUploadMb": 50 }
}
九、前端集成
- 技術棧:Vue/React + Axios
- 上傳:FormData POST
/plt/upload
- 實時進度:
setInterval
調/plt/status/{taskId}
或 SSE/WebSocket - 下載:
window.location.href = '/plt/download/' + outputName
十、部署與運維
- 容器化:Windows Containers(或 Linux 容器亦可加載 Ghostscript DLL)
- Kubernetes:Windows 節點組 + ConfigMap/Secret + PVC
- 彈性伸縮:HPA 按 CPU 與隊列長度自動擴容
- 安全:容器非管理員運行;Ghostscript 使用
-dSAFER
;上傳文件安全掃描 - 監控:Serilog + Prometheus + Grafana,關注 QPS、成功率、P95、隊列長度、失敗原因 Top N
十一、常見問題與優化
- 進度不精準:結合文件規模或并行分片估算
- I/O 瓶頸:將臨時目錄掛載到 RAM Disk
- 多格式支持:擴展輸出為 PDF/A、PNG、SVG
- 無服務器化:小文件可遷移至 Azure Functions/AWS Lambda
- 水印增強:動態二維碼、PDF 數字簽名
- CI/CD:GitHub Actions + Azure DevOps 壓測
- 前端體驗:可視化進度條、失敗自動重試、歷史記錄查看
更多架構細節與實踐優化,歡迎交流!