上传源代码版本
This commit is contained in:
343
.trae/specs/usps-auto-bag/checklist.md
Normal file
343
.trae/specs/usps-auto-bag/checklist.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# USPS 自动集包开发检查清单
|
||||
|
||||
## 开发前准备
|
||||
|
||||
- [ ] 已阅读 USPS自动集包需求文档.md
|
||||
- [ ] 已阅读 spec.md 接口规范
|
||||
- [ ] 已阅读 tasks.md 任务分解
|
||||
- [ ] 已确认数据库表结构(label_replace_requests, bag_tag_waybills)
|
||||
- [ ] 已确认 USPS 运单号识别规则
|
||||
|
||||
---
|
||||
|
||||
## 阶段一:模型定义
|
||||
|
||||
### 任务 1.1:创建任务状态模型
|
||||
**文件**:`src/MDL/Models/AutoPackTaskStatus.cs`
|
||||
|
||||
- [ ] 创建 `AutoPackTaskStatus` 类
|
||||
- [ ] TaskId (string)
|
||||
- [ ] TagNumber (string)
|
||||
- [ ] Status (string)
|
||||
- [ ] TotalCount (int)
|
||||
- [ ] ProcessedCount (int)
|
||||
- [ ] SuccessCount (int)
|
||||
- [ ] FailedCount (int)
|
||||
- [ ] CurrentWaybill (string)
|
||||
- [ ] StartTime (DateTime)
|
||||
- [ ] EndTime (DateTime?)
|
||||
- [ ] FailedItems (List<AutoPackFailedItem>)
|
||||
- [ ] CancellationTokenSource (CancellationTokenSource)
|
||||
- [ ] 创建 `AutoPackFailedItem` 类
|
||||
- [ ] WaybillNumber (string)
|
||||
- [ ] ErrorCode (int)
|
||||
- [ ] ErrorMessage (string)
|
||||
- [ ] 添加必要的 using 语句
|
||||
- [ ] 编译通过
|
||||
|
||||
### 任务 1.2:创建请求/响应 DTO
|
||||
**文件**:`src/MDL/Models/AutoPackRequest.cs`
|
||||
|
||||
- [ ] 创建 `StartAutoPackRequest` 类
|
||||
- [ ] TagNumber (string)
|
||||
- [ ] Creator (string, 默认 "system")
|
||||
- [ ] 创建 `StartAutoPackResponse` 类
|
||||
- [ ] TaskId (string)
|
||||
- [ ] TagNumber (string)
|
||||
- [ ] Status (string)
|
||||
- [ ] TotalCount (int)
|
||||
- [ ] Message (string)
|
||||
- [ ] 创建 `AutoPackProgressResponse` 类
|
||||
- [ ] TaskId (string)
|
||||
- [ ] TagNumber (string)
|
||||
- [ ] Status (string)
|
||||
- [ ] TotalCount (int)
|
||||
- [ ] ProcessedCount (int)
|
||||
- [ ] SuccessCount (int)
|
||||
- [ ] FailedCount (int)
|
||||
- [ ] CurrentWaybill (string)
|
||||
- [ ] Progress (int)
|
||||
- [ ] Message (string)
|
||||
- [ ] StartTime (DateTime)
|
||||
- [ ] EstimatedEndTime (DateTime?)
|
||||
- [ ] FailedItems (List<AutoPackFailedItem>)
|
||||
- [ ] 创建 `AutoPackResultResponse` 类
|
||||
- [ ] TaskId (string)
|
||||
- [ ] TagNumber (string)
|
||||
- [ ] Status (string)
|
||||
- [ ] TotalCount (int)
|
||||
- [ ] SuccessCount (int)
|
||||
- [ ] FailedCount (int)
|
||||
- [ ] StartTime (DateTime)
|
||||
- [ ] EndTime (DateTime?)
|
||||
- [ ] Duration (int)
|
||||
- [ ] FailedItems (List<AutoPackFailedItem>)
|
||||
- [ ] 编译通过
|
||||
|
||||
---
|
||||
|
||||
## 阶段二:数据访问层
|
||||
|
||||
### 任务 2.1:扩展 Repository 接口
|
||||
**文件**:`src/DAL/Interfaces/IBagTagRepository.cs`
|
||||
|
||||
- [ ] 添加 `GetEligibleUspsWaybillsAsync(DateTime cutoffTime)` 方法声明
|
||||
- [ ] 添加 `GetEligibleUspsWaybillCountAsync(DateTime cutoffTime)` 方法声明
|
||||
- [ ] 添加 XML 注释
|
||||
- [ ] 编译通过
|
||||
|
||||
### 任务 2.2:实现 Repository 方法
|
||||
**文件**:`src/DAL/Repositories/BagTagRepository.cs`
|
||||
|
||||
- [ ] 实现 `GetEligibleUspsWaybillsAsync` 方法
|
||||
- [ ] 使用 SqlSugar 执行 SQL 查询
|
||||
- [ ] 查询条件:ReplaceStatus = 'Y'
|
||||
- [ ] 查询条件:CreatedAt >= cutoffTime
|
||||
- [ ] 查询条件:FinalMileTrackingNumber IS NOT NULL AND != ''
|
||||
- [ ] 查询条件:未关联到 bag_tag_waybills(LEFT JOIN + IS NULL)
|
||||
- [ ] 查询条件:USPS 格式(REGEXP '^(92|93|94|95)')
|
||||
- [ ] 查询条件:USPS 格式(REGEXP '^420[0-9]{5}(92|93|94|95)')
|
||||
- [ ] 查询条件:USPS 格式(REGEXP '^420[0-9]{9}(92|93|94|95)')
|
||||
- [ ] ORDER BY CreatedAt ASC
|
||||
- [ ] 返回 List<string>
|
||||
- [ ] 实现 `GetEligibleUspsWaybillCountAsync` 方法
|
||||
- [ ] 使用 COUNT(*) 查询
|
||||
- [ ] 相同的 WHERE 条件
|
||||
- [ ] 返回 int
|
||||
- [ ] 添加异常处理
|
||||
- [ ] 编译通过
|
||||
|
||||
---
|
||||
|
||||
## 阶段三:业务逻辑层
|
||||
|
||||
### 任务 3.1:扩展 Service 接口
|
||||
**文件**:`src/BLL/Interfaces/IBagTagService.cs`
|
||||
|
||||
- [ ] 添加 `StartAutoPackAsync(string tagNumber, string creator)` 方法声明
|
||||
- [ ] 添加 `GetAutoPackProgressAsync(string taskId)` 方法声明
|
||||
- [ ] 添加 `CancelAutoPackAsync(string taskId)` 方法声明
|
||||
- [ ] 添加 `GetAutoPackResultAsync(string taskId)` 方法声明
|
||||
- [ ] 添加 XML 注释
|
||||
- [ ] 编译通过
|
||||
|
||||
### 任务 3.2:实现任务启动逻辑
|
||||
**文件**:`src/BLL/Services/BagTagService.cs`
|
||||
|
||||
- [ ] 注入 ICacheService(如未注入)
|
||||
- [ ] 实现 `StartAutoPackAsync` 方法
|
||||
- [ ] 验证袋牌存在(GetByTagNumberAsync)
|
||||
- [ ] 验证袋牌状态为 "Opened"
|
||||
- [ ] 计算 cutoffTime(DateTime.UtcNow.AddHours(-84))
|
||||
- [ ] 调用 GetEligibleUspsWaybillsAsync 获取包裹列表
|
||||
- [ ] 检查包裹数量 > 0
|
||||
- [ ] 生成唯一 TaskId(格式:task_{timestamp}_{guid})
|
||||
- [ ] 创建 AutoPackTaskStatus 对象
|
||||
- [ ] 存入缓存(key: "autopack:{taskId}", 过期时间 60 分钟)
|
||||
- [ ] 启动后台任务(Task.Run)
|
||||
- [ ] 返回 StartAutoPackResponse
|
||||
- [ ] 添加异常处理
|
||||
- [ ] 编译通过
|
||||
|
||||
### 任务 3.3:实现自动集包执行逻辑
|
||||
**文件**:`src/BLL/Services/BagTagService.cs`
|
||||
|
||||
- [ ] 创建 `ExecuteAutoPackAsync` 私有方法
|
||||
- [ ] 参数:taskId, tagNumber, waybills, creator
|
||||
- [ ] 创建 Random 对象
|
||||
- [ ] 遍历 waybills
|
||||
- [ ] 从缓存获取任务状态
|
||||
- [ ] 检查 CancellationTokenSource.IsCancellationRequested
|
||||
- [ ] 更新 CurrentWaybill
|
||||
- [ ] 调用 AssociateWaybillAsync 进行关联
|
||||
- [ ] 更新 SuccessCount 或 FailedCount
|
||||
- [ ] 记录失败信息到 FailedItems
|
||||
- [ ] 更新 ProcessedCount
|
||||
- [ ] 保存任务状态到缓存
|
||||
- [ ] 随机延迟 2-4 秒(最后一个不延迟)
|
||||
- [ ] 任务完成处理
|
||||
- [ ] 设置 Status(completed/cancelled)
|
||||
- [ ] 设置 EndTime
|
||||
- [ ] 清空 CurrentWaybill
|
||||
- [ ] 保存最终状态到缓存
|
||||
- [ ] 实现 `GetAutoPackProgressAsync` 方法
|
||||
- [ ] 从缓存获取任务状态
|
||||
- [ ] 计算 Progress 百分比
|
||||
- [ ] 计算 EstimatedEndTime
|
||||
- [ ] 映射到 AutoPackProgressResponse
|
||||
- [ ] 返回响应
|
||||
- [ ] 实现 `CancelAutoPackAsync` 方法
|
||||
- [ ] 从缓存获取任务状态
|
||||
- [ ] 检查任务是否存在且状态为 processing
|
||||
- [ ] 调用 CancellationTokenSource.Cancel()
|
||||
- [ ] 更新状态为 cancelled
|
||||
- [ ] 保存到缓存
|
||||
- [ ] 返回 bool
|
||||
- [ ] 实现 `GetAutoPackResultAsync` 方法
|
||||
- [ ] 从缓存获取任务状态
|
||||
- [ ] 映射到 AutoPackResultResponse
|
||||
- [ ] 计算 Duration
|
||||
- [ ] 返回响应
|
||||
- [ ] 添加异常处理
|
||||
- [ ] 编译通过
|
||||
|
||||
---
|
||||
|
||||
## 阶段四:接口层
|
||||
|
||||
### 任务 4.1:实现控制器方法
|
||||
**文件**:`src/CONTROLLER/Controllers/BagTagController.cs`
|
||||
|
||||
- [ ] 添加 `StartAutoPack` 方法
|
||||
- [ ] HTTP POST 路由:"auto-pack/start"
|
||||
- [ ] 参数:[FromBody] StartAutoPackRequest
|
||||
- [ ] 调用 _bagTagService.StartAutoPackAsync
|
||||
- [ ] 返回统一响应格式 { code, message, data }
|
||||
- [ ] 异常处理
|
||||
- [ ] 添加 `GetAutoPackProgress` 方法
|
||||
- [ ] HTTP GET 路由:"auto-pack/progress/{taskId}"
|
||||
- [ ] 参数:string taskId
|
||||
- [ ] 调用 _bagTagService.GetAutoPackProgressAsync
|
||||
- [ ] 处理任务不存在的情况
|
||||
- [ ] 返回统一响应格式
|
||||
- [ ] 异常处理
|
||||
- [ ] 添加 `CancelAutoPack` 方法
|
||||
- [ ] HTTP POST 路由:"auto-pack/cancel/{taskId}"
|
||||
- [ ] 参数:string taskId
|
||||
- [ ] 调用 _bagTagService.CancelAutoPackAsync
|
||||
- [ ] 处理取消失败的情况
|
||||
- [ ] 返回统一响应格式
|
||||
- [ ] 异常处理
|
||||
- [ ] 添加 `GetAutoPackResult` 方法
|
||||
- [ ] HTTP GET 路由:"auto-pack/result/{taskId}"
|
||||
- [ ] 参数:string taskId
|
||||
- [ ] 调用 _bagTagService.GetAutoPackResultAsync
|
||||
- [ ] 处理任务不存在的情况
|
||||
- [ ] 返回统一响应格式
|
||||
- [ ] 异常处理
|
||||
- [ ] 编译通过
|
||||
|
||||
### 任务 4.2:注册依赖注入
|
||||
**文件**:`src/CONTROLLER/Program.cs`
|
||||
|
||||
- [ ] 检查 ICacheService 是否已注册
|
||||
- [ ] 检查 IBagTagService 是否已注册
|
||||
- [ ] 确认无需额外注册
|
||||
|
||||
---
|
||||
|
||||
## 阶段五:测试与优化
|
||||
|
||||
### 任务 5.1:编写单元测试
|
||||
|
||||
- [ ] 测试启动任务 - 袋牌不存在
|
||||
- [ ] 测试启动任务 - 袋牌未打开
|
||||
- [ ] 测试启动任务 - 无符合条件的包裹
|
||||
- [ ] 测试启动任务 - 成功启动
|
||||
- [ ] 测试查询进度 - 任务不存在
|
||||
- [ ] 测试查询进度 - 正常查询
|
||||
- [ ] 测试取消任务 - 任务不存在
|
||||
- [ ] 测试取消任务 - 任务已完成
|
||||
- [ ] 测试取消任务 - 正常取消
|
||||
- [ ] 测试自动集包执行 - 全部成功
|
||||
- [ ] 测试自动集包执行 - 部分失败
|
||||
- [ ] 测试自动集包执行 - 取消操作
|
||||
- [ ] 所有测试通过
|
||||
|
||||
### 任务 5.2:性能优化与联调
|
||||
|
||||
- [ ] SQL 查询性能优化
|
||||
- [ ] 检查 label_replace_requests 表索引(CreatedAt, ReplaceStatus, FinalMileTrackingNumber)
|
||||
- [ ] 检查 bag_tag_waybills 表索引(FinalMileTrackingNumber)
|
||||
- [ ] 缓存配置优化
|
||||
- [ ] 确认缓存过期时间合理(60分钟)
|
||||
- [ ] 并发控制
|
||||
- [ ] 确认同时只能有一个自动集包任务在运行(可选)
|
||||
- [ ] 异常处理完善
|
||||
- [ ] 数据库连接异常
|
||||
- [ ] 缓存访问异常
|
||||
- [ ] 关联操作异常
|
||||
- [ ] WinForm 联调
|
||||
- [ ] 前端能正常调用启动接口
|
||||
- [ ] 前端能正常轮询进度
|
||||
- [ ] 进度条实时更新
|
||||
- [ ] 取消功能正常
|
||||
- [ ] 大数据量测试(100+ 包裹)
|
||||
|
||||
---
|
||||
|
||||
## 代码审查清单
|
||||
|
||||
### 代码规范
|
||||
|
||||
- [ ] 命名规范符合项目标准
|
||||
- [ ] 方法添加 XML 注释
|
||||
- [ ] 复杂逻辑添加行内注释
|
||||
- [ ] 无死代码
|
||||
- [ ] 无 Console.WriteLine(使用 ILogger)
|
||||
|
||||
### 异常处理
|
||||
|
||||
- [ ] 所有异步方法有 try-catch
|
||||
- [ ] 异常信息不暴露敏感信息
|
||||
- [ ] 异常正确记录日志
|
||||
|
||||
### 性能
|
||||
|
||||
- [ ] 数据库查询使用参数化 SQL
|
||||
- [ ] 避免 N+1 查询问题
|
||||
- [ ] 缓存使用合理
|
||||
|
||||
### 安全
|
||||
|
||||
- [ ] 输入参数验证
|
||||
- [ ] 防止 SQL 注入
|
||||
- [ ] 权限检查(如需要)
|
||||
|
||||
---
|
||||
|
||||
## 部署检查清单
|
||||
|
||||
- [ ] 代码编译通过
|
||||
- [ ] 单元测试全部通过
|
||||
- [ ] 数据库迁移脚本(如需要)
|
||||
- [ ] 配置文件更新(如需要)
|
||||
- [ ] API 文档更新
|
||||
- [ ] 部署到测试环境
|
||||
- [ ] 测试环境验证通过
|
||||
- [ ] 部署到生产环境
|
||||
- [ ] 生产环境验证通过
|
||||
|
||||
---
|
||||
|
||||
## 文档检查清单
|
||||
|
||||
- [ ] spec.md 已更新(如有变更)
|
||||
- [ ] tasks.md 已更新(如有变更)
|
||||
- [ ] 接口文档已更新
|
||||
- [ ] 前端联调文档已提供
|
||||
|
||||
---
|
||||
|
||||
## 验收标准
|
||||
|
||||
### 功能验收
|
||||
|
||||
- [ ] 创建 USPS 袋牌后能自动触发集包(或手动触发接口)
|
||||
- [ ] 正确筛选符合条件的 USPS 包裹(84小时内、未集包、已换单)
|
||||
- [ ] 逐个关联包裹,间隔 2-4 秒
|
||||
- [ ] 实时反馈进度(总数、已处理数、成功数、失败数)
|
||||
- [ ] 支持取消操作
|
||||
- [ ] 记录操作日志
|
||||
|
||||
### 性能验收
|
||||
|
||||
- [ ] 100 个包裹处理时间 < 10 分钟
|
||||
- [ ] 前端轮询响应时间 < 100ms
|
||||
- [ ] 内存占用稳定,无内存泄漏
|
||||
|
||||
### 兼容性验收
|
||||
|
||||
- [ ] WinForm 前端正常调用
|
||||
- [ ] 接口响应格式符合规范
|
||||
- [ ] 错误码定义清晰
|
||||
439
.trae/specs/usps-auto-bag/spec.md
Normal file
439
.trae/specs/usps-auto-bag/spec.md
Normal file
@@ -0,0 +1,439 @@
|
||||
# USPS 自动集包接口规格说明
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
根据 USPS自动集包需求文档,实现 USPS 尾程包裹的自动集包功能。当用户在系统中创建 USPS 袋牌后,系统自动筛选符合条件的包裹并逐个关联到该袋牌。
|
||||
|
||||
**核心特点**:
|
||||
- 前端使用 WinForm,需要实时反馈关联进度
|
||||
- 采用异步任务 + 进度查询的设计模式
|
||||
- 每次关联间隔 2-4 秒随机延迟,模拟人工操作
|
||||
|
||||
---
|
||||
|
||||
## 2. 接口设计
|
||||
|
||||
### 2.1 启动自动集包任务
|
||||
|
||||
**接口路径**:`POST /api/bagtag/auto-pack/start`
|
||||
|
||||
**请求参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| tagNumber | string | 是 | USPS 袋牌号 |
|
||||
| creator | string | 否 | 操作人,默认为 "system" |
|
||||
|
||||
**请求示例**:
|
||||
```json
|
||||
{
|
||||
"tagNumber": "USPS202604031200010001",
|
||||
"creator": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
**响应结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"taskId": "task_20260403120001_abc123",
|
||||
"tagNumber": "USPS202604031200010001",
|
||||
"status": "processing",
|
||||
"totalCount": 50,
|
||||
"message": "自动集包任务已启动"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 1001,
|
||||
"message": "Bag tag not found or not in opened status",
|
||||
"data": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 查询自动集包进度
|
||||
|
||||
**接口路径**:`GET /api/bagtag/auto-pack/progress/{taskId}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| taskId | string | 是 | 任务ID |
|
||||
|
||||
**响应结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"taskId": "task_20260403120001_abc123",
|
||||
"tagNumber": "USPS202604031200010001",
|
||||
"status": "processing",
|
||||
"totalCount": 50,
|
||||
"processedCount": 25,
|
||||
"successCount": 24,
|
||||
"failedCount": 1,
|
||||
"currentWaybill": "9201234567890123456789",
|
||||
"progress": 50,
|
||||
"message": "正在处理第 25/50 个包裹",
|
||||
"startTime": "2026-04-03T12:00:01Z",
|
||||
"estimatedEndTime": "2026-04-03T12:03:30Z",
|
||||
"failedItems": [
|
||||
{
|
||||
"waybillNumber": "9201234567890123456788",
|
||||
"errorCode": 10035,
|
||||
"errorMessage": "Waybill is already associated with another bag tag"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**状态说明**:
|
||||
|
||||
| 状态值 | 说明 |
|
||||
|--------|------|
|
||||
| pending | 等待处理 |
|
||||
| processing | 处理中 |
|
||||
| completed | 已完成 |
|
||||
| failed | 失败/异常终止 |
|
||||
| cancelled | 已取消 |
|
||||
|
||||
---
|
||||
|
||||
### 2.3 取消自动集包任务
|
||||
|
||||
**接口路径**:`POST /api/bagtag/auto-pack/cancel/{taskId}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| taskId | string | 是 | 任务ID |
|
||||
|
||||
**响应结构**:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "Task cancelled successfully",
|
||||
"data": {
|
||||
"taskId": "task_20260403120001_abc123",
|
||||
"status": "cancelled",
|
||||
"processedCount": 25,
|
||||
"successCount": 24,
|
||||
"failedCount": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 获取任务结果
|
||||
|
||||
**接口路径**:`GET /api/bagtag/auto-pack/result/{taskId}`
|
||||
|
||||
**路径参数**:
|
||||
|
||||
| 参数名 | 类型 | 必选 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| taskId | string | 是 | 任务ID |
|
||||
|
||||
**响应结构**(任务完成后):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"taskId": "task_20260403120001_abc123",
|
||||
"tagNumber": "USPS202604031200010001",
|
||||
"status": "completed",
|
||||
"totalCount": 50,
|
||||
"successCount": 48,
|
||||
"failedCount": 2,
|
||||
"startTime": "2026-04-03T12:00:01Z",
|
||||
"endTime": "2026-04-03T12:03:45Z",
|
||||
"duration": 224,
|
||||
"failedItems": [
|
||||
{
|
||||
"waybillNumber": "9201234567890123456788",
|
||||
"errorCode": 10035,
|
||||
"errorMessage": "Waybill is already associated with another bag tag"
|
||||
},
|
||||
{
|
||||
"waybillNumber": "9201234567890123456787",
|
||||
"errorCode": 10033,
|
||||
"errorMessage": "Channel does not match"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 业务逻辑
|
||||
|
||||
### 3.1 筛选条件
|
||||
|
||||
系统仅自动关联同时满足以下条件的包裹:
|
||||
|
||||
1. **尾程渠道为 USPS**
|
||||
- 通过运单号识别渠道(92/93/94/95 开头,或 420+邮编+92/93/94/95)
|
||||
|
||||
2. **当前尚未被集包**
|
||||
- 未关联到任何袋牌(bag_tag_waybills 表中不存在)
|
||||
|
||||
3. **已完成换单**
|
||||
- label_replace_requests 表中存在对应记录
|
||||
- 换单状态为 "Y"(正常换单)
|
||||
|
||||
4. **换单时间在前 3 天内(84小时)**
|
||||
- 以创建袋牌时间为基准
|
||||
- CreatedAt >= 当前时间 - 84小时
|
||||
|
||||
### 3.2 处理流程
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 1. 接收启动请求 (POST /api/bagtag/auto-pack/start) │
|
||||
│ - 验证袋牌存在且状态为 Opened │
|
||||
│ - 生成唯一任务ID │
|
||||
│ - 查询符合条件的包裹总数 │
|
||||
│ - 返回任务ID给前端 │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 2. 异步执行自动集包任务 │
|
||||
│ - 创建后台任务 │
|
||||
│ - 逐个关联包裹 │
|
||||
│ - 每单间隔 2-4 秒随机延迟 │
|
||||
│ - 实时更新任务进度状态 │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 3. 前端轮询进度 (GET /api/bagtag/auto-pack/progress/{id}) │
|
||||
│ - 建议轮询间隔:1-2 秒 │
|
||||
│ - 显示进度条、当前处理单号、成功/失败数量 │
|
||||
│ - 可实时取消任务 │
|
||||
└──────────────────────────┬──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 4. 任务完成 │
|
||||
│ - 返回最终结果 │
|
||||
│ - 记录操作日志 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.3 关联处理逻辑
|
||||
|
||||
对于每个符合条件的包裹:
|
||||
|
||||
1. 调用 `AssociateWaybillAsync` 方法进行关联
|
||||
2. 记录关联结果(成功/失败)
|
||||
3. 更新任务进度状态
|
||||
4. 生成 2-4 秒随机延迟
|
||||
5. 处理下一个包裹
|
||||
|
||||
### 3.4 任务状态管理
|
||||
|
||||
任务状态存储在内存缓存中(ICacheService),包含:
|
||||
|
||||
```csharp
|
||||
public class AutoPackTaskStatus
|
||||
{
|
||||
public string TaskId { get; set; }
|
||||
public string TagNumber { get; set; }
|
||||
public string Status { get; set; } // pending/processing/completed/failed/cancelled
|
||||
public int TotalCount { get; set; }
|
||||
public int ProcessedCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public string CurrentWaybill { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public List<FailedItem> FailedItems { get; set; }
|
||||
public CancellationTokenSource CancellationTokenSource { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 技术实现
|
||||
|
||||
### 4.1 新增模型
|
||||
|
||||
**AutoPackTaskStatus**(任务状态模型)
|
||||
- 位置:`MDL/Models/AutoPackTaskStatus.cs`
|
||||
|
||||
**AutoPackRequest**(启动请求模型)
|
||||
- 位置:`MDL/Models/BagTagRequest.cs`(扩展)
|
||||
|
||||
### 4.2 接口定义
|
||||
|
||||
**IBagTagService** 扩展:
|
||||
```csharp
|
||||
// 启动自动集包任务
|
||||
Task<AutoPackTaskResult> StartAutoPackAsync(string tagNumber, string creator);
|
||||
|
||||
// 查询任务进度
|
||||
Task<AutoPackTaskStatus> GetAutoPackProgressAsync(string taskId);
|
||||
|
||||
// 取消任务
|
||||
Task<bool> CancelAutoPackAsync(string taskId);
|
||||
|
||||
// 获取任务结果
|
||||
Task<AutoPackTaskStatus> GetAutoPackResultAsync(string taskId);
|
||||
```
|
||||
|
||||
**IBagTagRepository** 扩展:
|
||||
```csharp
|
||||
// 查询符合条件的 USPS 包裹
|
||||
Task<List<string>> GetEligibleUspsWaybillsAsync(string tagNumber, DateTime cutoffTime);
|
||||
|
||||
// 获取符合条件的包裹数量
|
||||
Task<int> GetEligibleUspsWaybillCountAsync(string tagNumber, DateTime cutoffTime);
|
||||
```
|
||||
|
||||
### 4.3 控制器实现
|
||||
|
||||
在 `BagTagController` 中添加:
|
||||
- `POST /api/bagtag/auto-pack/start`
|
||||
- `GET /api/bagtag/auto-pack/progress/{taskId}`
|
||||
- `POST /api/bagtag/auto-pack/cancel/{taskId}`
|
||||
- `GET /api/bagtag/auto-pack/result/{taskId}`
|
||||
|
||||
---
|
||||
|
||||
## 5. 数据库查询
|
||||
|
||||
### 5.1 查询符合条件的包裹
|
||||
|
||||
```sql
|
||||
SELECT DISTINCT
|
||||
l.FinalMileTrackingNumber
|
||||
FROM label_replace_requests l
|
||||
LEFT JOIN bag_tag_waybills b ON l.FinalMileTrackingNumber = b.FinalMileTrackingNumber
|
||||
WHERE
|
||||
l.ReplaceStatus = 'Y'
|
||||
AND l.CreatedAt >= @CutoffTime
|
||||
AND l.FinalMileTrackingNumber IS NOT NULL
|
||||
AND l.FinalMileTrackingNumber != ''
|
||||
AND b.Id IS NULL -- 未关联到任何袋牌
|
||||
AND (
|
||||
-- USPS 运单号格式:92/93/94/95 开头
|
||||
l.FinalMileTrackingNumber REGEXP '^(92|93|94|95)'
|
||||
-- 或 420+邮编+92/93/94/95 开头
|
||||
OR l.FinalMileTrackingNumber REGEXP '^420[0-9]{5}(92|93|94|95)'
|
||||
OR l.FinalMileTrackingNumber REGEXP '^420[0-9]{9}(92|93|94|95)'
|
||||
)
|
||||
ORDER BY l.CreatedAt ASC
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 错误码定义
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 0 | 成功 |
|
||||
| 1001 | 袋牌不存在或状态不正确 |
|
||||
| 1002 | 任务不存在 |
|
||||
| 1003 | 任务已取消 |
|
||||
| 1004 | 任务已完成 |
|
||||
| 1005 | 无符合条件的包裹 |
|
||||
| 10031 | 袋牌不存在 |
|
||||
| 10032 | 袋牌未打开 |
|
||||
| 10033 | 渠道不匹配 |
|
||||
| 10035 | 运单已关联到其他袋牌 |
|
||||
| 9999 | 系统错误 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 前端集成建议
|
||||
|
||||
### 7.1 WinForm 调用流程
|
||||
|
||||
```csharp
|
||||
// 1. 启动自动集包
|
||||
var response = await httpClient.PostAsJsonAsync("/api/bagtag/auto-pack/start",
|
||||
new { tagNumber = "USPS202604031200010001", creator = "admin" });
|
||||
var result = await response.Content.ReadFromJsonAsync<ApiResponse<AutoPackResult>>();
|
||||
var taskId = result.Data.TaskId;
|
||||
|
||||
// 2. 轮询进度
|
||||
timer = new Timer(async _ => {
|
||||
var progressResponse = await httpClient.GetAsync($"/api/bagtag/auto-pack/progress/{taskId}");
|
||||
var progress = await progressResponse.Content.ReadFromJsonAsync<ApiResponse<AutoPackProgress>>();
|
||||
|
||||
// 更新UI:进度条、当前单号、成功/失败数
|
||||
UpdateUI(progress.Data);
|
||||
|
||||
if (progress.Data.Status == "completed" || progress.Data.Status == "failed")
|
||||
{
|
||||
timer.Stop();
|
||||
ShowResult(progress.Data);
|
||||
}
|
||||
}, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
|
||||
|
||||
// 3. 取消任务(用户点击取消按钮)
|
||||
await httpClient.PostAsync($"/api/bagtag/auto-pack/cancel/{taskId}", null);
|
||||
```
|
||||
|
||||
### 7.2 UI 展示建议
|
||||
|
||||
- **进度条**:显示 `processedCount / totalCount` 百分比
|
||||
- **当前处理**:显示 `currentWaybill` 单号
|
||||
- **统计信息**:成功数、失败数、剩余数
|
||||
- **预计完成时间**:根据当前速度计算
|
||||
- **失败列表**: expandable 面板显示失败明细
|
||||
|
||||
---
|
||||
|
||||
## 8. 性能考虑
|
||||
|
||||
1. **异步处理**:使用 `Task.Run` 或后台服务执行集包任务
|
||||
2. **缓存进度**:使用 `ICacheService` 存储任务状态,避免数据库压力
|
||||
3. **批量查询**:一次性查询所有符合条件的包裹,避免多次数据库访问
|
||||
4. **延迟控制**:2-4 秒随机延迟,避免对系统造成过大压力
|
||||
|
||||
---
|
||||
|
||||
## 9. 安全考虑
|
||||
|
||||
1. **袋牌状态验证**:确保袋牌处于 Opened 状态才能自动集包
|
||||
2. **渠道匹配**:自动识别运单号渠道,确保与袋牌渠道一致
|
||||
3. **重复关联检查**:避免将已关联的包裹再次关联
|
||||
4. **任务超时**:设置任务最大执行时间(如 30 分钟),超时自动终止
|
||||
|
||||
---
|
||||
|
||||
## 10. 日志记录
|
||||
|
||||
每个包裹关联操作记录订单日志:
|
||||
|
||||
```csharp
|
||||
await _orderLogService.RecordOrderLogAsync(
|
||||
neutralWaybillNumber: string.Empty,
|
||||
finalMileTrackingNumber: waybillNumber,
|
||||
operationType: OrderLogOperationType.PACK,
|
||||
operationResult: success ? OrderLogOperationResult.SUCCESS : OrderLogOperationResult.FAILED,
|
||||
operationDescription: $"自动集包到袋牌 {tagNumber}: {(success ? "成功" : errorMessage)}",
|
||||
@operator: "system_auto"
|
||||
);
|
||||
```
|
||||
627
.trae/specs/usps-auto-bag/tasks.md
Normal file
627
.trae/specs/usps-auto-bag/tasks.md
Normal file
@@ -0,0 +1,627 @@
|
||||
# USPS 自动集包开发任务分解
|
||||
|
||||
## 任务概览
|
||||
|
||||
| 阶段 | 任务数 | 预计工时 |
|
||||
| ------ | ------ | ------- |
|
||||
| 模型定义 | 2 | 2h |
|
||||
| 数据访问层 | 2 | 3h |
|
||||
| 业务逻辑层 | 3 | 5h |
|
||||
| 接口层 | 2 | 3h |
|
||||
| 测试与优化 | 2 | 3h |
|
||||
| **总计** | **11** | **16h** |
|
||||
|
||||
***
|
||||
|
||||
## 阶段一:模型定义
|
||||
|
||||
### 任务 1.1:创建任务状态模型
|
||||
|
||||
**文件**:`src/MDL/Models/AutoPackTaskStatus.cs`
|
||||
|
||||
**内容**:
|
||||
|
||||
```csharp
|
||||
namespace MDL.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动集包任务状态
|
||||
/// </summary>
|
||||
public class AutoPackTaskStatus
|
||||
{
|
||||
public string TaskId { get; set; }
|
||||
public string TagNumber { get; set; }
|
||||
public string Status { get; set; } // pending/processing/completed/failed/cancelled
|
||||
public int TotalCount { get; set; }
|
||||
public int ProcessedCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public string CurrentWaybill { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public List<AutoPackFailedItem> FailedItems { get; set; } = new();
|
||||
public CancellationTokenSource CancellationTokenSource { get; set; }
|
||||
}
|
||||
|
||||
public class AutoPackFailedItem
|
||||
{
|
||||
public string WaybillNumber { get; set; }
|
||||
public int ErrorCode { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 模型包含所有必要字段
|
||||
- [ ] 字段命名符合项目规范
|
||||
- [ ] 支持 JSON 序列化
|
||||
|
||||
***
|
||||
|
||||
### 任务 1.2:创建请求/响应 DTO
|
||||
|
||||
**文件**:`src/MDL/Models/AutoPackRequest.cs`
|
||||
|
||||
**内容**:
|
||||
|
||||
```csharp
|
||||
namespace MDL.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动自动集包请求
|
||||
/// </summary>
|
||||
public class StartAutoPackRequest
|
||||
{
|
||||
public string TagNumber { get; set; }
|
||||
public string Creator { get; set; } = "system";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动自动集包响应
|
||||
/// </summary>
|
||||
public class StartAutoPackResponse
|
||||
{
|
||||
public string TaskId { get; set; }
|
||||
public string TagNumber { get; set; }
|
||||
public string Status { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动集包进度响应
|
||||
/// </summary>
|
||||
public class AutoPackProgressResponse
|
||||
{
|
||||
public string TaskId { get; set; }
|
||||
public string TagNumber { get; set; }
|
||||
public string Status { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int ProcessedCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public string CurrentWaybill { get; set; }
|
||||
public int Progress { get; set; }
|
||||
public string Message { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime? EstimatedEndTime { get; set; }
|
||||
public List<AutoPackFailedItem> FailedItems { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自动集包结果响应
|
||||
/// </summary>
|
||||
public class AutoPackResultResponse
|
||||
{
|
||||
public string TaskId { get; set; }
|
||||
public string TagNumber { get; set; }
|
||||
public string Status { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int SuccessCount { get; set; }
|
||||
public int FailedCount { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public int Duration { get; set; }
|
||||
public List<AutoPackFailedItem> FailedItems { get; set; }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 包含 Start、Progress、Result 三种响应 DTO
|
||||
- [ ] 字段与 spec.md 定义一致
|
||||
- [ ] 支持前端 WinForm 调用
|
||||
|
||||
***
|
||||
|
||||
## 阶段二:数据访问层
|
||||
|
||||
### 任务 2.1:扩展 Repository 接口
|
||||
|
||||
**文件**:`src/DAL/Interfaces/IBagTagRepository.cs`
|
||||
|
||||
**新增方法**:
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 查询符合条件的 USPS 包裹
|
||||
/// </summary>
|
||||
Task<List<string>> GetEligibleUspsWaybillsAsync(DateTime cutoffTime);
|
||||
|
||||
/// <summary>
|
||||
/// 获取符合条件的包裹数量
|
||||
/// </summary>
|
||||
Task<int> GetEligibleUspsWaybillCountAsync(DateTime cutoffTime);
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 接口定义符合项目规范
|
||||
- [ ] 参数设计合理(cutoffTime = 当前时间 - 84小时)
|
||||
|
||||
***
|
||||
|
||||
### 任务 2.2:实现 Repository 方法
|
||||
|
||||
**文件**:`src/DAL/Repositories/BagTagRepository.cs`
|
||||
|
||||
**实现要点**:
|
||||
|
||||
1. 使用 SqlSugar 执行 SQL 查询
|
||||
2. 查询条件:
|
||||
- ReplaceStatus = 'Y'
|
||||
- CreatedAt >= cutoffTime
|
||||
- FinalMileTrackingNumber 不为空
|
||||
- 未关联到 bag\_tag\_waybills
|
||||
- USPS 运单号格式(92/93/94/95 或 420+邮编+92/93/94/95)
|
||||
- 有一条换单成功的扫描记录
|
||||
|
||||
**SQL 参考**:
|
||||
|
||||
```sql
|
||||
SELECT DISTINCT
|
||||
l.FinalMileTrackingNumber
|
||||
FROM label_replace_requests l
|
||||
LEFT JOIN bag_tag_waybills b ON l.FinalMileTrackingNumber = b.FinalMileTrackingNumber
|
||||
WHERE
|
||||
l.ReplaceStatus = 'Y'
|
||||
AND l.CreatedAt >= @CutoffTime
|
||||
AND l.FinalMileTrackingNumber IS NOT NULL
|
||||
AND l.FinalMileTrackingNumber != ''
|
||||
AND b.Id IS NULL
|
||||
AND (
|
||||
l.FinalMileTrackingNumber REGEXP '^(92|93|94|95)'
|
||||
OR l.FinalMileTrackingNumber REGEXP '^420[0-9]{5}(92|93|94|95)'
|
||||
OR l.FinalMileTrackingNumber REGEXP '^420[0-9]{9}(92|93|94|95)'
|
||||
)
|
||||
ORDER BY l.CreatedAt ASC
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] SQL 查询性能优化(使用索引)
|
||||
- [ ] 正确处理 USPS 运单号格式
|
||||
- [ ] 返回结果按 CreatedAt 升序排列
|
||||
|
||||
***
|
||||
|
||||
## 阶段三:业务逻辑层
|
||||
|
||||
### 任务 3.1:扩展 Service 接口
|
||||
|
||||
**文件**:`src/BLL/Interfaces/IBagTagService.cs`
|
||||
|
||||
**新增方法**:
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 启动自动集包任务
|
||||
/// </summary>
|
||||
Task<StartAutoPackResponse> StartAutoPackAsync(string tagNumber, string creator);
|
||||
|
||||
/// <summary>
|
||||
/// 查询自动集包进度
|
||||
/// </summary>
|
||||
Task<AutoPackProgressResponse> GetAutoPackProgressAsync(string taskId);
|
||||
|
||||
/// <summary>
|
||||
/// 取消自动集包任务
|
||||
/// </summary>
|
||||
Task<bool> CancelAutoPackAsync(string taskId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取自动集包结果
|
||||
/// </summary>
|
||||
Task<AutoPackResultResponse> GetAutoPackResultAsync(string taskId);
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 接口定义完整
|
||||
- [ ] 返回类型与 DTO 对应
|
||||
|
||||
***
|
||||
|
||||
### 任务 3.2:实现任务启动逻辑
|
||||
|
||||
**文件**:`src/BLL/Services/BagTagService.cs`
|
||||
|
||||
**实现步骤**:
|
||||
|
||||
1. 验证袋牌存在且状态为 Opened
|
||||
2. 生成唯一任务ID(格式:`task_{timestamp}_{guid}`)
|
||||
3. 查询符合条件的包裹列表
|
||||
4. 如果无符合条件的包裹,返回错误
|
||||
5. 创建任务状态对象,存入缓存
|
||||
6. 启动后台任务执行自动集包
|
||||
7. 返回任务信息给前端
|
||||
|
||||
**关键代码**:
|
||||
|
||||
```csharp
|
||||
public async Task<StartAutoPackResponse> StartAutoPackAsync(string tagNumber, string creator)
|
||||
{
|
||||
// 1. 验证袋牌
|
||||
var tag = await _bagTagRepository.GetByTagNumberAsync(tagNumber);
|
||||
if (tag == null || tag.Status != "Opened")
|
||||
{
|
||||
throw new Exception("Bag tag not found or not in opened status");
|
||||
}
|
||||
|
||||
// 2. 查询符合条件的包裹
|
||||
var cutoffTime = DateTime.UtcNow.AddHours(-84);
|
||||
var waybills = await _bagTagRepository.GetEligibleUspsWaybillsAsync(cutoffTime);
|
||||
|
||||
if (waybills.Count == 0)
|
||||
{
|
||||
throw new Exception("No eligible waybills found");
|
||||
}
|
||||
|
||||
// 3. 创建任务
|
||||
var taskId = $"task_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
var taskStatus = new AutoPackTaskStatus
|
||||
{
|
||||
TaskId = taskId,
|
||||
TagNumber = tagNumber,
|
||||
Status = "processing",
|
||||
TotalCount = waybills.Count,
|
||||
ProcessedCount = 0,
|
||||
SuccessCount = 0,
|
||||
FailedCount = 0,
|
||||
StartTime = DateTime.UtcNow,
|
||||
FailedItems = new List<AutoPackFailedItem>(),
|
||||
CancellationTokenSource = new CancellationTokenSource()
|
||||
};
|
||||
|
||||
// 4. 保存任务状态到缓存
|
||||
await _cacheService.SetAsync($"autopack:{taskId}", taskStatus, 60); // 缓存60分钟
|
||||
|
||||
// 5. 启动后台任务
|
||||
_ = Task.Run(async () => await ExecuteAutoPackAsync(taskId, tagNumber, waybills, creator));
|
||||
|
||||
// 6. 返回响应
|
||||
return new StartAutoPackResponse
|
||||
{
|
||||
TaskId = taskId,
|
||||
TagNumber = tagNumber,
|
||||
Status = "processing",
|
||||
TotalCount = waybills.Count,
|
||||
Message = "自动集包任务已启动"
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 袋牌验证逻辑正确
|
||||
- [ ] 任务ID生成唯一
|
||||
- [ ] 任务状态正确存入缓存
|
||||
- [ ] 后台任务正确启动
|
||||
|
||||
***
|
||||
|
||||
### 任务 3.3:实现自动集包执行逻辑
|
||||
|
||||
**文件**:`src/BLL/Services/BagTagService.cs`
|
||||
|
||||
**实现步骤**:
|
||||
|
||||
1. 从缓存获取任务状态
|
||||
2. 遍历包裹列表逐个处理
|
||||
3. 每次处理前检查取消令牌
|
||||
4. 调用 AssociateWaybillAsync 进行关联
|
||||
5. 更新任务状态(成功/失败计数、当前单号)
|
||||
6. 记录订单日志
|
||||
7. 生成 2-4 秒随机延迟
|
||||
8. 处理完成后更新任务状态为 completed 或 failed
|
||||
|
||||
**关键代码**:
|
||||
|
||||
```csharp
|
||||
private async Task ExecuteAutoPackAsync(string taskId, string tagNumber, List<string> waybills, string creator)
|
||||
{
|
||||
var random = new Random();
|
||||
var cacheKey = $"autopack:{taskId}";
|
||||
|
||||
foreach (var waybill in waybills)
|
||||
{
|
||||
// 1. 获取任务状态
|
||||
var taskStatus = await _cacheService.GetAsync<AutoPackTaskStatus>(cacheKey);
|
||||
if (taskStatus == null || taskStatus.CancellationTokenSource?.IsCancellationRequested == true)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. 更新当前处理单号
|
||||
taskStatus.CurrentWaybill = waybill;
|
||||
await _cacheService.SetAsync(cacheKey, taskStatus, 60);
|
||||
|
||||
try
|
||||
{
|
||||
// 3. 执行关联
|
||||
var (success, errorCode, errorMessage) = await AssociateWaybillAsync(tagNumber, waybill, "system_auto");
|
||||
|
||||
if (success)
|
||||
{
|
||||
taskStatus.SuccessCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
taskStatus.FailedCount++;
|
||||
taskStatus.FailedItems.Add(new AutoPackFailedItem
|
||||
{
|
||||
WaybillNumber = waybill,
|
||||
ErrorCode = errorCode,
|
||||
ErrorMessage = errorMessage
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
taskStatus.FailedCount++;
|
||||
taskStatus.FailedItems.Add(new AutoPackFailedItem
|
||||
{
|
||||
WaybillNumber = waybill,
|
||||
ErrorCode = 9999,
|
||||
ErrorMessage = ex.Message
|
||||
});
|
||||
}
|
||||
|
||||
// 4. 更新进度
|
||||
taskStatus.ProcessedCount++;
|
||||
await _cacheService.SetAsync(cacheKey, taskStatus, 60);
|
||||
|
||||
// 5. 随机延迟 2-4 秒
|
||||
if (taskStatus.ProcessedCount < waybills.Count)
|
||||
{
|
||||
var delay = random.Next(2000, 4001);
|
||||
await Task.Delay(delay);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 任务完成
|
||||
var finalStatus = await _cacheService.GetAsync<AutoPackTaskStatus>(cacheKey);
|
||||
if (finalStatus != null)
|
||||
{
|
||||
finalStatus.Status = finalStatus.CancellationTokenSource?.IsCancellationRequested == true ? "cancelled" : "completed";
|
||||
finalStatus.EndTime = DateTime.UtcNow;
|
||||
finalStatus.CurrentWaybill = null;
|
||||
await _cacheService.SetAsync(cacheKey, finalStatus, 60);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 支持取消操作
|
||||
- [ ] 2-4 秒随机延迟正确实现
|
||||
- [ ] 进度实时更新到缓存
|
||||
- [ ] 失败记录完整保存
|
||||
- [ ] 订单日志正确记录
|
||||
|
||||
***
|
||||
|
||||
## 阶段四:接口层
|
||||
|
||||
### 任务 4.1:实现控制器方法
|
||||
|
||||
**文件**:`src/CONTROLLER/Controllers/BagTagController.cs`
|
||||
|
||||
**新增接口**:
|
||||
|
||||
1. **启动自动集包**
|
||||
|
||||
```csharp
|
||||
[HttpPost("auto-pack/start")]
|
||||
public async Task<IActionResult> StartAutoPack([FromBody] StartAutoPackRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _bagTagService.StartAutoPackAsync(request.TagNumber, request.Creator);
|
||||
return Ok(new { code = 0, message = "success", data = result });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Ok(new { code = 1001, message = ex.Message });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. **查询进度**
|
||||
|
||||
```csharp
|
||||
[HttpGet("auto-pack/progress/{taskId}")]
|
||||
public async Task<IActionResult> GetAutoPackProgress(string taskId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _bagTagService.GetAutoPackProgressAsync(taskId);
|
||||
if (result == null)
|
||||
{
|
||||
return Ok(new { code = 1002, message = "Task not found" });
|
||||
}
|
||||
return Ok(new { code = 0, message = "success", data = result });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Ok(new { code = 9999, message = ex.Message });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. **取消任务**
|
||||
|
||||
```csharp
|
||||
[HttpPost("auto-pack/cancel/{taskId}")]
|
||||
public async Task<IActionResult> CancelAutoPack(string taskId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var success = await _bagTagService.CancelAutoPackAsync(taskId);
|
||||
if (!success)
|
||||
{
|
||||
return Ok(new { code = 1002, message = "Task not found or already completed" });
|
||||
}
|
||||
return Ok(new { code = 0, message = "Task cancelled successfully" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Ok(new { code = 9999, message = ex.Message });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. **获取结果**
|
||||
|
||||
```csharp
|
||||
[HttpGet("auto-pack/result/{taskId}")]
|
||||
public async Task<IActionResult> GetAutoPackResult(string taskId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _bagTagService.GetAutoPackResultAsync(taskId);
|
||||
if (result == null)
|
||||
{
|
||||
return Ok(new { code = 1002, message = "Task not found" });
|
||||
}
|
||||
return Ok(new { code = 0, message = "success", data = result });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Ok(new { code = 9999, message = ex.Message });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 所有接口响应格式统一
|
||||
- [ ] 错误码与 spec.md 一致
|
||||
- [ ] 支持 JSONP(参考现有代码)
|
||||
|
||||
***
|
||||
|
||||
### 任务 4.2:注册依赖注入
|
||||
|
||||
**文件**:`src/CONTROLLER/Program.cs`(如有需要)
|
||||
|
||||
检查是否需要额外注册服务,通常现有 DI 配置已足够。
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 服务能正常解析
|
||||
- [ ] 无循环依赖
|
||||
|
||||
***
|
||||
|
||||
## 阶段五:测试与优化
|
||||
|
||||
### 任务 5.1:编写单元测试
|
||||
|
||||
**文件**:`test/...`(根据项目测试规范)
|
||||
|
||||
**测试用例**:
|
||||
|
||||
1. 启动任务 - 袋牌不存在
|
||||
2. 启动任务 - 袋牌未打开
|
||||
3. 启动任务 - 无符合条件的包裹
|
||||
4. 启动任务 - 成功启动
|
||||
5. 查询进度 - 任务不存在
|
||||
6. 查询进度 - 正常查询
|
||||
7. 取消任务 - 任务不存在
|
||||
8. 取消任务 - 任务已完成
|
||||
9. 取消任务 - 正常取消
|
||||
10. 自动集包执行 - 全部成功
|
||||
11. 自动集包执行 - 部分失败
|
||||
12. 自动集包执行 - 取消操作
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 核心逻辑覆盖率达到 80%+
|
||||
- [ ] 所有测试用例通过
|
||||
|
||||
***
|
||||
|
||||
### 任务 5.2:性能优化与联调
|
||||
|
||||
**优化项**:
|
||||
|
||||
1. SQL 查询性能优化(添加索引)
|
||||
2. 缓存过期时间调整
|
||||
3. 并发任务数限制
|
||||
4. 异常处理完善
|
||||
|
||||
**联调检查**:
|
||||
|
||||
1. WinForm 前端能正常调用接口
|
||||
2. 进度实时更新
|
||||
3. 取消功能正常
|
||||
4. 大数据量(1000+ 包裹)测试
|
||||
|
||||
**验收标准**:
|
||||
|
||||
- [ ] 100 个包裹处理时间 < 10 分钟
|
||||
- [ ] 内存占用稳定
|
||||
- [ ] 前端无卡顿
|
||||
|
||||
***
|
||||
|
||||
## 依赖关系图
|
||||
|
||||
```
|
||||
任务 1.1 (模型) ──┐
|
||||
├──→ 任务 2.1 (Repository接口) ──→ 任务 2.2 (Repository实现)
|
||||
任务 1.2 (DTO) ───┘ │
|
||||
│
|
||||
任务 3.1 (Service接口) ──────────────────────────────────────┤
|
||||
│ │
|
||||
↓ ↓
|
||||
任务 3.2 (启动逻辑) ─────────────→ 任务 3.3 (执行逻辑)
|
||||
│ │
|
||||
└────────────────┬─────────────────┘
|
||||
↓
|
||||
任务 4.1 (控制器)
|
||||
│
|
||||
↓
|
||||
任务 4.2 (DI注册)
|
||||
│
|
||||
↓
|
||||
┌───────────┴───────────┐
|
||||
↓ ↓
|
||||
任务 5.1 (单元测试) 任务 5.2 (优化联调)
|
||||
```
|
||||
|
||||
***
|
||||
|
||||
## 风险与应对
|
||||
|
||||
| 风险 | 影响 | 应对措施 |
|
||||
| ------------ | -- | -------------------------- |
|
||||
| 包裹数量过大导致任务超时 | 高 | 添加任务超时机制,支持断点续传 |
|
||||
| 缓存失效导致进度丢失 | 中 | 使用持久化存储(如数据库)保存任务状态 |
|
||||
| 并发任务过多 | 中 | 限制同时运行的自动集包任务数量 |
|
||||
| 前端轮询频率过高 | 低 | 建议轮询间隔 1-2 秒,或改用 WebSocket |
|
||||
|
||||
Reference in New Issue
Block a user