258 lines
8.0 KiB
Markdown
258 lines
8.0 KiB
Markdown
# PDF页面渲染方案 - ConvertPdfFirstPageToBitmap改造
|
||
|
||
## 问题诊断
|
||
当前`ConvertPdfFirstPageToBitmap`方法只生成纯白色Bitmap,未实现PDF页面的实际渲染,导致条码识别时无法识别到PDF中的内容(包括条码)。
|
||
|
||
## 关键需求更新 ⭐
|
||
1. **充分利用现有能力**:项目代码中已有将PDF URL转换成字节流的功能,此方案应与之集成
|
||
2. **确保缓存完整性**:只要验证了PDF有效性,不管是否成功提取条码,都必须将PDF字节流缓存到数据库
|
||
3. **保障业务连续性**:缓存的目的是确保现场可以正常进行面单打印,条码识别是增强功能非必须功能
|
||
|
||
## 现有资源确认
|
||
- ✅ 项目已集成`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
|
||
↓
|
||
条码识别(非必须)
|
||
```
|
||
|
||
## 推荐实施流程
|
||
|
||
### 第一阶段:保障业务连续性(高优先级)
|
||
1. **改造缓存逻辑**:确保PDF验证通过→立即缓存
|
||
- 在`ProcessSingleCacheTask`中调整顺序
|
||
- 先保存PDF字节流,再进行条码识别
|
||
- 条码识别失败不回滚缓存
|
||
|
||
2. **缓存表结构确认**:确保能存储完整PDF数据
|
||
- PdfBytes字段:存储PDF二进制数据 ✅ (已有)
|
||
- Status字段:标记缓存状态 ✅ (已有)
|
||
- 条码字段:可选,识别成功时更新 ✅ (已有)
|
||
|
||
### 第二阶段:改造ConvertPdfFirstPageToBitmap方法(中优先级)
|
||
|
||
#### 选项A1:使用DinkToPdf现有能力
|
||
```
|
||
输入:byte[] pdfBytes(已有字节流)
|
||
处理流程:
|
||
1. 使用PdfConverterSingleton进行转换
|
||
2. 或复用项目中TagGenerationService的PDF处理逻辑
|
||
3. 生成Bitmap用于条码识别
|
||
输出:Bitmap对象
|
||
```
|
||
|
||
#### 选项B1:集成Ghostscript.NET
|
||
```bash
|
||
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 → 异步进行条码识别
|
||
```
|
||
|
||
**新增逻辑**:
|
||
```csharp
|
||
// 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秒
|
||
- ✅ **异常处理**:所有异常都被捕获,不影响主流程
|
||
|
||
### 测试场景
|
||
1. 正常PDF→成功缓存+条码识别成功
|
||
2. 正常PDF→成功缓存+条码识别失败(验收:PDF仍被缓存)
|
||
3. 包含二维码的PDF→成功缓存+二维码识别
|
||
4. 包含一维码的PDF→成功缓存+一维码识别
|
||
5. 损坏的PDF→缓存失败,异常处理,业务降级
|
||
6. 超大PDF→性能和内存测试
|
||
|
||
## 实施优先级
|
||
1. **高优先级**:修改缓存逻辑,确保验证通过→立即缓存
|
||
2. **高优先级**:改造ConvertPdfFirstPageToBitmap进行真实渲染
|
||
3. **中优先级**:条码识别异步化,失败自动降级
|
||
4. **低优先级**:性能优化和缓存结果
|
||
|
||
## 预期效果
|
||
完成此方案后:
|
||
- ✅ 业务连续性保障:缓存的PDF数据可用于现场打印
|
||
- ✅ 渲染效果提升:调试图像显示真实PDF内容
|
||
- ✅ 条码识别增强:成功识别条码但不强制依赖
|
||
- ✅ 系统鲁棒性:异常不影响核心缓存功能
|
||
- ✅ 现有资源复用:充分利用已有的字节流转换能力
|
||
|