# 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 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 StartAutoPackAsync(string tagNumber, string creator); // 查询任务进度 Task GetAutoPackProgressAsync(string taskId); // 取消任务 Task CancelAutoPackAsync(string taskId); // 获取任务结果 Task GetAutoPackResultAsync(string taskId); ``` **IBagTagRepository** 扩展: ```csharp // 查询符合条件的 USPS 包裹 Task> GetEligibleUspsWaybillsAsync(string tagNumber, DateTime cutoffTime); // 获取符合条件的包裹数量 Task 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>(); 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>(); // 更新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" ); ```