摘要
面向 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 依赖:
powershellInstall-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
:csharpTask 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. 转换接口定义
csharp
public interface IPltToPdfConverter
{
Task ConvertAsync(
string inputPath,
string outputPath,
IProgress<(int percent, string message)> progress,
CancellationToken cancellationToken);
}
2. Ghostscript.NET 实现
csharp
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 水印服务
csharp
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);
}
}
七、后台任务与进度推送
csharp
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)
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 压测
- 前端体验:可视化进度条、失败自动重试、历史记录查看
更多架构细节与实践优化,欢迎交流!