Files
2026-06-01 16:30:29 +08:00

17 KiB
Raw Permalink Blame History

USPS 自动集包开发任务分解

任务概览

阶段 任务数 预计工时
模型定义 2 2h
数据访问层 2 3h
业务逻辑层 3 5h
接口层 2 3h
测试与优化 2 3h
总计 11 16h

阶段一:模型定义

任务 1.1:创建任务状态模型

文件src/MDL/Models/AutoPackTaskStatus.cs

内容

namespace MDL.Models
{
    /// <summary>
    /// 自动集包任务状态
    /// </summary>
    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<AutoPackFailedItem> FailedItems { get; set; } = new();
        public CancellationTokenSource CancellationTokenSource { get; set; }
    }

    public class AutoPackFailedItem
    {
        public string WaybillNumber { get; set; }
        public int ErrorCode { get; set; }
        public string ErrorMessage { get; set; }
    }
}

验收标准

  • 模型包含所有必要字段
  • 字段命名符合项目规范
  • 支持 JSON 序列化

任务 1.2:创建请求/响应 DTO

文件src/MDL/Models/AutoPackRequest.cs

内容

namespace MDL.Models
{
    /// <summary>
    /// 启动自动集包请求
    /// </summary>
    public class StartAutoPackRequest
    {
        public string TagNumber { get; set; }
        public string Creator { get; set; } = "system";
    }

    /// <summary>
    /// 启动自动集包响应
    /// </summary>
    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; }
    }

    /// <summary>
    /// 自动集包进度响应
    /// </summary>
    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; }
    }

    /// <summary>
    /// 自动集包结果响应
    /// </summary>
    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; }
    }
}

验收标准

  • 包含 Start、Progress、Result 三种响应 DTO
  • 字段与 spec.md 定义一致
  • 支持前端 WinForm 调用

阶段二:数据访问层

任务 2.1:扩展 Repository 接口

文件src/DAL/Interfaces/IBagTagRepository.cs

新增方法

/// <summary>
/// 查询符合条件的 USPS 包裹
/// </summary>
Task<List<string>> GetEligibleUspsWaybillsAsync(DateTime cutoffTime);

/// <summary>
/// 获取符合条件的包裹数量
/// </summary>
Task<int> GetEligibleUspsWaybillCountAsync(DateTime cutoffTime);

验收标准

  • 接口定义符合项目规范
  • 参数设计合理cutoffTime = 当前时间 - 84小时

任务 2.2:实现 Repository 方法

文件src/DAL/Repositories/BagTagRepository.cs

实现要点

  1. 使用 SqlSugar 执行 SQL 查询
  2. 查询条件:
    • ReplaceStatus = 'Y'
    • CreatedAt >= cutoffTime
    • FinalMileTrackingNumber 不为空
    • 未关联到 bag_tag_waybills
    • USPS 运单号格式92/93/94/95 或 420+邮编+92/93/94/95
    • 有一条换单成功的扫描记录

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 (
        l.FinalMileTrackingNumber REGEXP '^(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

验收标准

  • SQL 查询性能优化(使用索引)
  • 正确处理 USPS 运单号格式
  • 返回结果按 CreatedAt 升序排列

阶段三:业务逻辑层

任务 3.1:扩展 Service 接口

文件src/BLL/Interfaces/IBagTagService.cs

新增方法

/// <summary>
/// 启动自动集包任务
/// </summary>
Task<StartAutoPackResponse> StartAutoPackAsync(string tagNumber, string creator);

/// <summary>
/// 查询自动集包进度
/// </summary>
Task<AutoPackProgressResponse> GetAutoPackProgressAsync(string taskId);

/// <summary>
/// 取消自动集包任务
/// </summary>
Task<bool> CancelAutoPackAsync(string taskId);

/// <summary>
/// 获取自动集包结果
/// </summary>
Task<AutoPackResultResponse> GetAutoPackResultAsync(string taskId);

验收标准

  • 接口定义完整
  • 返回类型与 DTO 对应

任务 3.2:实现任务启动逻辑

文件src/BLL/Services/BagTagService.cs

实现步骤

  1. 验证袋牌存在且状态为 Opened
  2. 生成唯一任务ID格式task_{timestamp}_{guid}
  3. 查询符合条件的包裹列表
  4. 如果无符合条件的包裹,返回错误
  5. 创建任务状态对象,存入缓存
  6. 启动后台任务执行自动集包
  7. 返回任务信息给前端

关键代码

public async Task<StartAutoPackResponse> StartAutoPackAsync(string tagNumber, string creator)
{
    // 1. 验证袋牌
    var tag = await _bagTagRepository.GetByTagNumberAsync(tagNumber);
    if (tag == null || tag.Status != "Opened")
    {
        throw new Exception("Bag tag not found or not in opened status");
    }

    // 2. 查询符合条件的包裹
    var cutoffTime = DateTime.UtcNow.AddHours(-84);
    var waybills = await _bagTagRepository.GetEligibleUspsWaybillsAsync(cutoffTime);
    
    if (waybills.Count == 0)
    {
        throw new Exception("No eligible waybills found");
    }

    // 3. 创建任务
    var taskId = $"task_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
    var taskStatus = new AutoPackTaskStatus
    {
        TaskId = taskId,
        TagNumber = tagNumber,
        Status = "processing",
        TotalCount = waybills.Count,
        ProcessedCount = 0,
        SuccessCount = 0,
        FailedCount = 0,
        StartTime = DateTime.UtcNow,
        FailedItems = new List<AutoPackFailedItem>(),
        CancellationTokenSource = new CancellationTokenSource()
    };

    // 4. 保存任务状态到缓存
    await _cacheService.SetAsync($"autopack:{taskId}", taskStatus, 60); // 缓存60分钟

    // 5. 启动后台任务
    _ = Task.Run(async () => await ExecuteAutoPackAsync(taskId, tagNumber, waybills, creator));

    // 6. 返回响应
    return new StartAutoPackResponse
    {
        TaskId = taskId,
        TagNumber = tagNumber,
        Status = "processing",
        TotalCount = waybills.Count,
        Message = "自动集包任务已启动"
    };
}

验收标准

  • 袋牌验证逻辑正确
  • 任务ID生成唯一
  • 任务状态正确存入缓存
  • 后台任务正确启动

任务 3.3:实现自动集包执行逻辑

文件src/BLL/Services/BagTagService.cs

实现步骤

  1. 从缓存获取任务状态
  2. 遍历包裹列表逐个处理
  3. 每次处理前检查取消令牌
  4. 调用 AssociateWaybillAsync 进行关联
  5. 更新任务状态(成功/失败计数、当前单号)
  6. 记录订单日志
  7. 生成 2-4 秒随机延迟
  8. 处理完成后更新任务状态为 completed 或 failed

关键代码

private async Task ExecuteAutoPackAsync(string taskId, string tagNumber, List<string> waybills, string creator)
{
    var random = new Random();
    var cacheKey = $"autopack:{taskId}";
    
    foreach (var waybill in waybills)
    {
        // 1. 获取任务状态
        var taskStatus = await _cacheService.GetAsync<AutoPackTaskStatus>(cacheKey);
        if (taskStatus == null || taskStatus.CancellationTokenSource?.IsCancellationRequested == true)
        {
            break;
        }

        // 2. 更新当前处理单号
        taskStatus.CurrentWaybill = waybill;
        await _cacheService.SetAsync(cacheKey, taskStatus, 60);

        try
        {
            // 3. 执行关联
            var (success, errorCode, errorMessage) = await AssociateWaybillAsync(tagNumber, waybill, "system_auto");
            
            if (success)
            {
                taskStatus.SuccessCount++;
            }
            else
            {
                taskStatus.FailedCount++;
                taskStatus.FailedItems.Add(new AutoPackFailedItem
                {
                    WaybillNumber = waybill,
                    ErrorCode = errorCode,
                    ErrorMessage = errorMessage
                });
            }
        }
        catch (Exception ex)
        {
            taskStatus.FailedCount++;
            taskStatus.FailedItems.Add(new AutoPackFailedItem
            {
                WaybillNumber = waybill,
                ErrorCode = 9999,
                ErrorMessage = ex.Message
            });
        }

        // 4. 更新进度
        taskStatus.ProcessedCount++;
        await _cacheService.SetAsync(cacheKey, taskStatus, 60);

        // 5. 随机延迟 2-4 秒
        if (taskStatus.ProcessedCount < waybills.Count)
        {
            var delay = random.Next(2000, 4001);
            await Task.Delay(delay);
        }
    }

    // 6. 任务完成
    var finalStatus = await _cacheService.GetAsync<AutoPackTaskStatus>(cacheKey);
    if (finalStatus != null)
    {
        finalStatus.Status = finalStatus.CancellationTokenSource?.IsCancellationRequested == true ? "cancelled" : "completed";
        finalStatus.EndTime = DateTime.UtcNow;
        finalStatus.CurrentWaybill = null;
        await _cacheService.SetAsync(cacheKey, finalStatus, 60);
    }
}

验收标准

  • 支持取消操作
  • 2-4 秒随机延迟正确实现
  • 进度实时更新到缓存
  • 失败记录完整保存
  • 订单日志正确记录

阶段四:接口层

任务 4.1:实现控制器方法

文件src/CONTROLLER/Controllers/BagTagController.cs

新增接口

  1. 启动自动集包
[HttpPost("auto-pack/start")]
public async Task<IActionResult> StartAutoPack([FromBody] StartAutoPackRequest request)
{
    try
    {
        var result = await _bagTagService.StartAutoPackAsync(request.TagNumber, request.Creator);
        return Ok(new { code = 0, message = "success", data = result });
    }
    catch (Exception ex)
    {
        return Ok(new { code = 1001, message = ex.Message });
    }
}
  1. 查询进度
[HttpGet("auto-pack/progress/{taskId}")]
public async Task<IActionResult> GetAutoPackProgress(string taskId)
{
    try
    {
        var result = await _bagTagService.GetAutoPackProgressAsync(taskId);
        if (result == null)
        {
            return Ok(new { code = 1002, message = "Task not found" });
        }
        return Ok(new { code = 0, message = "success", data = result });
    }
    catch (Exception ex)
    {
        return Ok(new { code = 9999, message = ex.Message });
    }
}
  1. 取消任务
[HttpPost("auto-pack/cancel/{taskId}")]
public async Task<IActionResult> CancelAutoPack(string taskId)
{
    try
    {
        var success = await _bagTagService.CancelAutoPackAsync(taskId);
        if (!success)
        {
            return Ok(new { code = 1002, message = "Task not found or already completed" });
        }
        return Ok(new { code = 0, message = "Task cancelled successfully" });
    }
    catch (Exception ex)
    {
        return Ok(new { code = 9999, message = ex.Message });
    }
}
  1. 获取结果
[HttpGet("auto-pack/result/{taskId}")]
public async Task<IActionResult> GetAutoPackResult(string taskId)
{
    try
    {
        var result = await _bagTagService.GetAutoPackResultAsync(taskId);
        if (result == null)
        {
            return Ok(new { code = 1002, message = "Task not found" });
        }
        return Ok(new { code = 0, message = "success", data = result });
    }
    catch (Exception ex)
    {
        return Ok(new { code = 9999, message = ex.Message });
    }
}

验收标准

  • 所有接口响应格式统一
  • 错误码与 spec.md 一致
  • 支持 JSONP参考现有代码

任务 4.2:注册依赖注入

文件src/CONTROLLER/Program.cs(如有需要)

检查是否需要额外注册服务,通常现有 DI 配置已足够。

验收标准

  • 服务能正常解析
  • 无循环依赖

阶段五:测试与优化

任务 5.1:编写单元测试

文件test/...(根据项目测试规范)

测试用例

  1. 启动任务 - 袋牌不存在
  2. 启动任务 - 袋牌未打开
  3. 启动任务 - 无符合条件的包裹
  4. 启动任务 - 成功启动
  5. 查询进度 - 任务不存在
  6. 查询进度 - 正常查询
  7. 取消任务 - 任务不存在
  8. 取消任务 - 任务已完成
  9. 取消任务 - 正常取消
  10. 自动集包执行 - 全部成功
  11. 自动集包执行 - 部分失败
  12. 自动集包执行 - 取消操作

验收标准

  • 核心逻辑覆盖率达到 80%+
  • 所有测试用例通过

任务 5.2:性能优化与联调

优化项

  1. SQL 查询性能优化(添加索引)
  2. 缓存过期时间调整
  3. 并发任务数限制
  4. 异常处理完善

联调检查

  1. WinForm 前端能正常调用接口
  2. 进度实时更新
  3. 取消功能正常
  4. 大数据量1000+ 包裹)测试

验收标准

  • 100 个包裹处理时间 < 10 分钟
  • 内存占用稳定
  • 前端无卡顿

依赖关系图

任务 1.1 (模型) ──┐
                  ├──→ 任务 2.1 (Repository接口) ──→ 任务 2.2 (Repository实现)
任务 1.2 (DTO) ───┘                                          │
                                                             │
任务 3.1 (Service接口) ──────────────────────────────────────┤
                          │                                  │
                          ↓                                  ↓
                    任务 3.2 (启动逻辑) ─────────────→ 任务 3.3 (执行逻辑)
                          │                                  │
                          └────────────────┬─────────────────┘
                                           ↓
                                    任务 4.1 (控制器)
                                           │
                                           ↓
                                    任务 4.2 (DI注册)
                                           │
                                           ↓
                              ┌───────────┴───────────┐
                              ↓                       ↓
                         任务 5.1 (单元测试)      任务 5.2 (优化联调)

风险与应对

风险 影响 应对措施
包裹数量过大导致任务超时 添加任务超时机制,支持断点续传
缓存失效导致进度丢失 使用持久化存储(如数据库)保存任务状态
并发任务过多 限制同时运行的自动集包任务数量
前端轮询频率过高 建议轮询间隔 1-2 秒,或改用 WebSocket