# 定时任务执行范围优化 - 总结

**日期**: 2026-05-13  
**状态**: ✅ 完成并编译通过

---

## 🎯 问题分析

定时任务应该处理**订单表中有标签的数据**，但之前的实现只处理：
1. ❌ 缓存表中status=0（待处理）的记录
2. ❌ 缓存表中status=2（失败）且未超过重试次数的记录

**漏洞**：订单表中**新增的有标签订单**如果不在缓存表中，就永远不会被处理。

---

## ✅ 改进方案

现在定时任务按以下优先级处理：

```
第一步：处理失效的缓存（Status=3）
    └─ 订单表中有对应的有标签订单
    
第二步：处理待处理的任务（Status=0或2）
    └─ 订单表中有对应的有标签订单
    
第三步：处理订单表中新的有标签订单 ⭐ 新增
    └─ 缓存表中不存在对应记录
    └─ 订单表中该订单有标签数据
```

---

## 🔧 代码修改

### 1. 新增Repository方法

**文件**: `LabelPdfCacheRepository.cs`

```csharp
/// <summary>
/// 获取订单表中新的有标签订单（缓存表中不存在的）
/// </summary>
public async Task<List<string>> GetNewOrdersWithLabelsAsync(int limit)
{
    var db = _provider.GetClient();
    return await db.Queryable<LabelReplaceEntity>()
        .Where(o => !string.IsNullOrEmpty(o.Label))          // 订单有标签
        .Where(o => !SqlFunc.Subqueryable<LabelPdfCache>()
            .Where(c => c.NeutralWaybillNumber == o.NeutralWaybillNumber)
            .Any())                                           // 缓存表中不存在
        .Select(o => o.NeutralWaybillNumber)
        .Take(limit)
        .ToListAsync();
}
```

**关键SQL逻辑**：
```sql
SELECT o.NeutralWaybillNumber
FROM LabelReplaceEntity o
WHERE o.Label IS NOT NULL AND o.Label != ''
  AND NOT EXISTS (
    SELECT 1 FROM label_pdf_cache c 
    WHERE c.NeutralWaybillNumber = o.NeutralWaybillNumber
  )
```

### 2. 更新接口定义

**文件**: `ILabelPdfCacheRepository.cs`
- 添加 `GetNewOrdersWithLabelsAsync` 方法签名

### 3. 增强ProcessPendingTasksAsync逻辑

**文件**: `LabelPdfCacheService.cs`

新增第三步处理流程：
```csharp
// 第三步：处理订单表中新的有标签订单
var newBatchSize = remainingBatchSize - pendingTasks.Count;
if (newBatchSize > 0)
{
    var newOrders = await _cacheRepository.GetNewOrdersWithLabelsAsync(newBatchSize);
    foreach (var waybillNumber in newOrders)
    {
        if (await ProcessSingleCacheTask(waybillNumber))
        {
            successCount++;
        }
    }
}
```

**改进的日志**：
```
Completed processing PDF cache tasks, 
total processed: 45, 
invalid: 5, 
pending: 15, 
new orders: 25
```

---

## 📊 执行范围对比

### 修改前 ❌

| 订单状态 | 处理范围 |
|---------|---------|
| 订单有标签 | ❌ 仅处理缓存表中已存在的 |
| 新订单有标签 | ❌ **永远不会处理** |
| 缓存表无记录 | ❌ 跳过 |

### 修改后 ✅

| 订单状态 | 处理范围 |
|---------|---------|
| 失效缓存有标签 | ✅ 第一步处理 |
| 待处理缓存有标签 | ✅ 第二步处理 |
| 新订单有标签 | ✅ 第三步处理 |
| 缓存表无记录 | ✅ 新增处理 |

---

## 💡 工作流程示例

**场景**：早上10:00定时任务执行，batchSize=100

```
【第一步】处理失效缓存
  查询：SELECT * FROM label_pdf_cache WHERE Status=3 AND OrderWithLabel LIMIT 100
  结果：找到5条失效缓存
  操作：重新处理这5条
  
【第二步】处理待处理任务
  查询：SELECT * FROM label_pdf_cache 
        WHERE (Status=0 OR (Status=2 AND RetryCount<3)) 
        AND OrderWithLabel LIMIT 95
  结果：找到15条待处理
  操作：继续处理这15条
  
【第三步】处理新订单 ⭐ 新增
  查询：SELECT o.NeutralWaybillNumber FROM LabelReplaceEntity o
        WHERE o.Label IS NOT NULL 
        AND NOT EXISTS (SELECT 1 FROM label_pdf_cache c 
                       WHERE c.NeutralWaybillNumber = o.NeutralWaybillNumber)
        LIMIT 80
  结果：找到25条新订单有标签
  操作：为这25条新订单创建缓存
  
【结果】
  本次执行处理了 45 条记录
  - 失效缓存：5条
  - 待处理任务：15条
  - 新订单：25条
```

---

## 🛡️ 防护机制

1. **订单标签有效性检查**
   ```csharp
   .Where(o => !string.IsNullOrEmpty(o.Label))  // 确保Label不为空
   ```

2. **重复处理防护**
   ```csharp
   .Where(o => !SqlFunc.Subqueryable<LabelPdfCache>()
       .Where(c => c.NeutralWaybillNumber == o.NeutralWaybillNumber)
       .Any())  // 确保缓存表中不存在
   ```

3. **批量处理限制**
   - batchSize控制单次处理数量
   - 防止定时任务过度执行

4. **完整的日志记录**
   - 记录各阶段处理数量
   - 便于监控和调试

---

## ✨ 现在的覆盖场景

| 场景 | 处理方式 | 结果 |
|------|--------|------|
| 新订单有标签 | 第三步 | ✅ 立即创建缓存 |
| 已缓存订单 | 第一、二步 | ✅ 重试或更新 |
| 订单无标签 | 过滤掉 | ✅ 跳过 |
| 缓存表无记录 | 第三步 | ✅ 创建新记录 |

---

## 📈 预期收益

1. **完整覆盖** ✅
   - 不再有漏掉的新订单
   - 所有有标签订单都会被处理

2. **及时处理** ✅
   - 新订单会在下个定时任务周期处理
   - 缩短缓存生成时间

3. **可观测性** ✅
   - 详细的执行日志
   - 清晰的处理数量统计

4. **性能平衡** ✅
   - batchSize限制单次处理量
   - 不会过度消耗资源

---

## 📝 编译验证

```
✅ 编译成功（exit code = 0）
✅ 零编译错误
✅ 新增方法已实现
✅ 接口已更新
✅ 逻辑已完善
```

---

## 🚀 现在的定时任务能够

1. ✅ 处理订单表中**所有有标签的订单**
2. ✅ 优先处理失效和待处理的缓存
3. ✅ 自动发现新的有标签订单
4. ✅ 为新订单创建缓存记录
5. ✅ 提供详细的执行日志

---

**任务完成！定时任务现在能够正确处理订单表中所有有标签的数据。** ✅
