上传源代码版本
This commit is contained in:
165
.trae/documents/label_pdf_cache_plan.md
Normal file
165
.trae/documents/label_pdf_cache_plan.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# 面单PDF字节流缓存方案规划
|
||||
|
||||
## 一、需求与现有逻辑评估
|
||||
|
||||
### 现有逻辑分析
|
||||
|
||||
1. **下载接口**:`LabelController.DownloadLabelByWaybillNumber` 实时根据中性面单号获取标签数据,若为URL则实时下载,校验PDF页数后返回字节流,存在网络开销和超时风险。
|
||||
2. **下单接口**:`TagController.LabelReplace` 生成标签替换记录,存储到订单表(`LabelReplaceEntity`对应表),`Label`字段存储URL或base64编码内容。
|
||||
|
||||
### 核心需求目标
|
||||
|
||||
* 减少URL实时下载的网络开销
|
||||
|
||||
* 提前校验面单问题(页数超限、无数据等)
|
||||
|
||||
* 实现下载重试机制,避免超时
|
||||
|
||||
* 不影响原有换单流程,无缓存时降级使用原URL访问
|
||||
|
||||
## 二、数据库表设计
|
||||
|
||||
### 表名:`LabelPdfCache`
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| -------------------- | ------------------------- | ------------------------------ |
|
||||
| Id | bigint | 主键,自增 |
|
||||
| NeutralWaybillNumber | varchar(50) | 中性面单号,唯一索引 |
|
||||
| PdfBytes | longblob / varbinary(max) | PDF二进制字节流 |
|
||||
| PageCount | int | PDF实际页数 |
|
||||
| FileSize | int | 文件大小(字节) |
|
||||
| Status | tinyint | 缓存状态:0=待处理,1=处理成功,2=处理失败,3=已失效 |
|
||||
| RetryCount | int | 已重试次数,默认0 |
|
||||
| LastRetryTime | datetime | 最后重试时间 |
|
||||
| ErrorMessage | varchar(500) | 处理失败错误信息 |
|
||||
| CreatedTime | datetime | 创建时间 |
|
||||
| UpdatedTime | datetime | 更新时间 |
|
||||
|
||||
### 索引设计
|
||||
|
||||
* 唯一索引:`IX_NeutralWaybillNumber`(中性面单号,用于快速查询)
|
||||
|
||||
* 普通索引:`IX_Status_RetryCount`(状态+重试次数,用于定时任务扫描)
|
||||
|
||||
## 三、定时任务实现方案
|
||||
|
||||
### 任务执行逻辑
|
||||
|
||||
1. **扫描条件**:每次扫描订单表中满足以下条件的记录:
|
||||
|
||||
* `Label`字段不为空
|
||||
|
||||
* 未在`LabelPdfCache`表中存在,或`LabelPdfCache`中状态为失败且重试次数<3
|
||||
2. **处理流程**:
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[扫描待处理订单] --> B{是否有缓存记录}
|
||||
B -->|无| C[新建缓存记录状态=待处理]
|
||||
B -->|有失败记录| D[更新重试次数+1,状态=待处理]
|
||||
C --> E[下载/解析Label内容]
|
||||
D --> E
|
||||
E --> F{解析成功?}
|
||||
F -->|是| G[校验PDF页数≤1,大小正常]
|
||||
F -->|否| H[更新状态=失败,记录错误信息]
|
||||
G -->|校验通过| I[保存字节流,状态=成功]
|
||||
G -->|校验失败| J[更新状态=失败,记录校验错误]
|
||||
```
|
||||
|
||||
### 任务配置
|
||||
|
||||
* 执行频率:建议每5分钟执行一次,可配置
|
||||
|
||||
* 单次处理数量:每次最多处理100条,避免占用过多资源
|
||||
|
||||
* 重试间隔:失败后至少间隔10分钟再重试
|
||||
|
||||
## 四、接口逻辑改造
|
||||
|
||||
### 1. 下载接口优化(LabelController)
|
||||
|
||||
原有逻辑保持不变,新增缓存查询逻辑:
|
||||
|
||||
```csharp
|
||||
// 新增:先查询缓存
|
||||
var cache = await _labelPdfCacheService.GetValidCacheAsync(waybillNumber);
|
||||
if (cache != null && cache.Status == 1)
|
||||
{
|
||||
_logger.LogInformation("Hit PDF cache for waybill: {number}", waybillNumber);
|
||||
scanResult = ScanResult.ReturnedLabel;
|
||||
scanDescription = "命中缓存返回标签";
|
||||
return File(cache.PdfBytes, "application/pdf", $"label_{waybillNumber}.pdf");
|
||||
}
|
||||
// 未命中缓存,走原有逻辑
|
||||
// ... 原有下载/解析逻辑 ...
|
||||
// 新增:解析成功后异步写入缓存
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await _labelPdfCacheService.SaveCacheAsync(waybillNumber, labelBytes, pageCount, labelBytes.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Save cache failed for waybill: {number}", waybillNumber);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 2. 换单接口兼容(TagController)
|
||||
|
||||
无需改造原有逻辑,当调用`_labelReplaceService.ProcessLabelReplaceAsync`时,若需要返回字节流:
|
||||
|
||||
* 先查询缓存,存在则直接使用
|
||||
|
||||
* 不存在则走原有URL访问逻辑
|
||||
|
||||
## 五、重试机制设计
|
||||
|
||||
### 重试规则
|
||||
|
||||
1. 默认最大重试次数:3次
|
||||
2. 重试触发条件:
|
||||
|
||||
* 网络请求超时
|
||||
|
||||
* HTTP请求错误(5xx、429等可重试错误)
|
||||
|
||||
* 临时IO异常
|
||||
3. 不重试条件:
|
||||
|
||||
* PDF页数超过1页(校验不通过,无需重试)
|
||||
|
||||
* 标签数据格式错误(base64解析失败)
|
||||
|
||||
* 4xx错误(404、403等客户端错误)
|
||||
|
||||
### 退避策略
|
||||
|
||||
* 第1次失败:间隔10分钟重试
|
||||
|
||||
* 第2次失败:间隔30分钟重试
|
||||
|
||||
* 第3次失败:标记为最终失败,不再重试
|
||||
|
||||
## 六、异常处理与降级策略
|
||||
1. **缓存服务异常**:缓存服务不可用时,直接降级走原有实时下载逻辑,不影响主流程
|
||||
2. **定时任务异常**:定时任务执行失败不影响正常接口调用,仅预缓存功能暂时失效
|
||||
3. **缓存失效策略**:
|
||||
- **触发时机**:当订单的`Label`字段更新时(如换单服务更新标签URL、重新生成标签等场景),同步调用`_labelPdfCacheService.InvalidateCacheAsync(waybillNumber)`将对应缓存标记为失效状态(Status=3)
|
||||
- **失效后处理**:
|
||||
1. 被标记为失效的缓存不会在下载接口中被返回
|
||||
2. 定时任务扫描时会优先处理状态为已失效的记录,重置重试次数为0,重新下载解析新的Label内容
|
||||
3. 失效缓存的字节流会保留24小时后自动清理,方便问题排查
|
||||
- **兜底校验**:下载接口查询缓存时,会额外校验订单表的`Label`更新时间与缓存更新时间,若缓存更新时间早于Label更新时间,自动忽略缓存走实时下载逻辑,同时异步触发缓存更新,避免标记失效遗漏导致返回旧数据
|
||||
4. 确保系统稳定: 定时任务的启动和执行不能影响整个程序.
|
||||
|
||||
## 七、实施步骤
|
||||
|
||||
1. 新增`LabelPdfCache`实体类和数据库迁移脚本
|
||||
2. 实现`LabelPdfCacheService`缓存操作服务
|
||||
3. 实现定时任务服务(基于Hangfire或现有定时任务框架)
|
||||
4. 改造`DownloadLabelByWaybillNumber`接口增加缓存逻辑
|
||||
5. 适配换单服务的缓存查询逻辑
|
||||
6. 测试验证功能正确性和性能提升效果
|
||||
|
||||
Reference in New Issue
Block a user