8.0 KiB
8.0 KiB
PDF页面渲染方案 - ConvertPdfFirstPageToBitmap改造
问题诊断
当前ConvertPdfFirstPageToBitmap方法只生成纯白色Bitmap,未实现PDF页面的实际渲染,导致条码识别时无法识别到PDF中的内容(包括条码)。
关键需求更新 ⭐
- 充分利用现有能力:项目代码中已有将PDF URL转换成字节流的功能,此方案应与之集成
- 确保缓存完整性:只要验证了PDF有效性,不管是否成功提取条码,都必须将PDF字节流缓存到数据库
- 保障业务连续性:缓存的目的是确保现场可以正常进行面单打印,条码识别是增强功能非必须功能
现有资源确认
- ✅ 项目已集成
DinkToPdf库(通过PdfConverterSingleton) - ✅ 项目已有
System.Drawing和System.Drawing.Imaging - ✅ 项目已有PDF URL→字节流的转换能力(在下载/上传流程中)
- ✅ 完整的CORE SDK支持
改进后的解决方案
核心逻辑调整
原始流程(问题):
下载PDF → ConvertPdfFirstPageToBitmap(纯白) → 条码识别(失败) → 缓存PDF
改进流程(推荐):
下载PDF(已是字节流)
↓
验证PDF有效性 ✅ (关键点:必须执行)
├─ 有效 → 立即缓存PDF字节流到数据库 ⭐ (优先级:高)
└─ 无效 → 标记缓存失败,不阻塞业务
↓
条码识别(可选功能)
├─ ConvertPdfFirstPageToBitmap(渲染)
├─ RecognizeBarcodeAsync(识别)
└─ 识别成功 → 更新缓存的条码字段(选择性)
└─ 识别失败 → 不影响已缓存的PDF数据 ⭐
关键设计原则
原则1:缓存优先保障业务
- 验证通过即缓存:PDF验证成功→立即缓存PDF字节流
- 条码是增强功能:条码识别失败不影响PDF缓存
- 现场打印保障:确保缓存的PDF数据始终可用于打印
原则2:充分利用现有能力
- 复用字节流处理:项目中已有的PDF URL→字节流转换逻辑
- 整合现有DinkToPdf:如使用DinkToPdf转换,复用已有的
PdfConverterSingleton - 集成Ghostscript:如采用Ghostscript方案,将PDF字节流直接传入
原则3:非阻塞设计
- PDF缓存同步:验证+缓存为关键路径,必须快速完成
- 条码识别异步:条码识别为后续异步操作,失败不影响主流程
实施方案详细设计
方案A:基于现有DinkToPdf(最小化改动) ✅ 推荐短期
优点:
- 项目已集成DinkToPdf
- 与现有代码体系一致
- 改动最小
流程:
PDF字节流(已有)
↓
验证PDF + 缓存PDF字节流
↓
使用DinkToPdf或项目已有能力进行渲染
↓
条码识别(非必须)
方案B:集成Ghostscript(生产级方案) ✅ 推荐长期
优点:
- 渲染效果最佳
- 专业PDF处理
- 性能和质量兼优
流程:
PDF字节流(已有)
↓
验证PDF + 缓存PDF字节流
↓
使用Ghostscript渲染第一页→Bitmap
↓
条码识别(非必须)
推荐实施流程
第一阶段:保障业务连续性(高优先级)
-
改造缓存逻辑:确保PDF验证通过→立即缓存
- 在
ProcessSingleCacheTask中调整顺序 - 先保存PDF字节流,再进行条码识别
- 条码识别失败不回滚缓存
- 在
-
缓存表结构确认:确保能存储完整PDF数据
- PdfBytes字段:存储PDF二进制数据 ✅ (已有)
- Status字段:标记缓存状态 ✅ (已有)
- 条码字段:可选,识别成功时更新 ✅ (已有)
第二阶段:改造ConvertPdfFirstPageToBitmap方法(中优先级)
选项A1:使用DinkToPdf现有能力
输入:byte[] pdfBytes(已有字节流)
处理流程:
1. 使用PdfConverterSingleton进行转换
2. 或复用项目中TagGenerationService的PDF处理逻辑
3. 生成Bitmap用于条码识别
输出:Bitmap对象
选项B1:集成Ghostscript.NET
dotnet add package Ghostscript.NET
输入:byte[] pdfBytes(已有字节流)
处理流程:
1. 保存PDF字节流到临时文件
2. 使用GhostscriptRasterizer初始化
3. 设置DPI为200
4. 渲染第一页→Image
5. 转换为Bitmap
6. 清理临时文件
输出:Bitmap对象
第三阶段:异常处理和性能优化(低优先级)
- 渲染超时处理(3-5秒)
- 大文件内存管理
- 缓存渲染结果
实现要点详解
关键修改1:缓存优先原则
在ProcessSingleCacheTask中:
原始:下载PDF → 条码识别 → 成功则保存 → 失败则标记失败
改进:下载PDF → 验证PDF → 立即保存PDF → 异步进行条码识别
新增逻辑:
// 1. 获取PDF字节流(现有能力)
byte[] labelBytes = // ... 现有转换逻辑
// 2. 验证PDF有效性
int pageCount = GetPdfPageCount(labelBytes); // 验证通过
// 3. 立即保存PDF到缓存(同步)
await SaveCacheAsync(
waybillNumber,
labelBytes,
pageCount,
labelBytes.Length,
finalMileTrackingNumber,
customerId
// 暂不传入条码信息
);
// 4. 异步进行条码识别(不阻塞,失败不影响缓存)
_ = Task.Run(async () =>
{
try
{
var (barcodeNumber, barcodeType, confidence) =
await ExtractBarcodeFromPdfAsync(labelBytes);
if (!string.IsNullOrEmpty(barcodeNumber))
{
// 条码识别成功,更新缓存中的条码字段
await UpdateCacheWithBarcodeAsync(waybillNumber, barcodeNumber, barcodeType, confidence);
}
}
catch { /* 静默失败,不影响已缓存的PDF */ }
});
关键修改2:PDF字节流复用
充分利用现有能力:
现有代码中的PDF URL → 字节流转换
↓
直接传给ConvertPdfFirstPageToBitmap
↓
直接传给SaveCacheAsync
好处:
- 无需重复转换
- 一次转换多次使用
- 性能最优
风险评估与对策
风险1:缓存失败导致无备份
风险:如果缓存失败,可能没有PDF数据用于打印 对策:
- 重试机制:缓存失败重试3次
- 业务降级:缓存失败时保留URL,现场可实时下载
- 监控告警:记录所有缓存失败的case
风险2:条码识别阻塞缓存
风险:条码识别耗时影响业务 对策:
- 使用异步Task(已设计)
- 设置超时控制
- 失败自动降级
风险3:Ghostscript部署依赖
风险:Ghostscript需要系统配置 对策:
- 选择包含预编译二进制的NuGet版本
- 统一部署文档和脚本
- 开发环境提前测试
验证方案
验收标准
- ✅ 业务保障:无论条码识别成否,PDF都被成功缓存
- ✅ 调试可见:SaveDebugImage输出显示真实PDF内容(非空白)
- ✅ 条码增强:条码识别成功率>80%(失败不影响业务)
- ✅ 性能指标:
- PDF验证+缓存:<1秒
- PDF渲染:<3秒
- 条码识别:<2秒
- ✅ 异常处理:所有异常都被捕获,不影响主流程
测试场景
- 正常PDF→成功缓存+条码识别成功
- 正常PDF→成功缓存+条码识别失败(验收:PDF仍被缓存)
- 包含二维码的PDF→成功缓存+二维码识别
- 包含一维码的PDF→成功缓存+一维码识别
- 损坏的PDF→缓存失败,异常处理,业务降级
- 超大PDF→性能和内存测试
实施优先级
- 高优先级:修改缓存逻辑,确保验证通过→立即缓存
- 高优先级:改造ConvertPdfFirstPageToBitmap进行真实渲染
- 中优先级:条码识别异步化,失败自动降级
- 低优先级:性能优化和缓存结果
预期效果
完成此方案后:
- ✅ 业务连续性保障:缓存的PDF数据可用于现场打印
- ✅ 渲染效果提升:调试图像显示真实PDF内容
- ✅ 条码识别增强:成功识别条码但不强制依赖
- ✅ 系统鲁棒性:异常不影响核心缓存功能
- ✅ 现有资源复用:充分利用已有的字节流转换能力