diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..61a5292
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,7 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(dotnet build *)"
+ ]
+ }
+}
diff --git a/Sln.Wcs.Strategy/ITaskExecuteStrategy.cs b/Sln.Wcs.Strategy/ITaskExecuteStrategy.cs
index 2a0e0bb..3ffb4f3 100644
--- a/Sln.Wcs.Strategy/ITaskExecuteStrategy.cs
+++ b/Sln.Wcs.Strategy/ITaskExecuteStrategy.cs
@@ -1,13 +1,13 @@
namespace Sln.Wcs.Strategy;
///
-/// 任务执行策略接口 - 从任务队列中取出待执行任务,根据设备类型下发到对应 SDK
+/// 任务执行调度策略 - 按任务类别(包材/托盘/成品)并行调度,
+/// AGV 阶段并发下发(多车),提升机阶段按设备串行下发
///
public interface ITaskExecuteStrategy
{
///
- /// 执行一轮任务调度:查询待执行任务,按设备类型下发
+ /// 启动任务调度,持续运行直到 cancellationToken 被取消
///
- /// 本次下发的任务数量
- Task ExecuteAsync();
+ Task ExecuteAsync(CancellationToken cancellationToken = default);
}
diff --git a/Sln.Wcs.Strategy/Sln.Wcs.Strategy.csproj b/Sln.Wcs.Strategy/Sln.Wcs.Strategy.csproj
index 428753c..8370e97 100644
--- a/Sln.Wcs.Strategy/Sln.Wcs.Strategy.csproj
+++ b/Sln.Wcs.Strategy/Sln.Wcs.Strategy.csproj
@@ -9,8 +9,8 @@
-
-
+
+
diff --git a/Sln.Wcs.Strategy/TaskExecuteStrategy.cs b/Sln.Wcs.Strategy/TaskExecuteStrategy.cs
index 61f559f..6abba79 100644
--- a/Sln.Wcs.Strategy/TaskExecuteStrategy.cs
+++ b/Sln.Wcs.Strategy/TaskExecuteStrategy.cs
@@ -1,169 +1,203 @@
-using Sln.Wcs.HikRoBotSdk;
-using Sln.Wcs.HikRoBotSdk.Dto.GenAgvSchedulingTask;
-using Sln.Wcs.HoistSdk;
-using Sln.Wcs.HoistSdk.Dto.HoistTaskExecutor;
+using System.Collections.Concurrent;
+using Sln.Wcs.HikRoBotApi.Domain.Dto.GenAgvSchedulingTask;
+using Sln.Wcs.HikRoBotApi.Enum;
+using Sln.Wcs.HikRoBotApi.Service;
+using Sln.Wcs.HoistApi.Domain.Dto.HoistTaskExecutor;
+using Sln.Wcs.HoistApi.Domain.Enum;
+using Sln.Wcs.HoistApi.Service;
using Sln.Wcs.Model.Domain;
using Sln.Wcs.Repository.service;
namespace Sln.Wcs.Strategy;
-///
-/// 任务执行策略 - 轮询数据库中待执行的任务,根据设备类型调用对应 SDK 下发
-///
public class TaskExecuteStrategy : ITaskExecuteStrategy
{
private readonly ILiveTaskQueueService _taskQueueService;
private readonly ILiveTaskDetailService _taskDetailService;
- private readonly IHIKRoBotSdk _hikRobotSdk;
- private readonly IHoistSdk _hoistSdk;
+ private readonly IHikRoBotService _hikRobotService;
+ private readonly IHoistApiService _hoistService;
+
+ // AGV 多车并发,默认最大 10 个并发
+ private readonly SemaphoreSlim _agvSemaphore = new(10, 10);
+
+ // 提升机按设备串行,每台提升机一次只能执行一个任务
+ private readonly ConcurrentDictionary _hoistSemaphores = new();
public TaskExecuteStrategy(
ILiveTaskQueueService taskQueueService,
ILiveTaskDetailService taskDetailService,
- IHIKRoBotSdk hikRobotSdk,
- IHoistSdk hoistSdk)
+ IHikRoBotService hikRobotService,
+ IHoistApiService hoistService)
{
_taskQueueService = taskQueueService;
_taskDetailService = taskDetailService;
- _hikRobotSdk = hikRobotSdk;
- _hoistSdk = hoistSdk;
+ _hikRobotService = hikRobotService;
+ _hoistService = hoistService;
+ }
+
+ public Task ExecuteAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.WhenAll(
+ ProcessCategory(1, cancellationToken), // 包材
+ ProcessCategory(2, cancellationToken), // 成品
+ ProcessCategory(3, cancellationToken) // 托盘
+ );
}
///
- /// 执行一轮任务调度:查询所有待执行任务,按设备类型下发
+ /// 按任务类别轮询待执行任务,同一类别内多个任务并行处理
///
- public Task ExecuteAsync()
+ private async Task ProcessCategory(int taskCategory, CancellationToken ct)
{
- return Task.Run(() =>
+ while (!ct.IsCancellationRequested)
{
- // 1. 查询所有待执行的任务队列(task_status = 1)
- var pendingTasks = _taskQueueService.Query(x => x.taskStatus == 1);
-
- if (pendingTasks.Count == 0)
- return 0;
-
- int dispatchedCount = 0;
-
- foreach (var task in pendingTasks)
+ try
{
- try
+ var pendingTasks = _taskQueueService.Query(x =>
+ x.taskStatus == 1 && x.taskCategory == taskCategory);
+
+ if (pendingTasks.Count > 0)
{
- // 2. 查询任务明细
- var details = _taskDetailService.Query(x => x.taskCode == task.taskCode);
-
- if (details.Count == 0)
- continue;
-
- // 3. 取第一个未执行的明细步骤
- var currentDetail = details
- .Where(x => x.taskStatus == 1)
- .OrderBy(x => x.objId)
- .FirstOrDefault();
-
- if (currentDetail == null)
- continue;
-
- // 4. 根据设备类型下发任务
- var success = DispatchByDeviceType(currentDetail);
-
- if (success)
+ await Parallel.ForEachAsync(pendingTasks, ct, async (task, innerCt) =>
{
- // 5. 更新明细状态为执行中
- currentDetail.taskStatus = 2;
- _taskDetailService.Update(currentDetail);
-
- // 6. 更新队列状态为执行中
- task.taskStatus = 2;
- _taskQueueService.Update(task);
-
- dispatchedCount++;
- }
- }
- catch (Exception ex)
- {
- Console.WriteLine($"任务 {task.taskCode} 下发失败: {ex.Message}");
+ await ProcessTask(task, innerCt);
+ });
}
}
+ catch (Exception ex)
+ {
+ Console.WriteLine($"任务类别 {taskCategory} 调度异常: {ex.Message}");
+ }
- return dispatchedCount;
- });
+ await Task.Delay(1000, ct);
+ }
}
///
- /// 根据设备类型分发到对应 SDK
+ /// 处理单个任务:按顺序遍历明细步骤,依次下发到对应设备
///
- private bool DispatchByDeviceType(LiveTaskDetail detail)
+ private async Task ProcessTask(LiveTaskQueue task, CancellationToken ct)
+ {
+ try
+ {
+ var details = _taskDetailService.Query(x =>
+ x.taskCode == task.taskCode && x.taskStatus == 1)
+ .OrderBy(x => x.objId)
+ .ToList();
+
+ foreach (var detail in details)
+ {
+ if (ct.IsCancellationRequested) return;
+
+ var success = await DispatchDetail(detail);
+ if (!success) break;
+ }
+
+ // 检查是否还有待执行的明细,若无则更新队列状态为执行中
+ var remainingPending = _taskDetailService.Query(x =>
+ x.taskCode == task.taskCode && x.taskStatus == 1);
+
+ if (remainingPending.Count == 0)
+ {
+ task.taskStatus = 2;
+ _taskQueueService.Update(task);
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"任务 {task.taskCode} 处理异常: {ex.Message}");
+ }
+ }
+
+ ///
+ /// 根据设备类型分发明细到对应设备 API
+ ///
+ private Task DispatchDetail(LiveTaskDetail detail)
{
return detail.deviceType switch
{
1 => DispatchToAgv(detail),
2 => DispatchToHoist(detail),
- _ => throw new NotSupportedException($"不支持的设备类型: {detail.deviceType}")
+ _ => Task.FromResult(false) // ConveyorLine 暂不处理
};
}
///
- /// 下发 AGV 任务(海康机器人 SDK)
+ /// 下发 AGV 任务,通过 HikRoBotApi(AGV 多车可并发)
///
- private bool DispatchToAgv(LiveTaskDetail detail)
+ private async Task DispatchToAgv(LiveTaskDetail detail)
{
- var dto = new GenAgvSchedulingTaskDto
+ await _agvSemaphore.WaitAsync();
+ try
{
- reqCode = detail.taskCode,
- reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
- clientCode = "WCS",
- taskCode = detail.taskCode,
- taskTyp = detail.taskType switch
+ var dto = new GenAgvSchedulingTaskDto
{
- 1 => "1", // 入库
- 2 => "2", // 出库
- _ => "0"
- },
- taskMode = detail.taskType switch
+ reqCode = detail.taskCode,
+ reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
+ clientCode = "WCS",
+ taskCode = detail.taskCode,
+ taskTyp = detail.taskType switch { 1 => "1", 2 => "2", _ => "0" },
+ taskMode = detail.taskType switch { 1 => "2", 2 => "1", _ => "0" },
+ wbCode = detail.startPoint,
+ ctnrCode = detail.palletBarcode,
+ materialLot = detail.materialBarcode,
+ positionCodePath = new List
+ {
+ new() { positionCode = detail.startPoint, type = "00" },
+ new() { positionCode = detail.endPoint, type = "00" }
+ }
+ };
+
+ var result = _hikRobotService.GetGenAgvSchedulingTask(dto);
+ if (result.code.Equals(HikRoBotStatusEnum.成功))
{
- 1 => "2", // 入库 move
- 2 => "1", // 出库 move
- _ => "0"
- },
- wbCode = detail.startPoint,
- ctnrCode = detail.palletBarcode,
- materialLot = detail.materialBarcode,
- positionCodePath = new List
- {
- new() { positionCode = detail.startPoint, type = "00" },
- new() { positionCode = detail.endPoint, type = "00" }
+ detail.taskStatus = 2;
+ _taskDetailService.Update(detail);
+ return true;
}
- };
- var result = _hikRobotSdk.GenAgvSchedulingTask(dto);
- return result.code == "0";
- }
-
- ///
- /// 下发提升机任务(Hoist SDK)
- ///
- private bool DispatchToHoist(LiveTaskDetail detail)
- {
- var dto = new HoistTaskExeDto
+ Console.WriteLine($"AGV 任务 {detail.taskCode} 下发失败: [{result.code}] {result.msg}");
+ return false;
+ }
+ finally
{
- hoistCode = detail.startPoint,
- taskCode = detail.taskCode,
- startPoint = ParseIntPoint(detail.startPoint),
- endPoint = ParseIntPoint(detail.endPoint)
- };
-
- var result = _hoistSdk.HoistTaskExecutor(dto);
- return result.code == "0";
+ _agvSemaphore.Release();
+ }
}
///
- /// 将位置字符串转为整数楼层/层号
+ /// 下发提升机任务,通过 HoistApi(每台提升机串行)
///
- private static int ParseIntPoint(string? point)
+ private async Task DispatchToHoist(LiveTaskDetail detail)
{
- if (string.IsNullOrEmpty(point))
- return 0;
+ var hoistCode = detail.startPoint ?? "default";
+ var semaphore = _hoistSemaphores.GetOrAdd(hoistCode, _ => new SemaphoreSlim(1, 1));
- return int.TryParse(point, out var val) ? val : 0;
+ await semaphore.WaitAsync();
+ try
+ {
+ var dto = new HoistTaskExeDto
+ {
+ hoistCode = hoistCode,
+ taskCode = detail.taskCode,
+ startPoint = int.TryParse(detail.startPoint, out var s) ? s : 0,
+ endPoint = int.TryParse(detail.endPoint, out var e) ? e : 0
+ };
+
+ var result = _hoistService.HoistTaskExecutor(dto);
+ if (result.code.Equals(HoistStatusEnum.成功))
+ {
+ detail.taskStatus = 2;
+ _taskDetailService.Update(detail);
+ return true;
+ }
+
+ Console.WriteLine($"提升机 {hoistCode} 任务 {detail.taskCode} 下发失败: [{result.code}] {result.msg}");
+ return false;
+ }
+ finally
+ {
+ semaphore.Release();
+ }
}
}