533 lines
16 KiB
Markdown
533 lines
16 KiB
Markdown
# 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 实现,如有接口变更请同步更新。 |