# 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 实现，如有接口变更请同步更新。