上传源代码版本

This commit is contained in:
Im-Jenisson
2026-06-01 16:30:29 +08:00
commit b2a9b7d3c2
462 changed files with 104365 additions and 0 deletions

View File

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