# 自动出库任务后台服务实现计划
> **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 通知失败不影响任务创建