Files
LabelChange-server/.trae/docs/ParseDurationMs_Field_Addition.md
2026-06-01 16:30:29 +08:00

7.1 KiB
Raw Permalink Blame History

ParseDurationMs 字段添加总结

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


📋 字段定义

字段信息

  • 字段名: ParseDurationMs
  • 数据类型: INT
  • 可空: 是DEFAULT NULL
  • 含义: PDF解析花费的时间毫秒
  • 位置: BarcodeExtractTime 之后

🗄️ SQL脚本

1 新建表完整SQL

文件: CreateLabelPdfCacheTable_Complete.sql

CREATE TABLE `label_pdf_cache` (
  ...
  `BarcodeExtractTime` datetime DEFAULT NULL COMMENT '条码提取完成时间',
  `ParseDurationMs` int DEFAULT NULL COMMENT 'PDF解析花费的时间毫秒',
  ...
)

2 添加字段SQL

文件: AddParseDurationMsField.sql

ALTER TABLE label_pdf_cache 
ADD COLUMN ParseDurationMs INT DEFAULT NULL COMMENT 'PDF解析花费的时间毫秒' 
AFTER BarcodeExtractTime;

💻 代码修改

1. 实体类修改 (LabelPdfCache.cs)

/// <summary>
/// PDF解析花费的时间毫秒
/// </summary>
[SugarColumn(IsNullable = true)]
public int? ParseDurationMs { get; set; }

2. 时间记录逻辑 (LabelPdfCacheService.cs)

方法开始处

private async Task<bool> ProcessSingleCacheTask(string waybillNumber, LabelPdfCache? existingCache = null)
{
    var startTime = DateTime.UtcNow;  // ⭐ 记录开始时间
    try
    {
        // 处理逻辑...
    }
}

计算解析时间

var parseDurationMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;

在各个阶段记录

// 验证失败时
var duration = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
await UpdateCacheStatus(existingCache, waybillNumber, 2, errorMsg, retryCount, duration);

// 验证成功时
var parseDurationMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
await SaveCacheAsync(..., parseDurationMs: parseDurationMs);

3. 方法签名更新

SaveCacheAsync

public async Task<bool> SaveCacheAsync(
    string waybillNumber, byte[] pdfBytes, int pageCount, int fileSize, 
    string? originalUrl = null,
    string? finalMileTrackingNumber = null, int? customerId = null,
    string? barcodeNumber = null, byte barcodeType = 0, 
    int? barcodeConfidence = null, 
    int? parseDurationMs = null)  // ⭐ 新增参数

UpdateCacheStatus

private async Task UpdateCacheStatus(
    LabelPdfCache? existingCache, string waybillNumber, 
    byte status, string errorMessage, int retryCount, 
    int? parseDurationMs = null)  // ⭐ 新增参数

📊 记录时间的场景

场景 记录时间 说明
PDF验证失败页数超出 从开始到验证失败所用时间
PDF验证失败文件过大 从开始到验证失败所用时间
PDF验证成功 从开始到完成条码识别所用时间
HTTP下载错误 会记录到错误日志中
Base64解析错误 会记录到错误日志中

📝 示例数据

缓存表中的数据示例

| Id | NeutralWaybillNumber | ParseDurationMs | Status | BarcodeNumber |
|----|----------------------|-----------------|--------|---------------|
| 1  | 202605130001        | 245             | 1      | 1234567890    |
| 2  | 202605130002        | 532             | 1      | NULL          |
| 3  | 202605130003        | 89              | 2      | NULL          |
| 4  | 202605130004        | 1523            | 1      | 9876543210    |

说明

  • 245ms: 快速处理可能是简单PDF
  • 532ms: 中等处理(包含条码识别)
  • 89ms: 非常快(只是验证,未保存)
  • 1523ms: 较慢处理复杂PDF + 条码识别)

🔍 使用场景

1. 性能分析

-- 查询平均解析时间
SELECT AVG(ParseDurationMs) as AvgDuration, COUNT(*) as Total
FROM label_pdf_cache
WHERE Status = 1;

-- 查询最慢的10条记录
SELECT NeutralWaybillNumber, ParseDurationMs
FROM label_pdf_cache
ORDER BY ParseDurationMs DESC
LIMIT 10;

2. 监控告警

-- 找出解析时间超过5秒的记录可能表示性能问题
SELECT NeutralWaybillNumber, ParseDurationMs
FROM label_pdf_cache
WHERE ParseDurationMs > 5000
  AND Status = 1;

3. 优化评估

-- 按日期统计平均解析时间的变化趋势
SELECT DATE(CreatedTime) as Date, 
       AVG(ParseDurationMs) as AvgDuration,
       MIN(ParseDurationMs) as MinDuration,
       MAX(ParseDurationMs) as MaxDuration,
       COUNT(*) as ProcessCount
FROM label_pdf_cache
WHERE Status = 1
GROUP BY DATE(CreatedTime)
ORDER BY Date DESC;

📈 日志输出示例

[2026-05-13 10:30:45] INFO: Processing cache task for waybill: SFN202605130001
[2026-05-13 10:30:45] DEBUG: Successfully rendered PDF to bitmap using GhostScript, size: 800x1200
[2026-05-13 10:30:46] INFO: Successfully saved cache for waybill: SFN202605130001
  └─ ParseDurationMs: 532ms

[2026-05-13 10:30:47] WARN: PDF page count exceeded for waybill: SFN202605130002, pages: 3, duration: 89ms
[2026-05-13 10:30:47] INFO: Updated cache status for waybill: SFN202605130002
  └─ ParseDurationMs: 89ms

验证清单

✅ 实体类字段已添加
✅ 建表SQL已更新
✅ 添加字段SQL已创建
✅ SaveCacheAsync方法已更新
✅ UpdateCacheStatus方法已更新
✅ 接口定义已更新
✅ 时间记录逻辑已实现
✅ 编译成功exit code = 0
✅ 零编译错误

📋 SQL脚本执行步骤

新环境部署

# 执行完整建表脚本
mysql> source CreateLabelPdfCacheTable_Complete.sql;

# 验证字段
mysql> DESC label_pdf_cache;
# 应该看到 ParseDurationMs INT 字段

现有环境升级

# 1. 备份现有数据(重要!)
mysql> BACKUP TABLE label_pdf_cache TO '/backup/';

# 2. 执行添加字段脚本
mysql> source AddParseDurationMsField.sql;

# 3. 验证字段添加成功
mysql> SELECT COLUMN_NAME, COLUMN_TYPE 
       FROM INFORMATION_SCHEMA.COLUMNS 
       WHERE TABLE_NAME = 'label_pdf_cache' 
       AND COLUMN_NAME = 'ParseDurationMs';

# 应该返回:
# COLUMN_NAME: ParseDurationMs
# COLUMN_TYPE: int(11)

💡 性能提示

  1. 不需要索引

    • ParseDurationMs 字段无需单独索引
    • 通常用于分析而不是查询过滤
  2. 存储空间影响

    • INT 字段占用4字节
    • 每条记录增加4字节原为NULL时
    • 影响微小
  3. 查询建议

    -- 如果频繁按解析时间范围查询,可添加索引
    CREATE INDEX IX_ParseDurationMs ON label_pdf_cache (ParseDurationMs);
    

📊 预期数据分布

基于物流标签PDF通常的特点

解析时间范围 比例 场景
< 100ms 5% 缓存中检验失败的记录
100-500ms 60% 简单PDF无或简单条码
500-1000ms 25% 复杂PDF有条码识别
1000-2000ms 9% 超大文件或GhostScript不可用
> 2000ms 1% 异常情况

所有代码和SQL脚本已准备好

现在您可以:

  1. 在新环境使用完整建表SQL
  2. 在现有环境执行添加字段SQL
  3. 自动记录每个PDF的解析时间
  4. 用于性能分析和优化