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

294 lines
7.1 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
/// <summary>
/// PDF解析花费的时间毫秒
/// </summary>
[SugarColumn(IsNullable = true)]
public int? ParseDurationMs { get; set; }
```
### 2. 时间记录逻辑 (LabelPdfCacheService.cs)
**方法开始处**
```csharp
private async Task<bool> 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<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**
```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. 用于性能分析和优化