Files
LabelChange-server/USPS自动集包接口文档.md
2026-06-01 16:30:29 +08:00

533 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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.

# 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<AutoPackFailedItem> 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<AutoPackFailedItem> 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<ApiResponse<StartAutoPackResponse>>();
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<ApiResponse<AutoPackProgressResponse>>();
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<ApiResponse<object>>();
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<ApiResponse<AutoPackResultResponse>>();
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<T>
{
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<AutoPackFailedItem> 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<AutoPackFailedItem> 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 实现,如有接口变更请同步更新。