# ParseDurationMs 字段添加总结 **日期**: 2026-05-13 **状态**: ✅ 完成并编译通过 --- ## 📋 字段定义 ### 字段信息 - **字段名**: `ParseDurationMs` - **数据类型**: `INT` - **可空**: 是(DEFAULT NULL) - **含义**: PDF解析花费的时间(毫秒) - **位置**: BarcodeExtractTime 之后 --- ## 🗄️ SQL脚本 ### 1️⃣ 新建表完整SQL **文件**: `CreateLabelPdfCacheTable_Complete.sql` ```sql CREATE TABLE `label_pdf_cache` ( ... `BarcodeExtractTime` datetime DEFAULT NULL COMMENT '条码提取完成时间', `ParseDurationMs` int DEFAULT NULL COMMENT 'PDF解析花费的时间(毫秒)', ... ) ``` ### 2️⃣ 添加字段SQL **文件**: `AddParseDurationMsField.sql` ```sql ALTER TABLE label_pdf_cache ADD COLUMN ParseDurationMs INT DEFAULT NULL COMMENT 'PDF解析花费的时间(毫秒)' AFTER BarcodeExtractTime; ``` --- ## 💻 代码修改 ### 1. 实体类修改 (LabelPdfCache.cs) ```csharp /// /// PDF解析花费的时间(毫秒) /// [SugarColumn(IsNullable = true)] public int? ParseDurationMs { get; set; } ``` ### 2. 时间记录逻辑 (LabelPdfCacheService.cs) **方法开始处**: ```csharp private async Task ProcessSingleCacheTask(string waybillNumber, LabelPdfCache? existingCache = null) { var startTime = DateTime.UtcNow; // ⭐ 记录开始时间 try { // 处理逻辑... } } ``` **计算解析时间**: ```csharp var parseDurationMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds; ``` **在各个阶段记录**: ```csharp // 验证失败时 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**: ```csharp public async Task 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**: ```csharp 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. 性能分析 ```sql -- 查询平均解析时间 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. 监控告警 ```sql -- 找出解析时间超过5秒的记录(可能表示性能问题) SELECT NeutralWaybillNumber, ParseDurationMs FROM label_pdf_cache WHERE ParseDurationMs > 5000 AND Status = 1; ``` ### 3. 优化评估 ```sql -- 按日期统计平均解析时间的变化趋势 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脚本执行步骤 ### 新环境部署 ```bash # 执行完整建表脚本 mysql> source CreateLabelPdfCacheTable_Complete.sql; # 验证字段 mysql> DESC label_pdf_cache; # 应该看到 ParseDurationMs INT 字段 ``` ### 现有环境升级 ```bash # 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. **查询建议** ```sql -- 如果频繁按解析时间范围查询,可添加索引 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. 用于性能分析和优化