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(); + } } }