# 自动出库任务后台服务实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **目标:** 实现一个后台服务,自动检测到期库存并创建出库任务通知 WCS 系统 **架构:** 使用 BackgroundService 模式实现定时任务,通过 ITaskService 查询到期库存、创建任务并通知 WCS **技术栈:** .NET 6, BackgroundService, SqlSugar, Autofac, appsettings.json 配置 --- ## 前置检查 在开始实现前,请确认: - 项目位于: `d:\Git\ShanMeiXinNengYuan\Code\WMS\WIDESEA_WMSServer` - 主项目文件: `WIDESEA_WMSServer\WIDESEA_WMSServer.csproj` - 已阅读设计文档: `docs/plans/2026-03-06-auto-outbound-task-design.md` --- ## Task 1: 创建配置模型类 **Files:** - Create: `WIDESEA_Core/Core/AutoOutboundTaskOptions.cs` **Step 1: 创建配置模型类** 创建文件 `WIDESEA_Core/Core/AutoOutboundTaskOptions.cs`: ```csharp using System.Collections.Generic; namespace WIDESEA_Core.Core { /// /// 自动出库任务配置选项 /// public class AutoOutboundTaskOptions { /// /// 是否启用自动出库任务 /// public bool Enable { get; set; } = true; /// /// 检查间隔(秒) /// public int CheckIntervalSeconds { get; set; } = 300; /// /// 按巷道前缀配置目标地址 /// public Dictionary TargetAddresses { get; set; } = new() { { "GW", "10081" }, { "CW", "10080" } }; } } ``` **Step 2: 提交配置模型类** ```bash git add WIDESEA_Core/Core/AutoOutboundTaskOptions.cs git commit -m "feat: 添加自动出库任务配置模型类" ``` --- ## Task 2: 更新 appsettings.json 配置 **Files:** - Modify: `WIDESEA_WMSServer/appsettings.json` **Step 1: 添加配置节** 在 `appsettings.json` 中添加 `AutoOutboundTask` 配置节(在 `WebSocketPort` 后面): ```json { ..., "WebSocketPort": 9296, "AutoOutboundTask": { "Enable": true, "CheckIntervalSeconds": 300, "TargetAddresses": { "GW": "10081", "CW": "10080" } } } ``` **Step 2: 提交配置更新** ```bash git add WIDESEA_WMSServer/appsettings.json git commit -m "config: 添加自动出库任务配置" ``` --- ## Task 3: 在 ITaskService 接口中添加方法定义 **Files:** - Modify: `WIDESEA_ITaskInfoService/ITaskService.cs` **Step 1: 添加接口方法** 在 `ITaskService.cs` 中添加方法定义(在文件末尾,`}` 之前): ```csharp /// /// 自动创建出库任务 - 查询到期库存并创建任务 /// /// 包含创建结果的响应对象 Task CreateAutoOutboundTasksAsync(); ``` **Step 2: 提交接口更新** ```bash git add WIDESEA_ITaskInfoService/ITaskService.cs git commit -m "feat: 添加自动出库任务创建接口方法" ``` --- ## Task 4: 在 TaskService 中实现核心逻辑 **Files:** - Modify: `WIDESEA_TaskInfoService/TaskService.cs` **Step 1: 添加依赖注入字段** 在 `TaskService` 类的私有字段区域(第 20-25 行附近)添加: ```csharp private readonly IConfiguration _configuration; ``` 修改构造函数签名(第 37-48 行),添加 `IConfiguration` 参数: ```csharp public TaskService( IRepository BaseDal, IMapper mapper, IStockInfoService stockInfoService, ILocationInfoService locationInfoService, HttpClientHelper httpClientHelper, IConfiguration configuration) : base(BaseDal) { _mapper = mapper; _stockInfoService = stockInfoService; _locationInfoService = locationInfoService; _httpClientHelper = httpClientHelper; _configuration = configuration; } ``` **Step 2: 添加地址映射辅助方法** 在 `TaskService` 类中添加私有方法(在 `GetTasksByPalletCodeAsync` 方法后面,`#endregion` 之前): ```csharp /// /// 根据巷道确定目标地址 /// private string DetermineTargetAddress(string roadway, Dictionary addressMap) { if (string.IsNullOrWhiteSpace(roadway)) return "10080"; // 默认地址 foreach (var kvp in addressMap) { if (roadway.Contains(kvp.Key)) return kvp.Value; } return "10080"; // 默认地址 } ``` **Step 3: 实现自动出库任务创建方法** 在 `TaskService` 类的 `#endregion WCS逻辑处理` 之前添加完整方法: ```csharp /// /// 自动创建出库任务 - 查询到期库存并创建任务 /// public async Task CreateAutoOutboundTasksAsync() { try { // 1. 查询到期库存 var expiredStocks = await _stockInfoService.Repository .QueryAsync(s => s.OutboundDate <= DateTime.Now && s.StockStatus == StockStatusEmun.入库完成.GetHashCode()); if (expiredStocks == null || !expiredStocks.Any()) { return WebResponseContent.Instance.OK("无到期库存需要处理"); } // 加载位置详情 foreach (var stock in expiredStocks) { if (stock.LocationId > 0) { stock.LocationDetails = await _locationInfoService.Repository .GetFirstAsync(s => s.Id == stock.LocationId); } } // 过滤有位置且位置有库存的记录 expiredStocks = expiredStocks .Where(s => s.LocationDetails != null && s.LocationDetails.LocationStatus == LocationStatusEnum.InStock.GetHashCode()) .ToList(); if (!expiredStocks.Any()) { return WebResponseContent.Instance.OK("无符合条件的到期库存"); } // 2. 检查已存在的任务 var palletCodes = expiredStocks.Select(s => s.PalletCode).ToList(); var existingTasks = await Repository.QueryAsync(t => palletCodes.Contains(t.PalletCode) && (t.TaskStatus == TaskStatusEnum.New.GetHashCode() || t.TaskStatus == TaskStatusEnum.Executing.GetHashCode() || t.TaskStatus == TaskInStatusEnum.InNew.GetHashCode())); var processedPallets = existingTasks.Select(t => t.PalletCode).ToHashSet(); // 3. 筛选需要处理的库存 var stocksToProcess = expiredStocks .Where(s => !processedPallets.Contains(s.PalletCode)) .ToList(); if (!stocksToProcess.Any()) { return WebResponseContent.Instance.OK("所有到期库存已存在任务"); } // 4. 获取配置的目标地址映射 var targetAddressMap = _configuration.GetSection("AutoOutboundTask:TargetAddresses") .Get>() ?? new Dictionary(); // 5. 批量创建任务 var taskList = new List(); foreach (var stock in stocksToProcess) { // 根据巷道确定目标地址 var targetAddress = DetermineTargetAddress( stock.LocationDetails?.RoadwayNo ?? "", targetAddressMap); var task = new Dt_Task { WarehouseId = stock.WarehouseId, PalletCode = stock.PalletCode, PalletType = stock.PalletType, SourceAddress = stock.LocationCode, CurrentAddress = stock.LocationCode, NextAddress = targetAddress, TargetAddress = targetAddress, Roadway = stock.LocationDetails?.RoadwayNo ?? "", TaskType = TaskTypeEnum.Outbound.GetHashCode(), TaskStatus = TaskStatusEnum.New.GetHashCode(), Grade = 1, TaskNum = await Repository.GetTaskNo(), Creater = "system_auto" }; taskList.Add(task); } var addResult = await BaseDal.AddDataAsync(taskList) > 0; if (!addResult) { return WebResponseContent.Instance.Error($"批量创建任务失败,共 {taskList.Count} 个任务"); } // 6. 通知 WCS(异步,不影响主流程) _ = Task.Run(async () => { foreach (var task in taskList) { try { var wmstaskDto = _mapper.Map(task); await _httpClientHelper.Post( "http://logistics-service/api/logistics/notifyoutbound", JsonSerializer.Serialize(wmstaskDto)); } catch (Exception ex) { // WCS 通知失败不影响任务创建,记录日志即可 Console.WriteLine($"WCS 通知失败,任务编号: {task.TaskNum}, 错误: {ex.Message}"); } } }); return WebResponseContent.Instance.OK($"成功创建 {taskList.Count} 个出库任务", taskList.Count); } catch (Exception ex) { return WebResponseContent.Instance.Error($"自动创建出库任务失败: {ex.Message}"); } } ``` **Step 4: 提交实现** ```bash git add WIDESEA_TaskInfoService/TaskService.cs git commit -m "feat: 实现自动出库任务创建方法" ``` --- ## Task 5: 创建后台服务类 **Files:** - Create: `WIDESEA_WMSServer/BackgroundServices/AutoOutboundTaskBackgroundService.cs` **Step 1: 创建 BackgroundServices 目录** ```bash mkdir -p WIDESEA_WMSServer/BackgroundServices ``` **Step 2: 创建后台服务类** 创建文件 `WIDESEA_WMSServer/BackgroundServices/AutoOutboundTaskBackgroundService.cs`: ```csharp using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Threading; using System.Threading.Tasks; using WIDESEA_Core.Core; using WIDESEA_ITaskInfoService; namespace WIDESEA_WMSServer.BackgroundServices { /// /// 自动出库任务后台服务 /// 定期检查到期库存并创建出库任务 /// public class AutoOutboundTaskBackgroundService : BackgroundService { private readonly ILogger _logger; private readonly ITaskService _taskService; private readonly AutoOutboundTaskOptions _options; public AutoOutboundTaskBackgroundService( ILogger logger, ITaskService taskService, IOptions options) { _logger = logger; _taskService = taskService; _options = options.Value; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("自动出库任务后台服务已启动"); if (!_options.Enable) { _logger.LogInformation("自动出库任务功能已禁用,服务退出"); return; } _logger.LogInformation("自动出库任务检查间隔: {Seconds} 秒", _options.CheckIntervalSeconds); while (!stoppingToken.IsCancellationRequested) { try { _logger.LogDebug("开始检查到期库存..."); var result = await _taskService.CreateAutoOutboundTasksAsync(); if (result.Status) { _logger.LogInformation("到期库存检查完成: {Message}", result.Message); } else { _logger.LogWarning("到期库存检查失败: {Message}", result.Message); } } catch (Exception ex) { _logger.LogError(ex, "自动出库任务创建过程中发生异常"); } var delay = TimeSpan.FromSeconds(_options.CheckIntervalSeconds); _logger.LogDebug("等待 {Seconds} 秒后进行下次检查", delay.TotalSeconds); await Task.Delay(delay, stoppingToken); } _logger.LogInformation("自动出库任务后台服务已停止"); } public override async Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("正在停止自动出库任务后台服务..."); await base.StopAsync(cancellationToken); } } } ``` **Step 3: 提交后台服务类** ```bash git add WIDESEA_WMSServer/BackgroundServices/AutoOutboundTaskBackgroundService.cs git commit -m "feat: 添加自动出库任务后台服务" ``` --- ## Task 6: 注册服务和配置 **Files:** - Modify: `WIDESEA_WMSServer/Program.cs` **Step 1: 添加配置注册** 在 `Program.cs` 中的 `builder.Services.AddAllOptionRegister();` 之后添加: ```csharp builder.Services.Configure( builder.Configuration.GetSection("AutoOutboundTask")); ``` **Step 2: 添加后台服务注册** 在 `Program.cs` 中的 `builder.Services.AddInitializationHostServiceSetup();` 之后添加: ```csharp builder.Services.AddHostedService(); ``` **Step 3: 添加命名空间引用** 在 `Program.cs` 文件顶部的 using 区域添加: ```csharp using WIDESEA_WMSServer.BackgroundServices; using WIDESEA_Core.Core; ``` **Step 4: 提交 Program.cs 更新** ```bash git add WIDESEA_WMSServer/Program.cs git commit -m "config: 注册自动出库任务后台服务和配置" ``` --- ## Task 7: 编译验证 **Step 1: 编译项目** ```bash cd WIDESEA_WMSServer dotnet build --configuration Release ``` 预期输出: 编译成功,无错误 **Step 2: 检查编译输出** 确认输出包含: ``` Build succeeded. 0 Warning(s) 0 Error(s) ``` **Step 3: 如果有错误,根据错误信息修复** 常见问题: - 缺少 using 引用 → 添加相应的 namespace - 类型不匹配 → 检查属性名称和类型是否正确 --- ## Task 8: 手动测试 **Step 1: 准备测试数据** 在数据库中执行 SQL,创建一条测试库存记录: ```sql -- 确保有一个已入库的库存记录,OutboundDate 设置为过去时间 UPDATE Dt_StockInfo SET OutboundDate = DATEADD(MINUTE, -5, GETDATE()) WHERE PalletCode = 'TEST001' AND StockStatus = 1; -- 1 = 入库完成 -- 如果没有测试记录,插入一条 INSERT INTO Dt_StockInfo (PalletCode, PalletType, LocationId, LocationCode, WarehouseId, StockStatus, OutboundDate, CreateDate) VALUES ('TEST001', 1, 1, '01-01-01', 1, 1, DATEADD(MINUTE, -5, GETDATE()), GETDATE()) ``` **Step 2: 启动应用程序** ```bash cd WIDESEA_WMSServer dotnet run ``` **Step 3: 观察控制台输出** 预期看到类似日志: ``` info: WIDESEA_WMSServer.BackgroundServices.AutoOutboundTaskBackgroundService[0] 自动出库任务后台服务已启动 info: WIDESEA_WMSServer.BackgroundServices.AutoOutboundTaskBackgroundService[0] 自动出库任务检查间隔: 300 秒 ... info: WIDESEA_WMSServer.BackgroundServices.AutoOutboundTaskBackgroundService[0] 到期库存检查完成: 成功创建 X 个出库任务 ``` **Step 4: 验证数据库** 查询任务表确认任务已创建: ```sql SELECT * FROM Dt_Task WHERE PalletCode = 'TEST001' AND Creater = 'system_auto' ``` **Step 5: 测试完成,清理测试数据** ```sql -- 删除测试任务 DELETE FROM Dt_Task WHERE PalletCode = 'TEST001' -- 删除测试库存 DELETE FROM Dt_StockInfo WHERE PalletCode = 'TEST001' ``` --- ## Task 9: 配置验证测试 **Step 1: 测试禁用功能** 修改 `appsettings.json`: ```json "AutoOutboundTask": { "Enable": false, ... } ``` **Step 2: 启动应用验证** ```bash dotnet run ``` 预期输出应包含: `自动出库任务功能已禁用,服务退出` **Step 3: 恢复配置** ```json "AutoOutboundTask": { "Enable": true, ... } ``` **Step 4: 测试不同的检查间隔** 修改为 10 秒方便观察: ```json "CheckIntervalSeconds": 10 ``` 启动应用,观察检查是否按 10 秒间隔执行 **Step 5: 提交测试结论** 创建测试说明文件 `WIDESEA_WMSServer/BackgroundServices/README.md`: ```markdown # 自动出库任务后台服务 ## 功能说明 本服务自动检测到期库存并创建出库任务。 ## 配置 在 `appsettings.json` 中配置: ```json { "AutoOutboundTask": { "Enable": true, "CheckIntervalSeconds": 300, "TargetAddresses": { "GW": "10081", "CW": "10080" } } } ``` ## 测试方法 1. 设置库存的 OutboundDate 为过去时间 2. 启动应用,观察日志 3. 确认任务已创建到数据库 ``` **Step 6: 提交测试文档** ```bash git add WIDESEA_WMSServer/BackgroundServices/README.md git commit -m "docs: 添加后台服务测试说明" ``` --- ## Task 10: 最终验证和文档 **Step 1: 运行完整编译** ```bash cd .. dotnet build WIDESEA_WMSServer/WIDESEA_WMSServer.csproj --configuration Release ``` **Step 2: 确认所有文件已提交** ```bash git status ``` 应该看到: `nothing to commit, working tree clean` **Step 3: 查看提交历史** ```bash git log --oneline -10 ``` **Step 4: 创建功能总结** 更新设计文档,添加实施状态: 在 `docs/plans/2026-03-06-auto-outbound-task-design.md` 末尾添加: ```markdown ## 实施状态 - [x] 设计完成 - [x] 配置模型类创建 - [x] TaskService 方法实现 - [x] 后台服务类创建 - [x] Program.cs 注册完成 - [x] 编译验证通过 - [x] 手动测试完成 **实施日期**: 2026-03-06 **实施人**: Claude Code ``` **Step 5: 提交文档更新** ```bash git add docs/plans/2026-03-06-auto-outbound-task-design.md git commit -m "docs: 更新设计文档实施状态" ``` --- ## 完成检查清单 - [ ] 所有代码文件已创建 - [ ] 所有代码已编译通过 - [ ] 配置文件已更新 - [ ] 手动测试已完成 - [ ] 日志输出符合预期 - [ ] 数据库验证通过 - [ ] 文档已更新 - [ ] 所有更改已提交到 git --- ## 故障排查 ### 编译错误 **问题**: 找不到类型或命名空间 **解决**: 确保已添加正确的 using 引用 **问题**: 依赖注入失败 **解决**: 检查 Program.cs 中的服务注册顺序 ### 运行时错误 **问题**: 后台服务没有启动 **解决**: 1. 检查 `appsettings.json` 中 `Enable` 是否为 `true` 2. 查看启动日志中的错误信息 **问题**: 没有创建任务 **解决**: 1. 确认库存记录的 `OutboundDate` 已过期 2. 确认库存状态为"入库完成" 3. 检查是否已存在相同托盘的任务 **问题**: WCS 通知失败 **解决**: 1. 检查 WCS 服务是否运行 2. 验证通知地址是否正确 3. 注意: WCS 通知失败不影响任务创建