# USPS 自动集包接口文档 ## 1. 接口概述 本文档提供 USPS 自动集包功能的 API 接口说明,供 WinForm 前端调用。接口采用 RESTful 风格,返回 JSON 格式数据。 ## 2. 接口列表 | 接口名称 | 请求方法 | 接口路径 | 功能描述 | |---------|---------|---------|----------| | 启动自动集包 | POST | `/api/bagtag/auto-pack/start` | 启动 USPS 自动集包任务 | | 查询集包进度 | GET | `/api/bagtag/auto-pack/progress/{taskId}` | 查询自动集包任务进度 | | 取消集包任务 | POST | `/api/bagtag/auto-pack/cancel/{taskId}` | 取消正在执行的集包任务 | | 获取集包结果 | GET | `/api/bagtag/auto-pack/result/{taskId}` | 获取自动集包任务结果 | ## 3. 接口详情 ### 3.1 启动自动集包 **请求 URL**:`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 } ``` ### 3.2 查询集包进度 **请求 URL**:`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" } ] } } ``` **失败响应**: ```json { "code": 1002, "message": "Task not found", "data": null } ``` ### 3.3 取消集包任务 **请求 URL**:`POST /api/bagtag/auto-pack/cancel/{taskId}` **路径参数**: | 参数名 | 类型 | 必填 | 描述 | |--------|------|------|------| | taskId | string | 是 | 任务 ID | **成功响应**: ```json { "code": 0, "message": "Task cancelled successfully", "data": null } ``` **失败响应**: ```json { "code": 1002, "message": "Task not found or already completed", "data": null } ``` ### 3.4 获取集包结果 **请求 URL**:`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" } ] } } ``` **失败响应**: ```json { "code": 1002, "message": "Task not found", "data": null } ``` ## 4. 响应状态码 | 状态码 | 说明 | |--------|------| | 0 | 成功 | | 1001 | 袋牌不存在或状态不正确 | | 1002 | 任务不存在或已完成 | | 1003 | 任务已取消 | | 1004 | 任务已完成 | | 1005 | 无符合条件的包裹 | | 10031 | 袋牌不存在 | | 10032 | 袋牌未打开 | | 10033 | 渠道不匹配 | | 10035 | 运单已关联到其他袋牌 | | 9999 | 系统错误 | ## 5. 数据结构 ### 5.1 StartAutoPackResponse ```csharp public class StartAutoPackResponse { public string TaskId { get; set; } // 任务ID public string TagNumber { get; set; } // 袋牌号 public string Status { get; set; } // 任务状态 public int TotalCount { get; set; } // 包裹总数 public string Message { get; set; } // 消息 } ``` ### 5.2 AutoPackProgressResponse ```csharp public class AutoPackProgressResponse { public string TaskId { get; set; } // 任务ID 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 FailedItems { get; set; } // 失败列表 } ``` ### 5.3 AutoPackResultResponse ```csharp public class AutoPackResultResponse { public string TaskId { get; set; } // 任务ID 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 FailedItems { get; set; } // 失败列表 } ``` ### 5.4 AutoPackFailedItem ```csharp public class AutoPackFailedItem { public string WaybillNumber { get; set; } // 运单号 public int ErrorCode { get; set; } // 错误码 public string ErrorMessage { get; set; } // 错误信息 } ``` ## 6. WinForm 前端调用示例 ### 6.1 启动自动集包 ```csharp private async Task StartAutoPack() { using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:5000"); // 替换为实际服务地址 var requestData = new { tagNumber = "USPS202604031200010001", creator = "admin" }; var response = await httpClient.PostAsJsonAsync("/api/bagtag/auto-pack/start", requestData); var result = await response.Content.ReadFromJsonAsync>(); if (result.code == 0) { // 保存任务ID用于后续查询 _currentTaskId = result.data.TaskId; _totalPackages = result.data.TotalCount; // 显示启动成功信息 MessageBox.Show($"自动集包任务已启动,共 {_totalPackages} 个包裹需要处理"); // 开始轮询进度 StartProgressPolling(); } else { MessageBox.Show($"启动失败: {result.message}"); } } ``` ### 6.2 轮询进度 ```csharp private System.Threading.Timer _progressTimer; private void StartProgressPolling() { // 每1秒查询一次进度 _progressTimer = new System.Threading.Timer(async _ => { await UpdateProgress(); }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); } private async Task UpdateProgress() { if (string.IsNullOrEmpty(_currentTaskId)) return; using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:5000"); var response = await httpClient.GetAsync($"/api/bagtag/auto-pack/progress/{_currentTaskId}"); var result = await response.Content.ReadFromJsonAsync>(); if (result.code == 0) { var progress = result.data; // 更新UI(需要Invoke到UI线程) this.Invoke((MethodInvoker)delegate { // 更新进度条 progressBar.Value = progress.Progress; // 更新状态标签 lblStatus.Text = $"状态: {progress.Status}"; lblProgress.Text = $"进度: {progress.ProcessedCount}/{progress.TotalCount} ({progress.Progress}%)"; lblSuccess.Text = $"成功: {progress.SuccessCount}"; lblFailed.Text = $"失败: {progress.FailedCount}"; lblCurrent.Text = $"当前: {progress.CurrentWaybill}"; // 更新预计完成时间 if (progress.EstimatedEndTime.HasValue) { lblEstimated.Text = $"预计完成: {progress.EstimatedEndTime.Value.ToString("yyyy-MM-dd HH:mm:ss")}"; } // 检查任务是否完成 if (progress.Status == "completed" || progress.Status == "failed" || progress.Status == "cancelled") { _progressTimer?.Change(Timeout.Infinite, Timeout.Infinite); MessageBox.Show($"任务已{progress.Status}!\n成功: {progress.SuccessCount}, 失败: {progress.FailedCount}"); } }); } } ``` ### 6.3 取消任务 ```csharp private async Task CancelTask() { if (string.IsNullOrEmpty(_currentTaskId)) return; using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:5000"); var response = await httpClient.PostAsync($"/api/bagtag/auto-pack/cancel/{_currentTaskId}", null); var result = await response.Content.ReadFromJsonAsync>(); if (result.code == 0) { _progressTimer?.Change(Timeout.Infinite, Timeout.Infinite); MessageBox.Show("任务已取消"); } else { MessageBox.Show($"取消失败: {result.message}"); } } ``` ### 6.4 获取最终结果 ```csharp private async Task GetTaskResult() { if (string.IsNullOrEmpty(_currentTaskId)) return; using var httpClient = new HttpClient(); httpClient.BaseAddress = new Uri("http://localhost:5000"); var response = await httpClient.GetAsync($"/api/bagtag/auto-pack/result/{_currentTaskId}"); var result = await response.Content.ReadFromJsonAsync>(); if (result.code == 0) { var taskResult = result.data; // 显示结果 var message = $"任务结果:\n" + $"状态: {taskResult.Status}\n" + $"总数: {taskResult.TotalCount}\n" + $"成功: {taskResult.SuccessCount}\n" + $"失败: {taskResult.FailedCount}\n" + $"耗时: {taskResult.Duration} 秒\n\n"; if (taskResult.FailedItems != null && taskResult.FailedItems.Count > 0) { message += "失败明细:\n"; foreach (var item in taskResult.FailedItems) { message += $"- {item.WaybillNumber}: {item.ErrorMessage}\n"; } } MessageBox.Show(message, "任务结果"); } else { MessageBox.Show($"获取结果失败: {result.message}"); } } ``` ## 7. 辅助类定义 ```csharp // API 响应通用结构 public class ApiResponse { public int code { get; set; } public string message { get; set; } public T data { get; set; } } // 启动请求 public class StartAutoPackRequest { public string TagNumber { get; set; } public string Creator { get; set; } = "system"; } // 响应模型(与后端一致) 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; } } 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 FailedItems { get; set; } } 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 FailedItems { get; set; } } public class AutoPackFailedItem { public string WaybillNumber { get; set; } public int ErrorCode { get; set; } public string ErrorMessage { get; set; } } ``` ## 8. 注意事项 1. **袋牌状态**:只有状态为 "Opened" 的袋牌才能启动自动集包 2. **筛选条件**:系统会自动筛选 84 小时内换单成功的 USPS 包裹 3. **处理速度**:每单处理间隔 2-4 秒随机延迟,模拟人工操作 4. **任务缓存**:任务状态缓存 60 分钟,超时后无法查询 5. **并发控制**:目前支持多个任务同时运行 6. **网络超时**:建议设置合理的网络超时时间(如 30 秒) 7. **错误处理**:妥善处理 API 调用中的异常情况 8. **UI 响应**:使用异步操作避免 UI 卡顿 ## 9. 调试建议 1. **服务地址**:确保后端服务正常运行,地址配置正确 2. **袋牌准备**:测试前准备好状态为 "Opened" 的 USPS 袋牌 3. **数据准备**:确保有符合条件的 USPS 包裹数据 4. **日志查看**:后端服务日志可用于排查问题 5. **状态监控**:通过轮询接口实时监控任务进度 ## 10. 常见问题 | 问题 | 可能原因 | 解决方案 | |------|---------|----------| | 启动失败:Bag tag not found or not in opened status | 袋牌不存在或状态不是 Opened | 检查袋牌号是否正确,确保袋牌已打开 | | 启动失败:No eligible waybills found | 无符合条件的包裹 | 检查是否有 84 小时内换单成功的 USPS 包裹 | | 任务状态为 failed | 系统异常 | 查看后端日志,检查具体错误原因 | | 进度查询返回 404 | 任务 ID 错误或任务已过期 | 检查任务 ID 是否正确,任务是否在 60 分钟内 | | 取消任务失败 | 任务已完成或不存在 | 确认任务状态和 ID | ## 11. 版本信息 | 版本 | 日期 | 说明 | |------|------|------| | 1.0 | 2026-04-03 | 初始版本 | --- **注**:本文档基于后端 API 实现,如有接口变更请同步更新。