wanshenmean
2026-03-27 bf2aa9dd56432a74940ca1bb08fb4d7eaee37045
feat(WCS): 完善 WIDESEAWCS_Tasks 模块日志系统

为 RobotJob、ConveyorLineNewJob、StackerCraneJob 三个模块的代码添加完整的日志记录,
同时使用 ILogger 和 QuartzLogger 两种日志记录器,确保日志的完整性和可追溯性。

主要变更:

【日志规范】
- 所有 _logger.LogInformation/Debug/Warning/Error 均同步添加 QuartzLogger.Info/Debug/Warn/Error
- Info:记录正常业务流程节点
- Debug:记录详细执行路径和查询结果
- Warn:记录需要注意的异常情况(如版本冲突、未找到资源)
- Error:记录操作失败的情况

【RobotJob 模块】
- RobotClientManager: 添加 TCP 客户端连接/断开/订阅的完整日志
- RobotStateManager: 添加状态创建、版本冲突的日志
- RobotTaskProcessor: 添加取货指令下发、入库任务处理的完整日志
- RobotWorkflowOrchestrator: 添加放货/取货条件判断、条码生成的日志
- RobotJob: 添加 TCP 事件订阅、客户端断开、任务执行的日志
- 修复 Dt_RobotTask.TaskNum 属性引用错误

【ConveyorLineNewJob 模块】
- CommonConveyorLineNewJob: 添加并行处理、托盘检查、任务分发的完整日志
- ConveyorLineDispatchHandler: 添加心跳、入库请求、下一地址、出库完成的完整日志
- ConveyorLineTaskFilter: 添加任务查询、向 WMS 请求任务的日志
- ConveyorLineTargetAddressSelector: 添加拘束机/插拔钉机交互的完整日志

【StackerCraneJob 模块】
- CommonStackerCraneJob: 添加任务选择、命令构建/发送、任务完成的完整日志
- StackerCraneTaskSelector: 添加任务筛选、站台可用性判断的完整日志
- StackerCraneCommandBuilder: 添加命令类型匹配、入库/出库/移库命令构建的完整日志

【代码修复】
- 修复 ILogger<T> 类型 invariance 问题,改用非泛型 ILogger 接口
- 修复 LogWarn 方法名错误(应为 LogWarning)
- 修复 StackerCraneTaskSelectorTests 测试文件构造函数参数问题

修改文件:13 个
代码行数:+411 / -58

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
已修改13个文件
469 ■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotClientManager.cs 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 39 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs 61 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/StackerCraneTaskSelectorTests.cs 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/CommonConveyorLineNewJob.cs
@@ -1,5 +1,6 @@
using MapsterMapper;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Quartz;
using SqlSugar;
using System.Text.Json;
@@ -7,6 +8,7 @@
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -70,6 +72,11 @@
        private readonly HttpClientHelper _httpClientHelper;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger<CommonConveyorLineNewJob> _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="taskService">任务服务</param>
@@ -77,16 +84,18 @@
        /// <param name="routerService">路由服务</param>
        /// <param name="mapper">对象映射器</param>
        /// <param name="httpClientHelper">HTTP 客户端帮助类</param>
        public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper)
        /// <param name="logger">日志记录器</param>
        public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger<CommonConveyorLineNewJob> logger)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _routerService = routerService;
            _mapper = mapper;
            _httpClientHelper = httpClientHelper;
            _logger = logger;
            // 初始化调度处理器
            _conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper);
            _conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper, _logger);
        }
        /// <summary>
@@ -116,7 +125,8 @@
                    if (childDeviceCodes == null || childDeviceCodes.Count == 0)
                    {
                        // 没有子设备,直接返回
                        Console.WriteLine($"输送线 {conveyorLine.DeviceCode} 没有子设备");
                        _logger.LogInformation("输送线 {DeviceCode} 没有子设备", conveyorLine.DeviceCode);
                        QuartzLogger.Info($"输送线 {conveyorLine.DeviceCode} 没有子设备", conveyorLine.DeviceCode);
                        return Task.CompletedTask;
                    }
@@ -126,6 +136,9 @@
                        // 限制并发数:子设备数量和 CPU 核心数*2 的较小值
                        MaxDegreeOfParallelism = Math.Min(childDeviceCodes.Count, Environment.ProcessorCount * 2),
                    };
                    _logger.LogDebug("Execute:开始并行处理输送线 {DeviceCode},子设备数量: {Count}", conveyorLine.DeviceCode, childDeviceCodes.Count);
                    QuartzLogger.Debug($"开始并行处理输送线,子设备数量: {childDeviceCodes.Count}", conveyorLine.DeviceCode);
                    // 并行处理每个子设备
                    Parallel.For(0, childDeviceCodes.Count, parallelOptions, i =>
@@ -140,6 +153,8 @@
                            // 如果命令为空,跳过
                            if (command == null)
                            {
                                _logger.LogDebug("Execute:子设备 {ChildDeviceCode} 命令为空,跳过", childDeviceCode);
                                QuartzLogger.Debug($"子设备 {childDeviceCode} 命令为空,跳过", conveyorLine.DeviceCode);
                                return;
                            }
@@ -164,6 +179,9 @@
                                    {
                                        // 没有任务,向 WMS 请求出库托盘任务
                                        var position = checkPalletPositions.FirstOrDefault(x => x.Code == childDeviceCode);
                                        _logger.LogInformation("Execute:检查托盘位置 {ChildDeviceCode},请求WMS出库托盘任务", childDeviceCode);
                                        QuartzLogger.Info($"检查托盘位置 {childDeviceCode},请求WMS出库托盘任务", conveyorLine.DeviceCode);
                                        var responseResult = _httpClientHelper.Post<WebResponseContent>("GetOutBoundTrayTaskAsync", new CreateTaskDto()
                                        {
                                            WarehouseId = position.WarehouseId,
@@ -183,12 +201,19 @@
                            // ========== 检查 PLC_STB 标志 ==========
                            // 只有当 PLC_STB 为 1 时才处理任务
                            if (command.PLC_STB != 1) return;
                            if (command.PLC_STB != 1)
                            {
                                _logger.LogDebug("Execute:子设备 {ChildDeviceCode} PLC_STB 不为1,跳过", childDeviceCode);
                                QuartzLogger.Debug($"子设备 {childDeviceCode} PLC_STB 不为1,跳过", conveyorLine.DeviceCode);
                                return;
                            }
                            // ========== 处理无托盘条码的情况 ==========
                            // 无托盘条码时,请求出库任务
                            if (command.Barcode.IsNullOrEmpty() || command.Barcode.Replace("\0", "") == "")
                            {
                                _logger.LogDebug("Execute:子设备 {ChildDeviceCode} 无托盘条码,请求出库任务", childDeviceCode);
                                QuartzLogger.Debug($"子设备 {childDeviceCode} 无托盘条码,请求出库任务", conveyorLine.DeviceCode);
                                _conveyorLineDispatch.RequestOutbound(conveyorLine, command, childDeviceCode);
                                return;
                            }
@@ -200,6 +225,8 @@
                                Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
                                if (!task.IsNullOrEmpty())
                                {
                                    _logger.LogInformation("Execute:子设备 {ChildDeviceCode} 处理任务 {TaskNum},状态: {Status}", childDeviceCode, task.TaskNum, task.TaskStatus);
                                    QuartzLogger.Info($"处理任务 {task.TaskNum},状态: {task.TaskStatus}", conveyorLine.DeviceCode);
                                    // 处理任务状态(根据状态分发到不同方法)
                                    ProcessTaskState(conveyorLine, command, task, childDeviceCode);
                                    return;
@@ -209,7 +236,8 @@
                        catch (Exception innerEx)
                        {
                            // 记录异常,但不影响其他子设备的处理
                            Console.Error.WriteLine($"{DateTime.UtcNow:O} [{childDeviceCode}] CorrelationId={correlationId} {innerEx}");
                            _logger.LogError(innerEx, "Execute:子设备 {ChildDeviceCode} 处理异常,CorrelationId: {CorrelationId}", childDeviceCode, correlationId);
                            QuartzLogger.Error($"子设备处理异常: {innerEx.Message}", conveyorLine.DeviceCode, innerEx);
                        }
                    });
                }
@@ -217,7 +245,8 @@
            catch (Exception ex)
            {
                // 记录整体异常
                Console.Error.WriteLine(ex);
                _logger.LogError(ex, "Execute:输送线 {DeviceCode} 执行异常", ex.Message);
                QuartzLogger.Error($"输送线执行异常: {ex.Message}", "CommonConveyorLineNewJob", ex);
            }
            return Task.CompletedTask;
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
@@ -1,7 +1,9 @@
using MapsterMapper;
using Microsoft.Extensions.Logging;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -47,6 +49,11 @@
        private readonly IMapper _mapper;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 输送线任务过滤器
        /// </summary>
        /// <remarks>
@@ -69,16 +76,18 @@
        /// <param name="taskExecuteDetailService">任务执行明细服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="mapper">对象映射器</param>
        public ConveyorLineDispatchHandler(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper)
        /// <param name="logger">日志记录器</param>
        public ConveyorLineDispatchHandler(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, ILogger logger)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _routerService = routerService;
            _mapper = mapper;
            _logger = logger;
            // 初始化任务过滤器和目标地址选择器
            _taskFilter = new ConveyorLineTaskFilter(taskService);
            _targetAddressSelector = new ConveyorLineTargetAddressSelector();
            _taskFilter = new ConveyorLineTaskFilter(taskService, _logger);
            _targetAddressSelector = new ConveyorLineTargetAddressSelector(_logger);
        }
        /// <summary>
@@ -96,6 +105,8 @@
        {
            // 清除任务号,表示当前空闲
            conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, 0, childDeviceCode);
            _logger.LogDebug("HeartBeat:子设备 {ChildDeviceCode} 心跳", childDeviceCode);
            QuartzLogger.Debug($"HeartBeat:子设备 {childDeviceCode} 心跳", conveyorLine.DeviceCode);
        }
        /// <summary>
@@ -114,6 +125,9 @@
        /// <param name="childDeviceCode">子设备编码</param>
        public void RequestInbound(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            _logger.LogInformation("RequestInbound:子设备 {ChildDeviceCode} 请求入库", childDeviceCode);
            QuartzLogger.Info($"请求入库,子设备: {childDeviceCode}", conveyorLine.DeviceCode);
            // 向 WMS 请求新任务(基于条码)
            if (_taskFilter.RequestWmsTask(command.Barcode, childDeviceCode))
            {
@@ -132,6 +146,9 @@
                    // 更新任务状态到下一阶段
                    _taskService.UpdateTaskStatusToNext(task);
                    _logger.LogInformation("RequestInbound:入库任务已下发,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
                    QuartzLogger.Info($"入库任务已下发,任务号: {task.TaskNum},子设备: {childDeviceCode}", conveyorLine.DeviceCode);
                }
            }
        }
@@ -152,8 +169,13 @@
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task == null)
            {
                _logger.LogDebug("RequestInNextAddress:任务 {TaskNo} 不存在", command.TaskNo);
                QuartzLogger.Debug($"RequestInNextAddress:任务 {command.TaskNo} 不存在", conveyorLine.DeviceCode);
                return;
            }
            _logger.LogInformation("RequestInNextAddress:入库下一地址,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
            QuartzLogger.Info($"RequestInNextAddress:入库下一地址,任务号: {task.TaskNum},子设备: {childDeviceCode}", conveyorLine.DeviceCode);
            // 如果不是空托盘任务,处理目标地址(与拘束机/插拔钉机交互)
            if (task.TaskType != (int)TaskOutboundTypeEnum.OutEmpty)
@@ -189,7 +211,9 @@
                // 更新任务状态到下一阶段(通常是完成)
                WebResponseContent content = _taskService.UpdateTaskStatusToNext(task);
                Console.Out.WriteLine(content.Serialize());
                _logger.LogInformation("ConveyorLineInFinish:入库完成,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
                QuartzLogger.Info($"入库完成,任务号: {task.TaskNum}", conveyorLine.DeviceCode);
            }
        }
@@ -226,6 +250,9 @@
                // 更新任务状态
                _taskService.UpdateTaskStatusToNext(task);
                _logger.LogInformation("RequestOutbound:出库任务已下发,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
                QuartzLogger.Info($"出库任务已下发,任务号: {task.TaskNum}", conveyorLine.DeviceCode);
            }
        }
@@ -245,8 +272,13 @@
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task == null)
            {
                _logger.LogDebug("RequestOutNextAddress:任务 {TaskNo} 不存在", command.TaskNo);
                QuartzLogger.Debug($"RequestOutNextAddress:任务 {command.TaskNo} 不存在", conveyorLine.DeviceCode);
                return;
            }
            _logger.LogInformation("RequestOutNextAddress:出库下一地址,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
            QuartzLogger.Info($"RequestOutNextAddress:出库下一地址,任务号: {task.TaskNum},子设备: {childDeviceCode}", conveyorLine.DeviceCode);
            // 如果不是空托盘任务,处理目标地址
            if (task.TaskType != (int)TaskOutboundTypeEnum.OutEmpty)
@@ -282,7 +314,9 @@
                // 更新任务状态到下一阶段(通常是完成)
                WebResponseContent content = _taskService.UpdateTaskStatusToNext(task);
                Console.Out.WriteLine(content.Serialize());
                _logger.LogInformation("ConveyorLineOutFinish:出库完成,任务号: {TaskNum},子设备: {ChildDeviceCode}", task.TaskNum, childDeviceCode);
                QuartzLogger.Info($"出库完成,任务号: {task.TaskNum}", conveyorLine.DeviceCode);
            }
        }
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -1,3 +1,5 @@
using Microsoft.Extensions.Logging;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks
@@ -44,6 +46,20 @@
        private static readonly List<string> PinMachineCodes = new List<string> { "10190", "20100" };
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="logger">日志记录器</param>
        public ConveyorLineTargetAddressSelector(ILogger logger)
        {
            _logger = logger;
        }
        /// <summary>
        /// 处理入库场景的下一地址请求
        /// </summary>
        /// <remarks>
@@ -55,6 +71,8 @@
        /// <param name="childDeviceCode">当前子设备编码</param>
        public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
        {
            _logger.LogDebug("HandleInboundNextAddress:入库下一地址,子设备: {ChildDeviceCode},目标地址: {NextAddress}", childDeviceCode, nextAddress);
            QuartzLogger.Debug($"HandleInboundNextAddress:入库下一地址,子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode);
            // 调用通用处理方法,isUpper = true 表示处理上层
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true);
        }
@@ -71,6 +89,8 @@
        /// <param name="childDeviceCode">当前子设备编码</param>
        public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
        {
            _logger.LogDebug("HandleOutboundNextAddress:出库下一地址,子设备: {ChildDeviceCode},目标地址: {NextAddress}", childDeviceCode, nextAddress);
            QuartzLogger.Debug($"HandleOutboundNextAddress:出库下一地址,子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode);
            // 调用通用处理方法,isUpper = false 表示处理下层
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false);
        }
@@ -99,6 +119,8 @@
                ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
                if (constraint == null)
                {
                    _logger.LogDebug("HandleDeviceRequest:未找到拘束机设备");
                    QuartzLogger.Debug("HandleDeviceRequest:未找到拘束机设备", conveyorLine.DeviceCode);
                    // 未找到拘束机设备,直接返回
                    return;
                }
@@ -126,7 +148,8 @@
                        {
                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0);
                        }
                    });
                    },
                    "拘束机");
            }
            else if (PinMachineCodes.Contains(nextAddress))
            {
@@ -135,6 +158,8 @@
                PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
                if (pinMachine == null)
                {
                    _logger.LogDebug("HandleDeviceRequest:未找到插拔钉机设备");
                    QuartzLogger.Debug("HandleDeviceRequest:未找到插拔钉机设备", conveyorLine.DeviceCode);
                    return;
                }
@@ -161,7 +186,8 @@
                        {
                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0);
                        }
                    });
                    },
                    "插拔钉机");
            }
        }
@@ -178,18 +204,24 @@
        /// <param name="getMaterialRequest">获取物料请求状态的委托</param>
        /// <param name="getOutputRequest">获取出料请求状态的委托</param>
        /// <param name="setOutputReady">设置输出就绪标志的委托</param>
        private static void ProcessDeviceRequest(
        /// <param name="deviceType">设备类型描述</param>
        private void ProcessDeviceRequest(
            CommonConveyorLine conveyorLine,
            string childDeviceCode,
            Func<bool> getMaterialRequest,
            Func<bool> getOutputRequest,
            Action<bool> setOutputReady)
            Action<bool> setOutputReady,
            string deviceType)
        {
            // 获取物料请求状态
            bool materialReq = getMaterialRequest();
            // 获取出料请求状态
            bool outputReq = getOutputRequest();
            _logger.LogDebug("ProcessDeviceRequest:{DeviceType},子设备: {ChildDeviceCode},物料请求: {MaterialReq},出料请求: {OutputReq}",
                deviceType, childDeviceCode, materialReq, outputReq);
            QuartzLogger.Debug($"ProcessDeviceRequest:{deviceType},子设备: {childDeviceCode},物料请求: {materialReq},出料请求: {outputReq}", conveyorLine.DeviceCode);
            // 如果设备需要物料
            if (materialReq)
@@ -199,6 +231,9 @@
                // 回复 ACK 确认信号
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
                _logger.LogInformation("ProcessDeviceRequest:{DeviceType} 需要物料,已设置目标地址和ACK", deviceType);
                QuartzLogger.Info($"ProcessDeviceRequest:{deviceType} 需要物料,已设置目标地址和ACK", conveyorLine.DeviceCode);
            }
            else
            {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs
@@ -1,3 +1,5 @@
using Microsoft.Extensions.Logging;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
@@ -26,12 +28,19 @@
        private readonly ITaskService _taskService;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="taskService">任务服务实例</param>
        public ConveyorLineTaskFilter(ITaskService taskService)
        /// <param name="logger">日志记录器</param>
        public ConveyorLineTaskFilter(ITaskService taskService, ILogger logger)
        {
            _taskService = taskService;
            _logger = logger;
        }
        /// <summary>
@@ -46,7 +55,10 @@
        /// <returns>待处理的任务对象,如果没有则返回 null</returns>
        public Dt_Task? QueryPendingTask(string deviceCode, string childDeviceCode)
        {
            return _taskService.QueryConveyorLineTask(deviceCode, childDeviceCode);
            var task = _taskService.QueryConveyorLineTask(deviceCode, childDeviceCode);
            _logger.LogDebug("QueryPendingTask:设备 {DeviceCode},子设备 {ChildDeviceCode},查询结果: {TaskNum}", deviceCode, childDeviceCode, task?.TaskNum);
            QuartzLogger.Debug($"QueryPendingTask:设备 {deviceCode},子设备 {childDeviceCode},查询结果: {task?.TaskNum}", deviceCode);
            return task;
        }
        /// <summary>
@@ -61,7 +73,10 @@
        /// <returns>执行中的任务对象,如果没有则返回 null</returns>
        public Dt_Task? QueryExecutingTask(int taskNo, string childDeviceCode)
        {
            return _taskService.QueryExecutingConveyorLineTask(taskNo, childDeviceCode);
            var task = _taskService.QueryExecutingConveyorLineTask(taskNo, childDeviceCode);
            _logger.LogDebug("QueryExecutingTask:任务号 {TaskNo},子设备 {ChildDeviceCode},查询结果: {Found}", taskNo, childDeviceCode, task != null);
            QuartzLogger.Debug($"QueryExecutingTask:任务号 {taskNo},子设备 {childDeviceCode},查询结果: {(task != null)}", childDeviceCode);
            return task;
        }
        /// <summary>
@@ -76,7 +91,10 @@
        /// <returns>请求是否成功</returns>
        public bool RequestWmsTask(string barcode, string childDeviceCode)
        {
            return _taskService.RequestWMSTask(barcode, childDeviceCode).Status;
            _logger.LogInformation("RequestWmsTask:向WMS请求任务,条码: {Barcode},子设备: {ChildDeviceCode}", barcode, childDeviceCode);
            QuartzLogger.Info($"向WMS请求任务,条码: {barcode}", childDeviceCode);
            var result = _taskService.RequestWMSTask(barcode, childDeviceCode);
            return result.Status;
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotClientManager.cs
@@ -1,5 +1,6 @@
using System.Collections.Concurrent;
using System.Net.Sockets;
using Microsoft.Extensions.Logging;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_Tasks.SocketServer;
@@ -27,6 +28,11 @@
        /// 机械手状态管理器,用于读写设备状态
        /// </summary>
        private readonly RobotStateManager _stateManager;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 跟踪已启动消息处理的客户端,避免重复启动
@@ -59,10 +65,12 @@
        /// </summary>
        /// <param name="tcpSocket">TCP Socket 服务器实例</param>
        /// <param name="stateManager">状态管理器实例</param>
        public RobotClientManager(TcpSocketServer tcpSocket, RobotStateManager stateManager)
        /// <param name="logger">日志记录器</param>
        public RobotClientManager(TcpSocketServer tcpSocket, RobotStateManager stateManager, ILogger logger)
        {
            _tcpSocket = tcpSocket;
            _stateManager = stateManager;
            _logger = logger;
        }
        /// <summary>
@@ -92,6 +100,8 @@
                // 清理该客户端的 HandleClientAsync 启动标志
                // 以便下次重连时可以重新启动处理
                _handleClientStarted.TryRemove(ipAddress, out _);
                _logger.LogDebug("客户端未连接,IP: {IpAddress}", ipAddress);
                QuartzLogger.Debug($"客户端未连接,IP: {ipAddress}", robotCrane.DeviceName);
                return false;
            }
@@ -101,8 +111,9 @@
            {
                // 绑定客户端断开连接的事件处理
                _tcpSocket.RobotReceived += OnRobotReceived;
                // 记录日志(注意:日志内容为"客户端已断开连接",可能是遗留的占位文本)
                QuartzLogger.Warn($"客户端已断开连接", robotCrane.DeviceName);
                // 记录日志:事件订阅成功
                _logger.LogInformation("机械手TCP消息事件已订阅,设备: {DeviceName}", robotCrane.DeviceName);
                QuartzLogger.Info($"机械手TCP消息事件已订阅", robotCrane.DeviceName);
            }
            // 从 TCP 服务器的客户端字典中获取 TcpClient 对象
@@ -114,6 +125,8 @@
            {
                // 移除启动标志,返回 false 表示客户端不可用
                _handleClientStarted.TryRemove(ipAddress, out _);
                _logger.LogWarning("获取TcpClient失败,IP: {IpAddress}", ipAddress);
                QuartzLogger.Warn($"获取TcpClient失败,IP: {ipAddress}", robotCrane.DeviceName);
                return false;
            }
@@ -123,7 +136,8 @@
            // 如果尚未启动,则启动消息处理循环
            if (!alreadyStarted)
            {
                // 记录日志
                // 记录日志:启动消息处理
                _logger.LogInformation("启动客户端消息处理,IP: {IpAddress}", ipAddress);
                QuartzLogger.Info($"启动客户端消息处理", robotCrane.DeviceName);
                // 获取最新的状态对象
@@ -142,8 +156,8 @@
                            if (t.IsFaulted)
                            {
                                // 记录错误日志
                                QuartzLogger.Info($"监听客户端消息事件异常", robotCrane.DeviceName);
                                Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] HandleClientAsync error: {t.Exception?.GetBaseException().Message}");
                                _logger.LogError(t.Exception, "监听客户端消息事件异常,IP: {IpAddress}", ipAddress);
                                QuartzLogger.Error($"监听客户端消息事件异常", robotCrane.DeviceName, t.Exception);
                                // 发生错误时,移除启动标志,允许下次重试
                                _handleClientStarted.TryRemove(ipAddress, out _);
                            }
@@ -179,6 +193,10 @@
            // 移除该客户端的 HandleClientAsync 启动标志
            _handleClientStarted.TryRemove(clientId, out _);
            // 记录日志:客户端断开连接
            _logger.LogInformation("客户端断开连接,IP: {ClientId}", clientId);
            QuartzLogger.Info($"客户端断开连接", clientId);
            // 重置该客户端的状态信息
            _stateManager.TryUpdateStateSafely(clientId, state =>
            {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -116,7 +116,7 @@
            ILogger<RobotJob> logger)
        {
            // 初始化状态管理器,传入缓存服务
            _stateManager = new RobotStateManager(cache);
            _stateManager = new RobotStateManager(cache, _logger);
            _logger = logger;
            // 创建 Socket 网关,封装 TcpSocketServer 的访问
@@ -124,10 +124,10 @@
            ISocketClientGateway socketGateway = new SocketClientGateway(tcpSocket);
            // 初始化任务处理器
            _taskProcessor = new RobotTaskProcessor(socketGateway, _stateManager, robotTaskService, taskService, httpClientHelper);
            _taskProcessor = new RobotTaskProcessor(socketGateway, _stateManager, robotTaskService, taskService, httpClientHelper, _logger);
            // 初始化客户端管理器
            _clientManager = new RobotClientManager(tcpSocket, _stateManager);
            _clientManager = new RobotClientManager(tcpSocket, _stateManager, _logger);
            // 初始化命令处理器
            // 简单命令处理器:处理状态更新等简单命令
@@ -139,7 +139,7 @@
            _messageRouter = new RobotMessageHandler(socketGateway, _stateManager, cache, simpleCommandHandler, prefixCommandHandler, logger);
            // 初始化工作流编排器
            _workflowOrchestrator = new RobotWorkflowOrchestrator(_stateManager, _clientManager, _taskProcessor, robotTaskService);
            _workflowOrchestrator = new RobotWorkflowOrchestrator(_stateManager, _clientManager, _taskProcessor, robotTaskService, _logger);
            // 订阅客户端断开连接事件
            _clientManager.OnClientDisconnected += OnClientDisconnected;
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotStateManager.cs
@@ -1,6 +1,8 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using WIDESEAWCS_Common;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks
@@ -20,12 +22,19 @@
        private readonly ICacheService _cache;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cache">缓存服务实例(通常为 HybridCacheService)</param>
        public RobotStateManager(ICacheService cache)
        /// <param name="logger">日志记录器</param>
        public RobotStateManager(ICacheService cache, ILogger logger)
        {
            _cache = cache;
            _logger = logger;
        }
        /// <summary>
@@ -100,6 +109,8 @@
                newState.Version = DateTime.UtcNow.Ticks;
                // 直接添加到缓存
                _cache.AddObject(cacheKey, newState);
                _logger.LogDebug("TryUpdateStateSafely:创建新状态,IP: {IpAddress}", ipAddress);
                QuartzLogger.Debug($"创建新状态,IP: {ipAddress}", ipAddress);
                return true;
            }
@@ -110,12 +121,20 @@
            newState.Version = DateTime.UtcNow.Ticks;
            // 尝试安全更新,如果版本冲突则返回 false
            return _cache.TrySafeUpdate(
            bool success = _cache.TrySafeUpdate(
                cacheKey,
                newState,
                expectedVersion,
                s => s.Version
            );
            if (!success)
            {
                _logger.LogWarning("TryUpdateStateSafely:版本冲突,更新失败,IP: {IpAddress},期望版本: {ExpectedVersion}", ipAddress, expectedVersion);
                QuartzLogger.Warn($"版本冲突,更新失败,IP: {ipAddress}", ipAddress);
            }
            return success;
        }
        /// <summary>
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using WIDESEA_Core;
using WIDESEAWCS_Common;
@@ -68,6 +69,11 @@
        private readonly HttpClientHelper _httpClientHelper;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="socketClientGateway">Socket 网关</param>
@@ -75,18 +81,21 @@
        /// <param name="robotTaskService">机器人任务服务</param>
        /// <param name="taskService">通用任务服务</param>
        /// <param name="httpClientHelper">HTTP 客户端帮助类</param>
        /// <param name="logger">日志记录器</param>
        public RobotTaskProcessor(
            ISocketClientGateway socketClientGateway,
            RobotStateManager stateManager,
            IRobotTaskService robotTaskService,
            ITaskService taskService,
            HttpClientHelper httpClientHelper)
            HttpClientHelper httpClientHelper,
            ILogger logger)
        {
            _socketClientGateway = socketClientGateway;
            _stateManager = stateManager;
            _robotTaskService = robotTaskService;
            _taskService = taskService;
            _httpClientHelper = httpClientHelper;
            _logger = logger;
        }
        /// <summary>
@@ -141,8 +150,9 @@
            if (result)
            {
                // 发送成功,记录日志
                QuartzLogger.Error($"下发取货指令,指令: {taskString}", state.RobotCrane.DeviceName);
                // 发送成功,记录 Info 日志
                _logger.LogInformation("下发取货指令成功,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发取货指令成功,指令: {taskString}", state.RobotCrane?.DeviceName);
                // 更新任务状态为"机器人执行中"
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
@@ -156,6 +166,12 @@
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                // 发送失败,记录 Error 日志
                _logger.LogError("下发取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
@@ -182,6 +198,8 @@
            var currentTask = state.CurrentTask;
            if (currentTask == null)
            {
                _logger.LogDebug("HandleInboundTaskAsync:当前任务为空");
                QuartzLogger.Debug($"HandleInboundTaskAsync:当前任务为空", state.RobotCrane?.DeviceName ?? "Unknown");
                return false;
            }
@@ -213,6 +231,8 @@
                {
                    case RobotTaskTypeEnum.GroupPallet:
                        // 组盘任务不使用源地址,直接返回 false
                        _logger.LogDebug("HandleInboundTaskAsync:组盘任务不使用源地址");
                        QuartzLogger.Debug($"HandleInboundTaskAsync:组盘任务不使用源地址", state.RobotCrane?.DeviceName ?? "Unknown");
                        return false;
                    case RobotTaskTypeEnum.ChangePallet:
@@ -237,6 +257,8 @@
                    case RobotTaskTypeEnum.SplitPallet:
                        // 拆盘任务不使用目标地址
                        _logger.LogDebug("HandleInboundTaskAsync:拆盘任务不使用目标地址");
                        QuartzLogger.Debug($"HandleInboundTaskAsync:拆盘任务不使用目标地址", state.RobotCrane?.DeviceName ?? "Unknown");
                        return true;
                }
            }
@@ -253,12 +275,18 @@
                TaskType = taskType                         // 任务类型(入库/空托盘入库)
            };
            // 记录日志:开始调用 WMS 创建入库任务
            _logger.LogInformation("HandleInboundTaskAsync:调用WMS创建入库任务,托盘码: {PalletCode},任务类型: {TaskType}", PalletCode, taskType);
            QuartzLogger.Info($"调用WMS创建入库任务,托盘码: {PalletCode},任务类型: {taskType}", state.RobotCrane?.DeviceName ?? "Unknown");
            // 调用 WMS 接口创建入库任务
            var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.CreateTaskInboundAsync), taskDto.ToJson());
            // 如果调用失败或返回错误状态
            if (!result.Data.Status && result.IsSuccess)
            {
                _logger.LogError("HandleInboundTaskAsync:WMS返回错误状态,Status: {Status}", result.Data.Status);
                QuartzLogger.Error($"HandleInboundTaskAsync:WMS返回错误状态", state.RobotCrane?.DeviceName ?? "Unknown");
                return false;
            }
@@ -269,6 +297,8 @@
            var content = _taskService.ReceiveWMSTask(new List<WMSTaskDTO> { taskDTO });
            if (!content.Status)
            {
                _logger.LogError("HandleInboundTaskAsync:接收WMS任务失败");
                QuartzLogger.Error($"HandleInboundTaskAsync:接收WMS任务失败", state.RobotCrane?.DeviceName ?? "Unknown");
                return false;
            }
@@ -298,6 +328,8 @@
                // 更新任务状态到下一阶段
                if (_taskService.UpdateTaskStatusToNext(taskInfo).Status)
                {
                    _logger.LogInformation("HandleInboundTaskAsync:入库任务处理成功,任务号: {TaskNum}", taskInfo.TaskNum);
                    QuartzLogger.Info($"HandleInboundTaskAsync:入库任务处理成功,任务号: {taskInfo.TaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                    return true;
                }
            }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using WIDESEA_Core;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.LogHelper;
@@ -58,22 +59,30 @@
        private readonly IRobotTaskService _robotTaskService;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="stateManager">状态管理器</param>
        /// <param name="clientManager">客户端管理器</param>
        /// <param name="taskProcessor">任务处理器</param>
        /// <param name="robotTaskService">任务服务</param>
        /// <param name="logger">日志记录器</param>
        public RobotWorkflowOrchestrator(
            RobotStateManager stateManager,
            RobotClientManager clientManager,
            RobotTaskProcessor taskProcessor,
            IRobotTaskService robotTaskService)
            IRobotTaskService robotTaskService,
            ILogger logger)
        {
            _stateManager = stateManager;
            _clientManager = clientManager;
            _taskProcessor = taskProcessor;
            _robotTaskService = robotTaskService;
            _logger = logger;
        }
        /// <summary>
@@ -112,6 +121,8 @@
                    && latestState.RobotArmObject == 1
                    && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    _logger.LogInformation("ExecuteAsync:满足放货条件,开始处理取货完成,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足放货条件,开始处理取货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    // 发送放货指令
                    await HandlePickFinishedStateAsync(task, ipAddress);
                }
@@ -127,6 +138,8 @@
                    && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
                    || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
                {
                    _logger.LogInformation("ExecuteAsync:满足取货条件,开始处理放货完成,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Info($"ExecuteAsync:满足取货条件,开始处理放货完成", latestState.RobotCrane?.DeviceName ?? ipAddress);
                    // 发送取货指令
                    await HandlePutFinishedStateAsync(task, ipAddress);
                }
@@ -155,8 +168,9 @@
            if (result)
            {
                // 发送成功,记录日志
                QuartzLogger.Info($"下发放货指令,指�?: {taskString}", task.RobotRoadway);
                // 发送成功,记录 Info 日志
                _logger.LogInformation("HandlePickFinishedStateAsync:下发放货指令成功,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Info($"下发放货指令成功,指令: {taskString}", task.RobotRoadway);
                // 更新任务状态为"机器人执行中"
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
@@ -174,6 +188,12 @@
                        await _robotTaskService.UpdateRobotTaskAsync(task);
                    }
                }
            }
            else
            {
                // 发送失败,记录 Error 日志
                _logger.LogError("HandlePickFinishedStateAsync:下发放货指令失败,指令: {TaskString},任务号: {TaskNum}", taskString, task.RobotTaskNum);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", task.RobotRoadway);
            }
        }
@@ -202,6 +222,8 @@
            var stateForUpdate = _stateManager.GetState(ipAddress);
            if (stateForUpdate == null)
            {
                _logger.LogWarning("HandlePutFinishedStateAsync:获取状态失败,IP: {IpAddress}", ipAddress);
                QuartzLogger.Warn($"HandlePutFinishedStateAsync:获取状态失败,IP: {ipAddress}", ipAddress);
                return;
            }
@@ -231,12 +253,19 @@
                    stateForUpdate.CellBarcode.Add(trayBarcode1);
                    stateForUpdate.CellBarcode.Add(trayBarcode2);
                    // 记录日志
                    QuartzLogger.Info($"ȡ�������о�ţ���о: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName);
                    // 记录日志:生成托盘条码成功
                    _logger.LogInformation("HandlePutFinishedStateAsync:生成托盘条码成功: {Barcode1}+{Barcode2},任务号: {TaskNum}", trayBarcode1, trayBarcode2, task.RobotTaskNum);
                    QuartzLogger.Info($"生成托盘条码成功: {trayBarcode1}+{trayBarcode2}", stateForUpdate.RobotCrane.DeviceName);
                    // 发送取货指令
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
                else
                {
                    // 条码生成失败,记录错误日志
                    _logger.LogError("HandlePutFinishedStateAsync:生成托盘条码失败,任务号: {TaskNum}", task.RobotTaskNum);
                    QuartzLogger.Error($"生成托盘条码失败", stateForUpdate.RobotCrane.DeviceName);
                }
            }
            else
            {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Quartz;
using System;
using System.Diagnostics.CodeAnalysis;
@@ -11,6 +12,7 @@
using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_Tasks.StackerCraneJob;
using WIDESEA_Core;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_QuartzJob.Service;
namespace WIDESEAWCS_Tasks
@@ -79,6 +81,16 @@
        private readonly StackerCraneCommandBuilder _commandBuilder;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger<CommonStackerCraneJob> _logger;
        /// <summary>
        /// 堆垛机设备编码
        /// </summary>
        private string _deviceCode = string.Empty;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="taskService">任务服务</param>
@@ -86,25 +98,28 @@
        /// <param name="taskRepository">任务仓储</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="httpClientHelper">HTTP 客户端帮助类</param>
        /// <param name="logger">日志记录器</param>
        public CommonStackerCraneJob(
            ITaskService taskService,
            ITaskExecuteDetailService taskExecuteDetailService,
            ITaskRepository taskRepository,
            IRouterService routerService,
            HttpClientHelper httpClientHelper)
            HttpClientHelper httpClientHelper,
            ILogger<CommonStackerCraneJob> logger)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _taskRepository = taskRepository;
            _logger = logger;
            // 加载配置文件
            _config = LoadConfig();
            // 初始化任务选择器
            _taskSelector = new StackerCraneTaskSelector(taskService, routerService, httpClientHelper);
            _taskSelector = new StackerCraneTaskSelector(taskService, routerService, httpClientHelper, _logger);
            // 初始化命令构建器
            _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _config);
            _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _config, _logger);
        }
        /// <summary>
@@ -161,23 +176,33 @@
                if (!flag || value is not IStackerCrane commonStackerCrane)
                {
                    // 参数无效,直接返回
                    _logger.LogWarning("Execute:参数无效");
                    QuartzLogger.Warn("Execute:参数无效", "CommonStackerCraneJob");
                    return Task.CompletedTask;
                }
                _deviceCode = commonStackerCrane.DeviceCode;
                // ========== 订阅任务完成事件(全局只订阅一次) ==========
                if (!commonStackerCrane.IsEventSubscribed)
                {
                    // 绑定任务完成事件处理方法
                    commonStackerCrane.StackerCraneTaskCompletedEventHandler += CommonStackerCrane_StackerCraneTaskCompletedEventHandler;
                    _logger.LogInformation("Execute:订阅任务完成事件,设备: {DeviceCode}", _deviceCode);
                    QuartzLogger.Info($"订阅任务完成事件", _deviceCode);
                }
                // ========== 检查堆垛机任务完成状态 ==========
                commonStackerCrane.CheckStackerCraneTaskCompleted();
                _logger.LogDebug("Execute:检查任务完成状态,设备: {DeviceCode}", _deviceCode);
                QuartzLogger.Debug($"检查任务完成状态,设备: {_deviceCode}", _deviceCode);
                // ========== 检查是否可以发送新任务 ==========
                if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                {
                    // 堆垛机不可用(如正在执行上一任务),直接返回
                    _logger.LogDebug("Execute:堆垛机不可用,设备: {DeviceCode}", _deviceCode);
                    QuartzLogger.Debug($"堆垛机不可用,设备: {_deviceCode}", _deviceCode);
                    return Task.CompletedTask;
                }
@@ -187,8 +212,13 @@
                if (task == null)
                {
                    // 没有可用任务
                    _logger.LogDebug("Execute:没有可用任务,设备: {DeviceCode}", _deviceCode);
                    QuartzLogger.Debug($"没有可用任务,设备: {_deviceCode}", _deviceCode);
                    return Task.CompletedTask;
                }
                _logger.LogInformation("Execute:选择任务,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                QuartzLogger.Info($"选择任务,任务号: {task.TaskNum}", _deviceCode);
                // ========== 构建命令 ==========
                // 命令构建下沉到专用构建器
@@ -196,6 +226,8 @@
                if (stackerCraneTaskCommand == null)
                {
                    // 命令构建失败
                    _logger.LogWarning("Execute:命令构建失败,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                    QuartzLogger.Warn($"命令构建失败,任务号: {task.TaskNum}", _deviceCode);
                    return Task.CompletedTask;
                }
@@ -206,12 +238,21 @@
                    // 发送成功,更新状态
                    commonStackerCrane.LastTaskType = task.TaskType;
                    _taskService.UpdateTaskStatusToNext(task.TaskNum);
                    _logger.LogInformation("Execute:命令发送成功,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                    QuartzLogger.Info($"命令发送成功,任务号: {task.TaskNum}", _deviceCode);
                }
                else
                {
                    _logger.LogError("Execute:命令发送失败,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                    QuartzLogger.Error($"命令发送失败", _deviceCode);
                }
            }
            catch (Exception ex)
            {
                // 记录异常
                Console.WriteLine($"CommonStackerCraneJob Error: {ex.Message}");
                _logger.LogError(ex, "Execute:执行异常,设备: {DeviceCode}", _deviceCode);
                QuartzLogger.Error($"执行异常: {ex.Message}", _deviceCode, ex);
            }
            return Task.CompletedTask;
@@ -235,7 +276,8 @@
            if (stackerCrane != null)
            {
                // 记录日志
                Console.Out.WriteLine("TaskCompleted" + e.TaskNum);
                _logger.LogInformation("CommonStackerCrane_StackerCraneTaskCompletedEventHandler:任务完成,任务号: {TaskNum}", e.TaskNum);
                QuartzLogger.Info($"任务完成,任务号: {e.TaskNum}", stackerCrane.DeviceCode);
                // 更新任务状态为完成
                _taskService.StackCraneTaskCompleted(e.TaskNum);
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs
@@ -1,6 +1,8 @@
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob.Models;
@@ -39,19 +41,27 @@
        private readonly StackerCraneCommandConfig _config;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="taskService">任务服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="config">命令配置</param>
        /// <param name="logger">日志记录器</param>
        public StackerCraneCommandBuilder(
            ITaskService taskService,
            IRouterService routerService,
            StackerCraneCommandConfig config)
            StackerCraneCommandConfig config,
            ILogger logger)
        {
            _taskService = taskService;
            _routerService = routerService;
            _config = config;
            _logger = logger;
        }
        /// <summary>
@@ -66,6 +76,9 @@
        {
            // 根据巷道获取命令类型
            string commandType = GetCommandType(task.Roadway);
            _logger.LogInformation("ConvertToStackerCraneTaskCommand:构建命令,任务号: {TaskNum},巷道: {Roadway},命令类型: {CommandType}", task.TaskNum, task.Roadway, commandType);
            QuartzLogger.Info($"构建命令,任务号: {task.TaskNum},巷道: {task.Roadway},命令类型: {commandType}", task.Roadway);
            // 根据命令类型调用相应的构建方法
            return commandType switch
@@ -90,10 +103,14 @@
            {
                if (roadway.Contains(mapping.Key))
                {
                    _logger.LogDebug("GetCommandType:匹配巷道 {Roadway},命令类型: {CommandType}", roadway, mapping.Value);
                    QuartzLogger.Debug($"GetCommandType:匹配巷道 {roadway},命令类型: {mapping.Value}", roadway);
                    return mapping.Value;
                }
            }
            _logger.LogDebug("GetCommandType:巷道 {Roadway} 未匹配,使用默认命令类型: {DefaultType}", roadway, _config.DefaultCommandType);
            QuartzLogger.Debug($"GetCommandType:巷道 {roadway} 未匹配,使用默认命令类型: {_config.DefaultCommandType}", roadway);
            return _config.DefaultCommandType;
        }
@@ -153,6 +170,9 @@
            // 获取任务类型分组
            TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
            _logger.LogDebug("BuildCommand:任务号: {TaskNum},任务类型分组: {TaskTypeGroup}", task.TaskNum, taskTypeGroup);
            QuartzLogger.Debug($"BuildCommand:任务号: {task.TaskNum},任务类型分组: {taskTypeGroup}", task.Roadway);
            // 根据任务类型分发构建
            return taskTypeGroup switch
            {
@@ -178,6 +198,9 @@
        /// <returns>填充好的命令对象</returns>
        private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class
        {
            _logger.LogInformation("BuildInboundCommand:构建入库命令,任务号: {TaskNum}", task.TaskNum);
            QuartzLogger.Info($"BuildInboundCommand:构建入库命令,任务号: {task.TaskNum}", task.Roadway);
            // 确定任务类型(空托盘用特殊类型 100)
            int taskType = 0;
            if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
@@ -192,6 +215,8 @@
            if (router == null)
            {
                // 未找到站台,更新异常信息
                _logger.LogError("BuildInboundCommand:未找到站台【{CurrentAddress}】信息,任务号: {TaskNum}", task.CurrentAddress, task.TaskNum);
                QuartzLogger.Error($"BuildInboundCommand:未找到站台【{task.CurrentAddress}】信息", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.CurrentAddress}】信息,无法获取对应的堆垛机取货站台信息");
                return null;
            }
@@ -204,6 +229,8 @@
            // 解析目标地址(库位地址)
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _logger.LogError("BuildInboundCommand:入库任务终点地址解析失败,终点: {NextAddress},任务号: {TaskNum}", task.NextAddress, task.TaskNum);
                QuartzLogger.Error($"BuildInboundCommand:入库任务终点地址解析失败,终点: {task.NextAddress}", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"入库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
@@ -212,6 +239,10 @@
            SetCommandProperty(command, "EndRow", endRow);
            SetCommandProperty(command, "EndColumn", endColumn);
            SetCommandProperty(command, "EndLayer", endLayer);
            _logger.LogInformation("BuildInboundCommand:入库命令构建成功,起点: {StartRow}-{StartColumn}-{StartLayer},终点: {EndRow}-{EndColumn}-{EndLayer},任务号: {TaskNum}",
                router.SrmRow, router.SrmColumn, router.SrmLayer, endRow, endColumn, endLayer, task.TaskNum);
            QuartzLogger.Info($"BuildInboundCommand:入库命令构建成功,起点: {router.SrmRow}-{router.SrmColumn}-{router.SrmLayer},终点: {endRow}-{endColumn}-{endLayer}", task.Roadway);
            return command;
        }
@@ -231,6 +262,9 @@
        /// <returns>填充好的命令对象</returns>
        private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class
        {
            _logger.LogInformation("BuildOutboundCommand:构建出库命令,任务号: {TaskNum}", task.TaskNum);
            QuartzLogger.Info($"BuildOutboundCommand:构建出库命令,任务号: {task.TaskNum}", task.Roadway);
            // 确定任务类型
            int taskType = 0;
            if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
@@ -244,6 +278,8 @@
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType);
            if (router == null)
            {
                _logger.LogError("BuildOutboundCommand:未找到站台【{TargetAddress}】信息,任务号: {TaskNum}", task.TargetAddress, task.TaskNum);
                QuartzLogger.Error($"BuildOutboundCommand:未找到站台【{task.TargetAddress}】信息", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.TargetAddress}】信息,无法获取对应的堆垛机放货站台信息");
                return null;
            }
@@ -256,6 +292,8 @@
            // 解析起始地址(库位地址)
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _logger.LogError("BuildOutboundCommand:出库任务起点地址解析失败,起点: {CurrentAddress},任务号: {TaskNum}", task.CurrentAddress, task.TaskNum);
                QuartzLogger.Error($"BuildOutboundCommand:出库任务起点地址解析失败,起点: {task.CurrentAddress}", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"出库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
@@ -264,6 +302,10 @@
            SetCommandProperty(command, "StartRow", startRow);
            SetCommandProperty(command, "StartColumn", startColumn);
            SetCommandProperty(command, "StartLayer", startLayer);
            _logger.LogInformation("BuildOutboundCommand:出库命令构建成功,起点: {StartRow}-{StartColumn}-{StartLayer},终点: {EndRow}-{EndColumn}-{EndLayer},任务号: {TaskNum}",
                startRow, startColumn, startLayer, router.SrmRow, router.SrmColumn, router.SrmLayer, task.TaskNum);
            QuartzLogger.Info($"BuildOutboundCommand:出库命令构建成功,起点: {startRow}-{startColumn}-{startLayer},终点: {router.SrmRow}-{router.SrmColumn}-{router.SrmLayer}", task.Roadway);
            return command;
        }
@@ -283,9 +325,14 @@
        /// <returns>填充好的命令对象</returns>
        private T? BuildRelocationCommand<T>(Dt_Task task, T command) where T : class
        {
            _logger.LogInformation("BuildRelocationCommand:构建移库命令,任务号: {TaskNum}", task.TaskNum);
            QuartzLogger.Info($"BuildRelocationCommand:构建移库命令,任务号: {task.TaskNum}", task.Roadway);
            // 解析目标地址
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _logger.LogError("BuildRelocationCommand:移库任务终点地址解析失败,终点: {NextAddress},任务号: {TaskNum}", task.NextAddress, task.TaskNum);
                QuartzLogger.Error($"BuildRelocationCommand:移库任务终点地址解析失败,终点: {task.NextAddress}", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
@@ -298,6 +345,8 @@
            // 解析起始地址
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _logger.LogError("BuildRelocationCommand:移库任务起点地址解析失败,起点: {CurrentAddress},任务号: {TaskNum}", task.CurrentAddress, task.TaskNum);
                QuartzLogger.Error($"BuildRelocationCommand:移库任务起点地址解析失败,起点: {task.CurrentAddress}", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
@@ -307,6 +356,10 @@
            SetCommandProperty(command, "StartColumn", startColumn);
            SetCommandProperty(command, "StartLayer", startLayer);
            _logger.LogInformation("BuildRelocationCommand:移库命令构建成功,起点: {StartRow}-{StartColumn}-{StartLayer},终点: {EndRow}-{EndColumn}-{EndLayer},任务号: {TaskNum}",
                startRow, startColumn, startLayer, endRow, endColumn, endLayer, task.TaskNum);
            QuartzLogger.Info($"BuildRelocationCommand:移库命令构建成功,起点: {startRow}-{startColumn}-{startLayer},终点: {endRow}-{endColumn}-{endLayer}", task.Roadway);
            return command;
        }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
@@ -1,9 +1,11 @@
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Diagnostics.CodeAnalysis;
using WIDESEA_Core;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
@@ -40,6 +42,11 @@
        private readonly IRouterService _routerService;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
        /// <summary>
        /// 移库检查委托函数
        /// </summary>
        /// <remarks>
@@ -53,8 +60,9 @@
        /// <param name="taskService">任务服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="httpClientHelper">HTTP 客户端帮助类</param>
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper)
            : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum))
        /// <param name="logger">日志记录器</param>
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, HttpClientHelper httpClientHelper, ILogger logger)
            : this(taskService, routerService, taskNum => QueryTransferTask(httpClientHelper, taskNum), logger)
        {
        }
@@ -64,11 +72,13 @@
        /// <param name="taskService">任务服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="transferCheck">移库检查函数</param>
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, Func<int, Dt_Task?> transferCheck)
        /// <param name="logger">日志记录器</param>
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService, Func<int, Dt_Task?> transferCheck, ILogger logger)
        {
            _taskService = taskService;
            _routerService = routerService;
            _transferCheck = transferCheck;
            _logger = logger;
        }
        /// <summary>
@@ -88,35 +98,49 @@
        public Dt_Task? SelectTask(IStackerCrane commonStackerCrane)
        {
            Dt_Task? candidateTask;
            var deviceCode = commonStackerCrane.DeviceCode;
            _logger.LogInformation("SelectTask:开始选择任务,设备: {DeviceCode},上一任务类型: {LastTaskType}", deviceCode, commonStackerCrane.LastTaskType);
            QuartzLogger.Info($"开始选择任务,设备: {deviceCode},上一任务类型: {commonStackerCrane.LastTaskType}", deviceCode);
            // 根据上一任务类型决定查询策略
            if (commonStackerCrane.LastTaskType == null)
            {
                // 没有上一任务类型,查询普通任务
                candidateTask = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
                candidateTask = _taskService.QueryStackerCraneTask(deviceCode);
                _logger.LogDebug("SelectTask:查询普通任务,设备: {DeviceCode},结果: {TaskNum}", deviceCode, candidateTask?.TaskNum);
                QuartzLogger.Debug($"查询普通任务,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode);
            }
            else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                // 上一任务是出库,优先查入库任务
                candidateTask = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                candidateTask = _taskService.QueryStackerCraneInTask(deviceCode);
                // 如果没有入库任务,再查一下出库任务
                candidateTask ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                candidateTask ??= _taskService.QueryStackerCraneOutTask(deviceCode);
                _logger.LogDebug("SelectTask:出库后优先查入库,设备: {DeviceCode},结果: {TaskNum}", deviceCode, candidateTask?.TaskNum);
                QuartzLogger.Debug($"出库后优先查入库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode);
            }
            else
            {
                // 上一任务是入库(非出库),优先查出库任务
                candidateTask = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                candidateTask = _taskService.QueryStackerCraneOutTask(deviceCode);
                _logger.LogDebug("SelectTask:入库后优先查出库,设备: {DeviceCode},结果: {TaskNum}", deviceCode, candidateTask?.TaskNum);
                QuartzLogger.Debug($"入库后优先查出库,设备: {deviceCode},结果: {candidateTask?.TaskNum}", deviceCode);
            }
            // 如果没有候选任务,返回 null
            if (candidateTask == null)
            {
                _logger.LogDebug("SelectTask:没有候选任务,设备: {DeviceCode}", deviceCode);
                QuartzLogger.Debug($"没有候选任务,设备: {deviceCode}", deviceCode);
                return null;
            }
            // 如果不是出库任务,直接返回
            if (candidateTask.TaskType.GetTaskTypeGroup() != TaskTypeGroup.OutbondGroup)
            {
                _logger.LogInformation("SelectTask:选中非出库任务,设备: {DeviceCode},任务号: {TaskNum},任务类型: {TaskType}", deviceCode, candidateTask.TaskNum, candidateTask.TaskType);
                QuartzLogger.Info($"选中非出库任务,任务号: {candidateTask.TaskNum},任务类型: {candidateTask.TaskType}", deviceCode);
                return candidateTask;
            }
@@ -124,28 +148,35 @@
            Dt_Task? selectedTask = TrySelectOutboundTask(candidateTask);
            if (selectedTask != null)
            {
                _logger.LogInformation("SelectTask:选中出库任务,设备: {DeviceCode},任务号: {TaskNum}", deviceCode, selectedTask.TaskNum);
                QuartzLogger.Info($"选中出库任务,任务号: {selectedTask.TaskNum}", deviceCode);
                return selectedTask;
            }
            // 查找其他可用的出库站台
            var otherOutStationCodes = _routerService
                .QueryNextRoutes(commonStackerCrane.DeviceCode, candidateTask.NextAddress, candidateTask.TaskType)
                .QueryNextRoutes(deviceCode, candidateTask.NextAddress, candidateTask.TaskType)
                .Select(x => x.ChildPosi)
                .ToList();
            // 查询其他站台的出库任务
            var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
            var tasks = _taskService.QueryStackerCraneOutTasks(deviceCode, otherOutStationCodes);
            foreach (var alternativeTask in tasks)
            {
                selectedTask = TrySelectOutboundTask(alternativeTask);
                if (selectedTask != null)
                {
                    _logger.LogInformation("SelectTask:选中备选出库任务,设备: {DeviceCode},任务号: {TaskNum}", deviceCode, selectedTask.TaskNum);
                    QuartzLogger.Info($"选中备选出库任务,任务号: {selectedTask.TaskNum}", deviceCode);
                    return selectedTask;
                }
            }
            // 没有可用出库任务,尝试返回入库任务
            return _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
            var inboundTask = _taskService.QueryStackerCraneInTask(deviceCode);
            _logger.LogInformation("SelectTask:返回入库任务,设备: {DeviceCode},任务号: {TaskNum}", deviceCode, inboundTask?.TaskNum);
            QuartzLogger.Info($"返回入库任务,任务号: {inboundTask?.TaskNum}", deviceCode);
            return inboundTask;
        }
        /// <summary>
@@ -267,6 +298,8 @@
            if (router == null)
            {
                // 未找到站台路由信息
                _logger.LogWarning("IsOutTaskStationAvailable:未找到站台路由信息,站台: {NextAddress},任务号: {TaskNum}", task.NextAddress, task.TaskNum);
                QuartzLogger.Warn($"IsOutTaskStationAvailable:未找到站台路由信息,站台: {task.NextAddress}", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
                return false;
            }
@@ -276,6 +309,8 @@
            if (device == null)
            {
                // 未找到设备
                _logger.LogWarning("IsOutTaskStationAvailable:未找到出库站台对应的通讯对象,站台: {ChildPosiDeviceCode},任务号: {TaskNum}", router.ChildPosiDeviceCode, task.TaskNum);
                QuartzLogger.Warn($"IsOutTaskStationAvailable:未找到出库站台对应的通讯对象,站台: {router.ChildPosiDeviceCode}", task.Roadway);
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
                return false;
            }
@@ -284,7 +319,11 @@
            CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            // 检查站台是否被占用
            return conveyorLine.IsOccupied(router.ChildPosi);
            bool isOccupied = conveyorLine.IsOccupied(router.ChildPosi);
            _logger.LogInformation("IsOutTaskStationAvailable:站台 {ChildPosi},是否被占用: {IsOccupied},任务号: {TaskNum}", router.ChildPosi, isOccupied, task.TaskNum);
            QuartzLogger.Info($"IsOutTaskStationAvailable:站台 {router.ChildPosi},是否被占用: {isOccupied}", task.Roadway);
            return !isOccupied;
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tests/StackerCraneTaskSelectorTests.cs
@@ -1,3 +1,4 @@
using Microsoft.Extensions.Logging;
using Moq;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
@@ -44,6 +45,7 @@
        taskService.Setup(x => x.AddData(It.IsAny<Dt_Task>())).Returns(WebResponseContent.Instance.OK());
        var transferCheckCalledCount = 0;
        var mockLogger = new Mock<ILogger>();
        var selector = new StackerCraneTaskSelector(
            taskService.Object,
            routerService.Object,
@@ -52,7 +54,8 @@
                transferCheckCalledCount++;
                Assert.Equal(1001, taskNum);
                return relocationTask;
            });
            },
            mockLogger.Object);
        var selectedTask = selector.SelectTask(stackerCrane.Object);
@@ -104,10 +107,12 @@
            .Setup(x => x.QueryNextRoutes("SC01", "OUT-01", (int)TaskOutboundTypeEnum.Outbound))
            .Returns(new List<WIDESEAWCS_QuartzJob.Models.Dt_Router>());
        var mockLogger = new Mock<ILogger>();
        var selector = new StackerCraneTaskSelector(
            taskService.Object,
            routerService.Object,
            _ => newOutboundTask);
            _ => newOutboundTask,
            mockLogger.Object);
        _ = selector.SelectTask(stackerCrane.Object);