# 标签获取时间字段实现方案

## 1. 需求概述

在 `label_replace_requests` 表中添加一个新字段 `LabelRetrievedAt`，用于记录客户获取标签的时间，并通过数据库触发器自动更新该字段。

## 2. 实现步骤

### 2.1 修改数据模型

已在 `LabelReplaceEntity` 类中添加了 `LabelRetrievedAt` 字段：

```csharp
/// <summary>
/// 获取标签时间
/// </summary>
[SugarColumn(IsNullable = true)]
public DateTime? LabelRetrievedAt { get; set; }
```

### 2.2 数据库表结构更新

**方法1：使用代码优先方式**

当应用程序启动时，SqlSugar 的 CodeFirst 功能会自动创建新字段。

**方法2：手动执行SQL语句**

```sql
ALTER TABLE label_replace_requests 
ADD COLUMN LabelRetrievedAt DATETIME NULL COMMENT '获取标签时间';
```

### 2.3 创建触发器

#### 2.3.1 基本触发器

```sql
DELIMITER $$
CREATE TRIGGER update_label_retrieved_timestamp
BEFORE UPDATE ON label_replace_requests
FOR EACH ROW
BEGIN
    -- 当Label字段被设置或修改时，更新LabelRetrievedAt
    IF NEW.Label IS NOT NULL AND (OLD.Label IS NULL OR NEW.Label != OLD.Label) THEN
        SET NEW.LabelRetrievedAt = UTC_TIMESTAMP();
    END IF;
    -- 同时更新UpdatedAt字段
    SET NEW.UpdatedAt = UTC_TIMESTAMP();
END$$
DELIMITER ;
```

#### 2.3.2 包含条件判断的触发器（可选）

```sql
DELIMITER $$
CREATE TRIGGER update_label_retrieved_timestamp
BEFORE UPDATE ON label_replace_requests
FOR EACH ROW
BEGIN
    -- 当Label字段被设置或修改时，更新LabelRetrievedAt
    IF NEW.Label IS NOT NULL AND (OLD.Label IS NULL OR NEW.Label != OLD.Label) THEN
        SET NEW.LabelRetrievedAt = UTC_TIMESTAMP();
    END IF;
    
    -- 仅当记录确实被修改时才更新UpdatedAt
    IF NOT (NEW.BillOfLadingNumber <=> OLD.BillOfLadingNumber AND 
            NEW.MasterPackageNumber <=> OLD.MasterPackageNumber AND 
            NEW.ReferenceNumber <=> OLD.ReferenceNumber AND 
            NEW.NeutralWaybillNumber <=> OLD.NeutralWaybillNumber AND 
            NEW.FinalMileTrackingNumber <=> OLD.FinalMileTrackingNumber AND 
            NEW.Label <=> OLD.Label AND 
            NEW.ReplaceStatus <=> OLD.ReplaceStatus AND 
            NEW.CustomerId <=> OLD.CustomerId) THEN
        SET NEW.UpdatedAt = UTC_TIMESTAMP();
    END IF;
END$$
DELIMITER ;
```

## 3. 触发器说明

### 3.1 触发时机
- `BEFORE UPDATE`：在更新操作执行前触发
- `FOR EACH ROW`：对每一行更新都会触发

### 3.2 逻辑说明
- 当 `Label` 字段从 `NULL` 变为非 `NULL`，或其值被修改时，更新 `LabelRetrievedAt` 为当前时间
- 同时更新 `UpdatedAt` 字段，保持与现有逻辑一致

### 3.3 时间戳设置
- 使用 `UTC_TIMESTAMP()` 函数获取当前UTC时间，与代码中使用的 `DateTime.UtcNow` 保持一致
- `LabelRetrievedAt` 字段为可为空的日期时间类型，仅在标签被设置时才更新

## 4. 测试验证

### 4.1 功能测试

1. **创建测试记录**：
   ```sql
   INSERT INTO label_replace_requests (NeutralWaybillNumber, ReplaceStatus) 
   VALUES ('TEST123456', 'Y');
   ```

2. **查询初始状态**：
   ```sql
   SELECT LabelRetrievedAt, UpdatedAt FROM label_replace_requests WHERE NeutralWaybillNumber = 'TEST123456';
   ```
   预期结果：`LabelRetrievedAt` 为 `NULL`

3. **设置标签**：
   ```sql
   UPDATE label_replace_requests SET Label = 'base64_encoded_pdf' 
   WHERE NeutralWaybillNumber = 'TEST123456';
   ```

4. **验证时间戳是否更新**：
   ```sql
   SELECT LabelRetrievedAt, UpdatedAt FROM label_replace_requests WHERE NeutralWaybillNumber = 'TEST123456';
   ```
   预期结果：`LabelRetrievedAt` 和 `UpdatedAt` 都被更新为当前时间

5. **更新其他字段**：
   ```sql
   UPDATE label_replace_requests SET FinalMileTrackingNumber = 'FM123456' 
   WHERE NeutralWaybillNumber = 'TEST123456';
   ```

6. **验证LabelRetrievedAt是否保持不变**：
   ```sql
   SELECT LabelRetrievedAt, UpdatedAt FROM label_replace_requests WHERE NeutralWaybillNumber = 'TEST123456';
   ```
   预期结果：`LabelRetrievedAt` 保持不变，`UpdatedAt` 被更新为当前时间

### 4.2 性能测试

1. **批量更新测试**：
   ```sql
   -- 创建测试数据
   INSERT INTO label_replace_requests (NeutralWaybillNumber, ReplaceStatus) 
   VALUES 
   ('TEST001', 'Y'),
   ('TEST002', 'Y'),
   ('TEST003', 'Y'),
   ('TEST004', 'Y'),
   ('TEST005', 'Y');
   
   -- 批量更新标签
   UPDATE label_replace_requests SET Label = 'base64_encoded_pdf' 
   WHERE NeutralWaybillNumber LIKE 'TEST%';
   ```

2. **验证批量更新结果**：
   ```sql
   SELECT NeutralWaybillNumber, LabelRetrievedAt FROM label_replace_requests WHERE NeutralWaybillNumber LIKE 'TEST%';
   ```
   预期结果：所有记录的 `LabelRetrievedAt` 都被更新为当前时间

## 5. 注意事项

### 5.1 权限要求
- 需要 `CREATE TRIGGER` 权限
- 需要对 `label_replace_requests` 表有 `TRIGGER` 权限
- 需要对 `label_replace_requests` 表有 `ALTER` 权限（用于添加新字段）

### 5.2 性能影响
- 触发器会在每次更新操作时执行，可能影响高频更新场景的性能
- 建议在生产环境中进行性能测试

### 5.3 维护性
- 触发器逻辑存储在数据库中，与应用代码分离，可能增加维护难度
- 建议在代码中添加注释，说明触发器的存在和功能

### 5.4 兼容性
- 确保MySQL版本支持触发器（MySQL 5.0及以上版本支持）
- 不同MySQL版本的触发器语法可能略有差异

## 6. 触发器管理

### 6.1 修改触发器

```sql
-- 先删除旧触发器
DROP TRIGGER IF EXISTS update_label_retrieved_timestamp;

-- 再创建新触发器
DELIMITER $$
CREATE TRIGGER update_label_retrieved_timestamp
BEFORE UPDATE ON label_replace_requests
FOR EACH ROW
BEGIN
    -- 当Label字段被设置或修改时，更新LabelRetrievedAt
    IF NEW.Label IS NOT NULL AND (OLD.Label IS NULL OR NEW.Label != OLD.Label) THEN
        SET NEW.LabelRetrievedAt = UTC_TIMESTAMP();
    END IF;
    -- 同时更新UpdatedAt字段
    SET NEW.UpdatedAt = UTC_TIMESTAMP();
END$$
DELIMITER ;
```

### 6.2 删除触发器

```sql
DROP TRIGGER IF EXISTS update_label_retrieved_timestamp;
```

## 7. 代码优化建议

### 7.1 数据模型优化

考虑在 `LabelReplaceEntity` 类中添加构造函数，确保所有时间字段都有合理的默认值：

```csharp
public LabelReplaceEntity()
{
    CreatedAt = DateTime.UtcNow;
    UpdatedAt = DateTime.UtcNow;
    ReplaceStatus = "Y";
    LabelRetrievedAt = null;
}
```

### 7.2 服务层优化

在 `LabelReplaceService.ProcessLabelReplaceAsync` 方法中，当设置或修改标签时，可以考虑在代码层面也设置 `LabelRetrievedAt` 字段，以确保即使不使用触发器也能正确记录时间：

```csharp
// 更新现有记录时
if (request.Label != null)
{
    existingRecord.Label = request.Label;
    existingRecord.LabelRetrievedAt = DateTime.UtcNow;
}
```

## 8. 总结

通过添加 `LabelRetrievedAt` 字段并创建相应的数据库触发器，可以自动记录客户获取标签的时间。这种方法的优点是：

1. **自动记录**：无需在应用代码中手动设置，触发器会自动处理
2. **准确性**：仅在标签被设置或修改时才更新时间戳
3. **一致性**：与现有的 `UpdatedAt` 字段更新逻辑保持一致

在实际应用中，应根据系统的具体情况（如更新频率、性能要求等）选择合适的实现方式。