wanshenmean
2026-03-11 a8f45091019012eeafec563913dee71cda3d9790
refactor: modularize WIDESEAWCS_Tasks workflows
已添加18个文件
已修改6个文件
2477 ■■■■■ 文件已修改
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs 166 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs 252 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotMessageRouter.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotPrefixCommandHandler.cs 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotSimpleCommandHandler.cs 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotWorkflowOrchestrator.cs 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/ISocketClientGateway.cs 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs 141 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs 138 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/SocketClientGateway.cs 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs 332 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-common-stacker-crane-job-refactor-plan.md 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-conveyorline-dispatch-handler-refactor-plan.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-formation-stacker-crane-job-refactor-plan.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs
@@ -1,24 +1,23 @@
#region << ç‰ˆ æœ¬ æ³¨ é‡Š >>
#region << ç‰ˆ æœ¬ æ³¨ é‡Š >>
/*----------------------------------------------------------------
 * å‘½åç©ºé—´ï¼šWIDESEAWCS_Tasks.ConveyorLineJob
 * åˆ›å»ºè€…:胡童庆
 * åˆ›å»ºæ—¶é—´ï¼š2024/8/2 16:13:36
 * ç‰ˆæœ¬ï¼šV1.0.0
 * æè¿°ï¼š
 * å‘½åç©ºé—´ï¼šWIDESEAWCS_Tasks.ConveyorLineJob
 * åˆ›å»ºè€…:胡童庆
 * åˆ›å»ºæ—¶é—´ï¼š2024/8/2 16:13:36
 * ç‰ˆæœ¬ï¼šV1.0.0
 * æè¿°ï¼š
 *
 * ----------------------------------------------------------------
 * ä¿®æ”¹äººï¼š
 * ä¿®æ”¹æ—¶é—´ï¼š
 * ç‰ˆæœ¬ï¼šV1.0.1
 * ä¿®æ”¹è¯´æ˜Žï¼š
 * ä¿®æ”¹äººï¼š
 * ä¿®æ”¹æ—¶é—´ï¼š
 * ç‰ˆæœ¬ï¼šV1.0.1
 * ä¿®æ”¹è¯´æ˜Žï¼š
 *
 *----------------------------------------------------------------*/
#endregion << ç‰ˆ æœ¬ æ³¨ é‡Š >>
#endregion << ç‰ˆ æœ¬ æ³¨ é‡Š >>
using AutoMapper;
using System.Data;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core;
using WIDESEAWCS_Core.Helper;
@@ -36,37 +35,36 @@
        private readonly IRouterService _routerService;
        private readonly IMapper _mapper;
        private readonly ConveyorLineTaskFilter _taskFilter;
        private readonly ConveyorLineTargetAddressSelector _targetAddressSelector;
        public ConveyorLineDispatchHandler(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _routerService = routerService;
            _mapper = mapper;
            _taskFilter = new ConveyorLineTaskFilter(taskService);
            _targetAddressSelector = new ConveyorLineTargetAddressSelector();
        }
        /// <summary>
        /// å¿ƒè·³å¤„理
        /// å¿ƒè·³å¤„理
        /// </summary>
        /// <param name="conveyorLine"></param>
        /// <param name="command"></param>
        /// <param name="childDeviceCode"></param>
        public void HeartBeat(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            //心跳处理逻辑
            conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, 0, childDeviceCode);
        }
        /// <summary>
        /// è¾“送线请求入库
        /// è¾“送线请求入库
        /// </summary>
        /// <param name="conveyorLine">输送线实例对象</param>
        /// <param name="command">读取的请求信息</param>
        /// <param name="childDeviceCode">子设备编号</param>
        public void RequestInbound(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            if (_taskService.RequestWMSTask(command.Barcode, childDeviceCode).Status)
            if (_taskFilter.RequestWmsTask(command.Barcode, childDeviceCode))
            {
                Dt_Task task = _taskService.QueryConveyorLineTask(conveyorLine.DeviceCode, childDeviceCode);
                Dt_Task? task = _taskFilter.QueryPendingTask(conveyorLine.DeviceCode, childDeviceCode);
                if (task != null)
                {
                    ConveyorLineTaskCommandNew taskCommand = _mapper.Map<ConveyorLineTaskCommandNew>(task);
@@ -79,41 +77,17 @@
        }
        /// <summary>
        /// è¾“送线请求入库下一地址
        /// è¾“送线请求入库下一地址
        /// </summary>
        /// <param name="conveyorLine">输送线实例对象</param>
        /// <param name="command">读取的请求信息</param>
        /// <param name="childDeviceCode">子设备编号</param>
        public void RequestInNextAddress(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
            if (task != null)
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task == null)
            {
                const string ConstraintMachineName = "拘束机";
                const string PinMachineName = "插拔钉机";
                var devices = Storage.Devices;
                if (string.Equals(task.NextAddress, ConstraintMachineName, StringComparison.Ordinal))
                {
                    ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
                    if (constraint == null) return;
                    ProcessDeviceRequest(conveyorLine, constraint, childDeviceCode,
                    () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestUpper),
                    () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestUpper),
                    outputReq => constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0));
                return;
                }
                else if (string.Equals(task.NextAddress, PinMachineName, StringComparison.Ordinal))
                {
                    PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
                    if (pinMachine == null) return;
                    ProcessDeviceRequest(conveyorLine, pinMachine, childDeviceCode,
                    () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestUpper),
                    () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestUpper),
                    outputReq => pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0));
                }
            _targetAddressSelector.HandleInboundNextAddress(conveyorLine, task.NextAddress, childDeviceCode);
                Dt_Task? newTask = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
                if (newTask != null)
@@ -124,17 +98,13 @@
                    }
                }
            }
        }
        /// <summary>
        /// è¾“送线入库完成
        /// è¾“送线入库完成
        /// </summary>
        /// <param name="conveyorLine">输送线实例对象</param>
        /// <param name="command">读取的请求信息</param>
        /// <param name="childDeviceCode">子设备编号</param>
        public void ConveyorLineInFinish(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task != null)
            {
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
@@ -144,20 +114,13 @@
        }
        /// <summary>
        /// è¾“送线请求出信息
        /// è¾“送线请求出信息
        /// </summary>
        /// <param name="conveyorLine">输送线实例对象</param>
        /// <param name="command">读取的请求信息</param>
        /// <param name="childDeviceCode">子设备编号</param>
        public void RequestOutbound(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            Dt_Task task = _taskService.QueryConveyorLineTask(conveyorLine.DeviceCode, childDeviceCode);
            Dt_Task? task = _taskFilter.QueryPendingTask(conveyorLine.DeviceCode, childDeviceCode);
            if (task != null)
            {
                //ConveyorLineTaskCommandNew taskCommand = _mapper.Map<ConveyorLineTaskCommandNew>(task);
                //taskCommand.WCS_ACK = command.WCS_ACK;
                //conveyorLine.SendCommand(taskCommand, childDeviceCode);
                conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, task.TaskNum, childDeviceCode);
                conveyorLine.SetValue(ConveyorLineDBNameNew.Barcode, task.PalletCode, childDeviceCode);
                conveyorLine.SetValue(ConveyorLineDBNameNew.Target, task.TargetAddress, childDeviceCode);
@@ -168,87 +131,32 @@
        }
        /// <summary>
        /// è¾“送线请求出库下一地址
        /// è¾“送线请求出库下一地址
        /// </summary>
        /// <param name="conveyorLine">输送线实例对象</param>
        /// <param name="command">读取的请求信息</param>
        /// <param name="childDeviceCode">子设备编号</param>
        public void RequestOutNextAddress(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
            if (task != null)
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task == null)
            {
                const string ConstraintMachineName = "拘束机";
                const string PinMachineName = "插拔钉机";
                var devices = Storage.Devices;
                if (string.Equals(task.NextAddress, ConstraintMachineName, StringComparison.Ordinal))
                {
                    ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
                    if (constraint == null)
                    {
                        // å¤„理 processing ä¸ºç©ºçš„æƒ…况(可根据实际业务需求添加处理逻辑)
                        return;
                    }
                    ProcessDeviceRequest(conveyorLine, constraint, childDeviceCode,
                    () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestLower),
                    () => constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestLower),
                    outputReq => constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0));
                }
                else if (string.Equals(task.NextAddress, PinMachineName, StringComparison.Ordinal))
                {
                    PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
                    if (pinMachine == null)
                    {
                        // å¤„理 pinMachine ä¸ºç©ºçš„æƒ…况(可根据实际业务需求添加处理逻辑)
                        return;
                    }
                    ProcessDeviceRequest(conveyorLine, pinMachine, childDeviceCode,
                    () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestLower),
                    () => pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestLower),
                    outputReq => pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0));
                }
                Dt_Task? newTask = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
            _targetAddressSelector.HandleOutboundNextAddress(conveyorLine, task.NextAddress, childDeviceCode);
            }
            _ = _taskService.UpdatePosition(task.TaskNum, task.CurrentAddress);
        }
        /// <summary>
        /// è¾“送线出库完成
        /// è¾“送线出库完成
        /// </summary>
        /// <param name="conveyorLine">输送线实例对象</param>
        /// <param name="command">读取的请求信息</param>
        /// <param name="childDeviceCode">子设备编号</param>
        public void ConveyorLineOutFinish(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, string childDeviceCode)
        {
            Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
            Dt_Task? task = _taskFilter.QueryExecutingTask(command.TaskNo, childDeviceCode);
            if (task != null)
            {
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
                WebResponseContent content = _taskService.UpdateTaskStatusToNext(task);
                Console.Out.WriteLine(content.Serialize());
            }
        }
        /// <summary>
        /// é€šç”¨çš„设备请求处理方法
        /// </summary>
        private void ProcessDeviceRequest<T>(CommonConveyorLine conveyorLine, T device, string childDeviceCode,
            Func<bool> getMaterialRequest, Func<bool> getOutputRequest, Action<bool> setOutputReady)
        {
            bool materialReq = getMaterialRequest();
            bool outputReq = getOutputRequest();
            if (materialReq)
            {
                conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode);
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
            }
            else
            {
                setOutputReady(outputReq);
            }
        }
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// è¾“送线设备请求处理器:处理拘束机/插拔钉机上下层请求。
    /// </summary>
    public class ConveyorLineTargetAddressSelector
    {
        private const string ConstraintMachineName = "拘束机";
        private const string PinMachineName = "插拔钉机";
        public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
        {
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true);
        }
        public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode)
        {
            HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false);
        }
        private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, bool isUpper)
        {
            var devices = Storage.Devices;
            if (string.Equals(nextAddress, ConstraintMachineName, StringComparison.Ordinal))
            {
                ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName);
                if (constraint == null)
                {
                    return;
                }
                ProcessDeviceRequest(
                    conveyorLine,
                    childDeviceCode,
                    getMaterialRequest: () => isUpper
                        ? constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestUpper)
                        : constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.MaterialRequestLower),
                    getOutputRequest: () => isUpper
                        ? constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestUpper)
                        : constraint.GetValue<ConstraintMachineDBName, bool>(ConstraintMachineDBName.OutputRequestLower),
                    setOutputReady: outputReq =>
                    {
                        if (isUpper)
                        {
                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0);
                        }
                        else
                        {
                            constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0);
                        }
                    });
            }
            else if (string.Equals(nextAddress, PinMachineName, StringComparison.Ordinal))
            {
                PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName);
                if (pinMachine == null)
                {
                    return;
                }
                ProcessDeviceRequest(
                    conveyorLine,
                    childDeviceCode,
                    getMaterialRequest: () => isUpper
                        ? pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestUpper)
                        : pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.MaterialRequestLower),
                    getOutputRequest: () => isUpper
                        ? pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestUpper)
                        : pinMachine.GetValue<PinMachineDBName, bool>(PinMachineDBName.OutputRequestLower),
                    setOutputReady: outputReq =>
                    {
                        if (isUpper)
                        {
                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0);
                        }
                        else
                        {
                            pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0);
                        }
                    });
            }
        }
        private static void ProcessDeviceRequest(
            CommonConveyorLine conveyorLine,
            string childDeviceCode,
            Func<bool> getMaterialRequest,
            Func<bool> getOutputRequest,
            Action<bool> setOutputReady)
        {
            bool materialReq = getMaterialRequest();
            bool outputReq = getOutputRequest();
            if (materialReq)
            {
                conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode);
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode);
            }
            else
            {
                setOutputReady(outputReq);
            }
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// è¾“送线任务访问器:统一封装任务查询与 WMS è¯·æ±‚。
    /// </summary>
    public class ConveyorLineTaskFilter
    {
        private readonly ITaskService _taskService;
        public ConveyorLineTaskFilter(ITaskService taskService)
        {
            _taskService = taskService;
        }
        public Dt_Task? QueryPendingTask(string deviceCode, string childDeviceCode)
        {
            return _taskService.QueryConveyorLineTask(deviceCode, childDeviceCode);
        }
        public Dt_Task? QueryExecutingTask(int taskNo, string childDeviceCode)
        {
            return _taskService.QueryExecutingConveyorLineTask(taskNo, childDeviceCode);
        }
        public bool RequestWmsTask(string barcode, string childDeviceCode)
        {
            return _taskService.RequestWMSTask(barcode, childDeviceCode).Status;
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs
@@ -1,12 +1,11 @@
using Quartz;
using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_QuartzJob.StackerCrane.Enum;
using WIDESEAWCS_Tasks.StackerCraneJob;
@@ -18,14 +17,22 @@
        private readonly ITaskService _taskService;
        private readonly ITaskExecuteDetailService _taskExecuteDetailService;
        private readonly ITaskRepository _taskRepository;
        private readonly IRouterService _routerService;
        public FormationCommonStackerCraneJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, ITaskRepository taskRepository, IRouterService routerService)
        private readonly FormationStackerCraneTaskSelector _taskSelector;
        private readonly FormationStackerCraneCommandBuilder _commandBuilder;
        public FormationCommonStackerCraneJob(
            ITaskService taskService,
            ITaskExecuteDetailService taskExecuteDetailService,
            ITaskRepository taskRepository,
            IRouterService routerService)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _taskRepository = taskRepository;
            _routerService = routerService;
            _taskSelector = new FormationStackerCraneTaskSelector(taskService, routerService);
            _commandBuilder = new FormationStackerCraneCommandBuilder(taskService, routerService);
        }
        public Task Execute(IJobExecutionContext context)
@@ -52,14 +59,23 @@
                commonStackerCrane.CheckStackerCraneTaskCompleted();
                if (commonStackerCrane.StackerCraneWorkStatusValue == FormationStackerCraneOperationStatus.Idle)
                if (commonStackerCrane.StackerCraneWorkStatusValue != FormationStackerCraneOperationStatus.Idle)
                {
                    Dt_Task? task = GetTask(commonStackerCrane);
                    if (task != null)
                    return Task.CompletedTask;
                }
                Dt_Task? task = _taskSelector.SelectTask(commonStackerCrane);
                if (task == null)
                    {
                        FormationStackerCraneTaskCommand? stackerCraneTaskCommand = ConvertToStackerCraneTaskCommand(task);
                        if (stackerCraneTaskCommand != null)
                    return Task.CompletedTask;
                }
                var stackerCraneTaskCommand = _commandBuilder.ConvertToStackerCraneTaskCommand(task);
                if (stackerCraneTaskCommand == null)
                        {
                    return Task.CompletedTask;
                }
                            bool sendFlag = commonStackerCrane.SendCommand(stackerCraneTaskCommand);
                            if (sendFlag)
                            {
@@ -67,22 +83,18 @@
                                _taskService.UpdateTaskStatusToNext(task.TaskNum);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"FormationCommonStackerCraneJob Error: {ex.Message}");
            }
            return Task.CompletedTask;
        }
        /// <summary>
        /// ä»»åŠ¡å®Œæˆäº‹ä»¶è®¢é˜…çš„æ–¹æ³•
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, WIDESEAWCS_QuartzJob.StackerCrane.StackerCraneTaskCompletedEventArgs e)
        private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, StackerCraneTaskCompletedEventArgs e)
        {
            SpeFormationStackerCrane? commonStackerCrane = sender as SpeFormationStackerCrane;
            if (commonStackerCrane != null)
@@ -95,214 +107,6 @@
                }
            }
        }
        /// <summary>
        /// èŽ·å–ä»»åŠ¡
        /// </summary>
        /// <param name="commonStackerCrane">堆垛机对象</param>
        /// <returns></returns>
        private Dt_Task? GetTask(SpeFormationStackerCrane commonStackerCrane)
        {
            Dt_Task? task = null;
            if (commonStackerCrane.LastTaskType == null)
            {
                task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
            }
            else
            {
                if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
                {
                    task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                    task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                }
                else
                {
                    task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                }
            }
            if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                if (IsOutTaskStationAvailable(task))
                {
                    return task;
                }
                List<string> otherOutStationCodes = _routerService.QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
                    .Select(x => x.ChildPosi).ToList();
                List<Dt_Task> tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
                foreach (var alternativeTask in tasks)
                {
                    if (IsOutTaskStationAvailable(alternativeTask))
                    {
                        return alternativeTask;
                    }
                }
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
            }
            return task;
        }
        /// <summary>
        /// æ£€æŸ¥å‡ºåº“任务的出库站台是否可用
        /// </summary>
        /// <param name="task">任务实体</param>
        /// <returns>如果站台可用返回true,否则返回false</returns>
        private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
                return false;
            }
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
            if (device == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
                return false;
            }
            CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            return conveyorLine.IsOccupied(router.ChildPosi);
        }
        /// <summary>
        /// ä»»åŠ¡å®žä½“è½¬æ¢æˆå‘½ä»¤Model
        /// </summary>
        /// <param name="task">任务实体</param>
        /// <returns></returns>
        public FormationStackerCraneTaskCommand? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
        {
            FormationStackerCraneTaskCommand stackerCraneTaskCommand = new FormationStackerCraneTaskCommand
            {
                Barcode = task.PalletCode,
                TaskNum = task.TaskNum,
                WorkType = 4,
                WorkAction = 1,
                FireAlarm = 0,
                FieldName = ""
            };
            TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
            return taskTypeGroup switch
            {
                TaskTypeGroup.InboundGroup => BuildInboundCommand(task, stackerCraneTaskCommand),
                TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, stackerCraneTaskCommand),
                TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, stackerCraneTaskCommand),
                _ => stackerCraneTaskCommand
            };
        }
        /// <summary>
        /// æž„建入库命令
        /// </summary>
        private FormationStackerCraneTaskCommand? BuildInboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.CurrentAddress}】信息,无法获取对应的堆垛机取货站台信息");
                return null;
            }
            command.StartRow = Convert.ToInt16(router.SrmRow);
            command.StartColumn = Convert.ToInt16(router.SrmColumn);
            command.StartLayer = Convert.ToInt16(router.SrmLayer);
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"入库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            command.EndRow = endRow;
            command.EndColumn = endColumn;
            command.EndLayer = endLayer;
            return command;
        }
        /// <summary>
        /// æž„建出库命令
        /// </summary>
        private FormationStackerCraneTaskCommand? BuildOutboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.TargetAddress}】信息,无法获取对应的堆垛机放货站台信息");
                return null;
            }
            command.EndRow = Convert.ToInt16(router.SrmRow);
            command.EndColumn = Convert.ToInt16(router.SrmColumn);
            command.EndLayer = Convert.ToInt16(router.SrmLayer);
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"出库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            command.StartRow = startRow;
            command.StartColumn = startColumn;
            command.StartLayer = startLayer;
            return command;
        }
        /// <summary>
        /// æž„建移库命令
        /// </summary>
        private FormationStackerCraneTaskCommand? BuildRelocationCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
        {
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            command.EndRow = endRow;
            command.EndColumn = endColumn;
            command.EndLayer = endLayer;
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            command.StartRow = startRow;
            command.StartColumn = startColumn;
            command.StartLayer = startLayer;
            return command;
        }
        /// <summary>
        /// è§£æžåœ°å€å­—符串(格式:行-列-层)
        /// </summary>
        private bool TryParseAddress(string address, out short row, out short column, out short layer)
        {
            row = column = layer = 0;
            string[] parts = address.Split("-");
            if (parts.Length != 3)
            {
                return false;
            }
            return short.TryParse(parts[0], out row)
                && short.TryParse(parts[1], out column)
                && short.TryParse(parts[2], out layer);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,135 @@
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
using WIDESEAWCS_Tasks.StackerCraneJob;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// åˆ†å®¹å †åž›æœºå‘½ä»¤æž„建器:负责任务到命令对象的转换。
    /// </summary>
    public class FormationStackerCraneCommandBuilder
    {
        private readonly ITaskService _taskService;
        private readonly IRouterService _routerService;
        public FormationStackerCraneCommandBuilder(ITaskService taskService, IRouterService routerService)
        {
            _taskService = taskService;
            _routerService = routerService;
        }
        public FormationStackerCraneTaskCommand? ConvertToStackerCraneTaskCommand(Dt_Task task)
        {
            FormationStackerCraneTaskCommand command = new()
            {
                Barcode = task.PalletCode,
                TaskNum = task.TaskNum,
                WorkType = 4,
                WorkAction = 1,
                FireAlarm = 0,
                FieldName = string.Empty
            };
            TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
            return taskTypeGroup switch
            {
                TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),
                TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),
                TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),
                _ => command
            };
        }
        private FormationStackerCraneTaskCommand? BuildInboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.CurrentAddress}】信息,无法获取对应的堆垛机取货站台信息");
                return null;
            }
            command.StartRow = Convert.ToInt16(router.SrmRow);
            command.StartColumn = Convert.ToInt16(router.SrmColumn);
            command.StartLayer = Convert.ToInt16(router.SrmLayer);
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"入库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            command.EndRow = endRow;
            command.EndColumn = endColumn;
            command.EndLayer = endLayer;
            return command;
        }
        private FormationStackerCraneTaskCommand? BuildOutboundCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.TargetAddress}】信息,无法获取对应的堆垛机放货站台信息");
                return null;
            }
            command.EndRow = Convert.ToInt16(router.SrmRow);
            command.EndColumn = Convert.ToInt16(router.SrmColumn);
            command.EndLayer = Convert.ToInt16(router.SrmLayer);
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"出库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            command.StartRow = startRow;
            command.StartColumn = startColumn;
            command.StartLayer = startLayer;
            return command;
        }
        private FormationStackerCraneTaskCommand? BuildRelocationCommand(Dt_Task task, FormationStackerCraneTaskCommand command)
        {
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            command.EndRow = endRow;
            command.EndColumn = endColumn;
            command.EndLayer = endLayer;
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            command.StartRow = startRow;
            command.StartColumn = startColumn;
            command.StartLayer = startLayer;
            return command;
        }
        private static bool TryParseAddress(string address, out short row, out short column, out short layer)
        {
            row = column = layer = 0;
            string[] parts = address.Split("-");
            if (parts.Length != 3)
            {
                return false;
            }
            return short.TryParse(parts[0], out row)
                && short.TryParse(parts[1], out column)
                && short.TryParse(parts[2], out layer);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// åˆ†å®¹å †åž›æœºä»»åŠ¡é€‰æ‹©å™¨ï¼šå°è£…ä»»åŠ¡æŒ‘é€‰ä¸Žç«™å°å¯ç”¨æ€§åˆ¤æ–­ã€‚
    /// </summary>
    public class FormationStackerCraneTaskSelector
    {
        private readonly ITaskService _taskService;
        private readonly IRouterService _routerService;
        public FormationStackerCraneTaskSelector(ITaskService taskService, IRouterService routerService)
        {
            _taskService = taskService;
            _routerService = routerService;
        }
        public Dt_Task? SelectTask(SpeFormationStackerCrane commonStackerCrane)
        {
            Dt_Task? task;
            if (commonStackerCrane.LastTaskType == null)
            {
                task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
            }
            else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
            }
            else
            {
                task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
            }
            if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                if (IsOutTaskStationAvailable(task))
                {
                    return task;
                }
                var otherOutStationCodes = _routerService
                    .QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
                    .Select(x => x.ChildPosi)
                    .ToList();
                var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
                foreach (var alternativeTask in tasks)
                {
                    if (IsOutTaskStationAvailable(alternativeTask))
                    {
                        return alternativeTask;
                    }
                }
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
            }
            return task;
        }
        private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
                return false;
            }
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
            if (device == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
                return false;
            }
            CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            return conveyorLine.IsOccupied(router.ChildPosi);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotMessageRouter.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
using System.Net.Sockets;
namespace WIDESEAWCS_Tasks.Workflow.Abstractions
{
    /// <summary>
    /// æœºå™¨äººæ¶ˆæ¯è·¯ç”±å…¥å£ã€‚用于承接 TcpSocketServer çš„æ¶ˆæ¯äº‹ä»¶ã€‚
    /// </summary>
    public interface IRobotMessageRouter
    {
        Task<string?> HandleMessageReceivedAsync(string message, bool isJson, TcpClient client, RobotSocketState state);
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotPrefixCommandHandler.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
using System.Net.Sockets;
namespace WIDESEAWCS_Tasks.Workflow.Abstractions
{
    /// <summary>
    /// æœºå™¨äººå‰ç¼€å‘½ä»¤å¤„理器(pickfinished / putfinished)。
    /// </summary>
    public interface IRobotPrefixCommandHandler
    {
        bool IsPrefixCommand(string message);
        Task HandleAsync(string message, RobotSocketState state, TcpClient client);
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotSimpleCommandHandler.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,10 @@
namespace WIDESEAWCS_Tasks.Workflow.Abstractions
{
    /// <summary>
    /// æœºå™¨äººç®€å•命令处理器(如运行状态、模式切换、全流程完成命令)。
    /// </summary>
    public interface IRobotSimpleCommandHandler
    {
        Task<bool> HandleAsync(string message, RobotSocketState state);
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/IRobotWorkflowOrchestrator.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
using WIDESEAWCS_Model.Models;
namespace WIDESEAWCS_Tasks.Workflow.Abstractions
{
    /// <summary>
    /// æœºå™¨äººæµç¨‹ç¼–排器。负责 RobotJob å†…的状态机分支执行。
    /// </summary>
    public interface IRobotWorkflowOrchestrator
    {
        Task ExecuteAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress);
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Abstractions/ISocketClientGateway.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
using System.Net.Sockets;
using WIDESEAWCS_QuartzJob;
namespace WIDESEAWCS_Tasks.Workflow.Abstractions
{
    /// <summary>
    /// Socket å®¢æˆ·ç«¯ç½‘关。用于隔离 Robot ä¸šåС坹 TcpSocketServer çš„直接依赖。
    /// </summary>
    public interface ISocketClientGateway
    {
        Task<bool> SendToClientAsync(string clientId, string message);
        Task SendMessageAsync(TcpClient client, string message);
        IReadOnlyList<string> GetClientIds();
        Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane);
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotJob.cs
@@ -1,17 +1,17 @@
using Quartz;
using Quartz;
using WIDESEA_Core;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
using WIDESEAWCS_Tasks.Workflow;
using WIDESEAWCS_Tasks.SocketServer;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// æœºæ¢°æ‰‹ä»»åŠ¡ä½œä¸š - è´Ÿè´£åè°ƒæœºæ¢°æ‰‹å®¢æˆ·ç«¯è¿žæŽ¥ã€æ¶ˆæ¯å¤„理和任务执行
    /// æœºå™¨äººä»»åŠ¡ä½œä¸šï¼šè´Ÿè´£è°ƒåº¦ä¸Žç”Ÿå‘½å‘¨æœŸç®¡ç†ï¼Œå…·ä½“çŠ¶æ€æœºæµç¨‹äº¤ç»™ç¼–æŽ’å™¨ã€‚
    /// </summary>
    [DisallowConcurrentExecution]
    public class RobotJob : IJob
@@ -22,9 +22,9 @@
        private readonly RobotClientManager _clientManager;
        private readonly RobotStateManager _stateManager;
        private readonly RobotMessageHandler _messageHandler;
        private readonly IRobotMessageRouter _messageRouter;
        private readonly RobotTaskProcessor _taskProcessor;
        private readonly IRobotTaskService _robotTaskService;
        private readonly IRobotWorkflowOrchestrator _workflowOrchestrator;
        public RobotJob(
            TcpSocketServer tcpSocket,
@@ -33,31 +33,32 @@
            ICacheService cache,
            HttpClientHelper httpClientHelper)
        {
            _robotTaskService = robotTaskService;
            // åˆå§‹åŒ–管理器
            _stateManager = new RobotStateManager(cache);
            _taskProcessor = new RobotTaskProcessor(tcpSocket, _stateManager, robotTaskService, taskService, httpClientHelper);
            _clientManager = new RobotClientManager(tcpSocket, _stateManager);
            _messageHandler = new RobotMessageHandler(tcpSocket, _stateManager, cache, robotTaskService, _taskProcessor);
            // è®¢é˜…客户端管理器的事件
            // æ”¶å£ Socket è®¿é—®ï¼ŒåŽç»­è‹¥æ›¿æ¢é€šä¿¡å®žçŽ°åªéœ€æ›¿æ¢ç½‘å…³å±‚ã€‚
            ISocketClientGateway socketGateway = new SocketClientGateway(tcpSocket);
            _taskProcessor = new RobotTaskProcessor(socketGateway, _stateManager, robotTaskService, taskService, httpClientHelper);
            _clientManager = new RobotClientManager(tcpSocket, _stateManager);
            var simpleCommandHandler = new RobotSimpleCommandHandler(_taskProcessor);
            var prefixCommandHandler = new RobotPrefixCommandHandler(robotTaskService, _taskProcessor, _stateManager, socketGateway);
            _messageRouter = new RobotMessageHandler(socketGateway, _stateManager, cache, simpleCommandHandler, prefixCommandHandler);
            _workflowOrchestrator = new RobotWorkflowOrchestrator(_stateManager, _clientManager, _taskProcessor, robotTaskService);
            _clientManager.OnClientDisconnected += OnClientDisconnected;
            // è®¢é˜…TCP服务器的消息事件(全局只订阅一次)
            // å…¨å±€åªè®¢é˜…一次消息事件,保持原有行为。
            if (System.Threading.Interlocked.CompareExchange(ref _messageSubscribedFlag, 1, 0) == 0)
            {
                tcpSocket.MessageReceived += _messageHandler.HandleMessageReceivedAsync;
                Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] æœºå™¨äººTCP消息事件已订阅");
                tcpSocket.MessageReceived += _messageRouter.HandleMessageReceivedAsync;
                Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] æœºå™¨æ‰‹TCP消息事件已订阅");
            }
        }
        /// <summary>
        /// å®¢æˆ·ç«¯æ–­å¼€è¿žæŽ¥æ—¶çš„处理
        /// </summary>
        private void OnClientDisconnected(object? sender, RobotSocketState state)
        {
            // å¯ä»¥åœ¨è¿™é‡Œæ·»åŠ æ–­å¼€è¿žæŽ¥åŽçš„å¤„ç†é€»è¾‘
            Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] å®¢æˆ·ç«¯å·²æ–­å¼€è¿žæŽ¥: {state.IPAddress}");
        }
@@ -72,118 +73,34 @@
            string ipAddress = robotCrane.IPAddress;
            // èŽ·å–æˆ–åˆ›å»ºçŠ¶æ€
            RobotSocketState state = _stateManager.GetOrCreateState(ipAddress, robotCrane);
            state.RobotCrane = robotCrane;
            try
            {
                // ç¡®ä¿å®¢æˆ·ç«¯å·²è¿žæŽ¥å¹¶è®¢é˜…消息事件
                if (!_clientManager.EnsureClientSubscribed(ipAddress, robotCrane))
                {
                    return; // å®¢æˆ·ç«¯æœªè¿žæŽ¥æˆ–订阅失败,跳过本次执行
                    return;
                }
                // èŽ·å–ä»»åŠ¡å¹¶å¤„ç†
                Dt_RobotTask? task = _taskProcessor.GetTask(robotCrane);
                var task = _taskProcessor.GetTask(robotCrane);
                if (task != null)
                {
                    // æ¯æ¬¡åˆ¤æ–­å‰é‡æ–°ä»Žç¼“存获取最新状态
                    var latestState = _stateManager.GetState(ipAddress);
                    if (latestState == null) return;
                    if (latestState == null)
                    {
                        return;
                    }
                    if (latestState.RobotTaskTotalNum < MaxTaskTotalNum)
                    {
                        await ProcessTaskAsync(latestState, task, ipAddress);
                        await _workflowOrchestrator.ExecuteAsync(latestState, task, ipAddress);
                    }
                }
            }
            catch (Exception)
            {
                // å¼‚常处理已在各组件中处理
            }
        }
        /// <summary>
        /// å¤„理机械手任务
        /// </summary>
        private async Task ProcessTaskAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
        {
            // å¤„理正在执行的任务
            if (latestState.RobotRunMode == 2 && latestState.RobotControlMode == 1 && latestState.OperStatus != "Running")
            {
                // å–货完成状态处理
                if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished") && latestState.RobotArmObject == 1 &&
                    task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    await HandlePickFinishedStateAsync(latestState, task, ipAddress);
                }
                // æ”¾è´§å®ŒæˆçŠ¶æ€å¤„ç†
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished") && latestState.OperStatus == "Homed" &&
                    latestState.RobotArmObject == 0 &&
                    (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode() ||
                    task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
                {
                    await HandlePutFinishedStateAsync(latestState, task, ipAddress);
                }
            }
        }
        /// <summary>
        /// å¤„理取货完成状态
        /// </summary>
        private async Task HandlePickFinishedStateAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
        {
            string taskString = $"Putbattery,{task.RobotTargetAddress}";
            bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
            if (result)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                // é‡æ–°èŽ·å–æœ€æ–°çŠ¶æ€å¹¶æ›´æ–°
                var stateToUpdate = _stateManager.GetState(ipAddress);
                if (stateToUpdate != null)
                {
                    stateToUpdate.CurrentTask = task;
                    if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
                        await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
        }
        /// <summary>
        /// å¤„理放货完成状态
        /// </summary>
        private async Task HandlePutFinishedStateAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
        {
            // é‡æ–°èŽ·å–æœ€æ–°çŠ¶æ€
            var stateForUpdate = _stateManager.GetState(ipAddress);
            if (stateForUpdate == null) return;
            if (!stateForUpdate.IsSplitPallet && !stateForUpdate.IsGroupPallet)
            {
                stateForUpdate.IsSplitPallet = task.RobotTaskType == RobotTaskTypeEnum.SplitPallet.GetHashCode();
                stateForUpdate.IsGroupPallet = task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode() ||
                    task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
            }
            if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
            {
                string prefix = "TRAY";
                // ç”Ÿæˆä¸¤ä¸ªæ‰˜ç›˜æ¡ç 
                string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
                string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
                if (!trayBarcode1.IsNullOrEmpty() && !trayBarcode2.IsNullOrEmpty())
                {
                    stateForUpdate.CellBarcode.Add(trayBarcode1);
                    stateForUpdate.CellBarcode.Add(trayBarcode2);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
            }
            else // ä»»åŠ¡å¼€å§‹æ‰§è¡Œç›´æŽ¥å‘é€å–è´§åœ°å€
            {
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                // å¼‚常处理已在组件内部进行,Job å±‚保持兜底吞吐语义。
            }
        }
    }
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotMessageHandler.cs
@@ -1,275 +1,62 @@
using System.Net.Sockets;
using System.Net.Sockets;
using WIDESEAWCS_Common;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_DTO.Stock;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_Tasks.SocketServer;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// æœºæ¢°æ‰‹æ¶ˆæ¯å¤„理器 - è´Ÿè´£å¤„理来自TCP客户端的消息
    /// æœºå™¨äººæ¶ˆæ¯è·¯ç”±å…¥å£ï¼šè´Ÿè´£ç¼“存状态读取、命令分发和回包触发。
    /// </summary>
    public class RobotMessageHandler
    public class RobotMessageHandler : IRobotMessageRouter
    {
        private readonly TcpSocketServer _tcpSocket;
        private readonly ISocketClientGateway _socketClientGateway;
        private readonly RobotStateManager _stateManager;
        private readonly ICacheService _cache;
        private readonly IRobotTaskService _robotTaskService;
        private readonly RobotTaskProcessor _taskProcessor;
        private readonly IRobotSimpleCommandHandler _simpleCommandHandler;
        private readonly IRobotPrefixCommandHandler _prefixCommandHandler;
        public RobotMessageHandler(
            TcpSocketServer tcpSocket,
            ISocketClientGateway socketClientGateway,
            RobotStateManager stateManager,
            ICacheService cache,
            IRobotTaskService robotTaskService,
            RobotTaskProcessor taskProcessor)
            IRobotSimpleCommandHandler simpleCommandHandler,
            IRobotPrefixCommandHandler prefixCommandHandler)
        {
            _tcpSocket = tcpSocket;
            _socketClientGateway = socketClientGateway;
            _stateManager = stateManager;
            _cache = cache;
            _robotTaskService = robotTaskService;
            _taskProcessor = taskProcessor;
            _simpleCommandHandler = simpleCommandHandler;
            _prefixCommandHandler = prefixCommandHandler;
        }
        /// <summary>
        /// å¤„理接收到的消息
        /// å¤„理接收到的消息。保持原有行为:简单命令和前缀命令都回写原消息。
        /// </summary>
        public async Task<string?> HandleMessageReceivedAsync(string message, bool isJson, TcpClient client, RobotSocketState state)
        {
            if (!(_cache?.TryGetValue($"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}", out state)) ?? false)
            var cacheKey = $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}";
            if (!_cache.TryGetValue(cacheKey, out RobotSocketState? cachedState) || cachedState == null)
            {
                return null;
            }
            var activeState = cachedState;
            string messageLower = message.ToLowerInvariant();
            if (await IsSimpleCommandAsync(messageLower, state))
            if (await _simpleCommandHandler.HandleAsync(messageLower, activeState))
            {
                await _tcpSocket.SendMessageAsync(client, message);
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                    return null;
            }
            else if (IsPrefixCommand(messageLower))
            {
                await HandlePrefixCommandAsync(message, state, client);
            }
                await _socketClientGateway.SendMessageAsync(client, message);
                _stateManager.TryUpdateStateSafely(activeState.IPAddress, activeState);
            return null;
        }
        /// <summary>
        /// å¤„理前缀命令(pickfinished, putfinished)
        /// </summary>
        private async Task HandlePrefixCommandAsync(string message, RobotSocketState state, TcpClient client)
            if (_prefixCommandHandler.IsPrefixCommand(messageLower))
        {
            try
            {
                var parts = message.Split(',');
                if (parts.Length >= 1 && state.CurrentTask != null)
                {
                    var cmd = parts[0].ToLowerInvariant();
                    int[] positions = parts.Skip(1)
                       .Select(p => int.TryParse(p, out int value) ? value : (int?)null)
                       .Where(v => v.HasValue && v.Value != 0)
                       .Select(v => v!.Value)
                       .ToArray();
                    var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId);
                    if (cmd.StartsWith("pickfinished"))
                    {
                        await HandlePickFinishedAsync(state, positions, task, client);
                    }
                    else if (cmd.StartsWith("putfinished"))
                    {
                        await HandlePutFinishedAsync(state, positions, task, client);
                await _prefixCommandHandler.HandleAsync(message, activeState, client);
                    }
                    await _tcpSocket.SendMessageAsync(client, message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}");
            }
        }
        /// <summary>
        /// å¤„理取货完成命令
        /// </summary>
        private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task, TcpClient client)
        {
            if (state.IsSplitPallet)
            {
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                state.LastPickPositions = positions;
                var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
                if (result.Data.Status && result.IsSuccess)
                {
                    state.CurrentAction = "PickFinished";
                }
            }
            else
            {
                state.CurrentAction = "PickFinished";
            }
            state.LastPickPositions = positions;
            if (task != null)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode();
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                    await _robotTaskService.Repository.UpdateDataAsync(task);
            }
        }
        /// <summary>
        /// å¤„理放货完成命令
        /// </summary>
        private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task, TcpClient client)
        {
            bool putSuccess = true;
            if (state.IsGroupPallet)
            {
                state.LastPutPositions = positions;
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
                    ? nameof(ConfigKey.ChangePalletAsync) : nameof(ConfigKey.GroupPalletAsync);
                var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO);
                putSuccess = result.Data.Status && result.IsSuccess;
            }
            if (putSuccess)
            {
                state.CurrentAction = "PutFinished";
                state.RobotTaskTotalNum += positions.Length;
                if (task != null)
                {
                    task.RobotTaskTotalNum += positions.Length;
                }
            }
            if (task != null)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode();
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                    await _robotTaskService.Repository.UpdateDataAsync(task);
            }
        }
        /// <summary>
        /// æœºæ¢°æ‰‹ç®€å•命令处理
        /// </summary>
        private async Task<bool> IsSimpleCommandAsync(string message, RobotSocketState state)
        {
            RobotTaskTypeEnum? GetRobotTaskType() => state.CurrentTask != null ? (RobotTaskTypeEnum)state.CurrentTask.RobotTaskType : null;
            switch (message)
            {
                case "homing":
                    state.OperStatus = "Homing";
                    return true;
                case "homed":
                    state.OperStatus = "Homed";
                    return true;
                case "picking":
                    state.CurrentAction = "Picking";
                    return true;
                case "puting":
                    state.CurrentAction = "Putting";
                    return true;
                case "allpickfinished": // å–货完成
                    state.CurrentAction = "AllPickFinished";
                    var robotTaskType = GetRobotTaskType();
                    if (robotTaskType == RobotTaskTypeEnum.SplitPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
                    {
                        if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                        {
                            _taskProcessor.DeleteTask(state.CurrentTask.RobotTaskId);
                            return true;
                        }
                    }
                    return false;
                case "allputfinished": // æ”¾è´§å®Œæˆ
                    state.CurrentAction = "AllPutFinished";
                    robotTaskType = GetRobotTaskType();
                    if (robotTaskType == RobotTaskTypeEnum.GroupPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
                    {
                        if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                        {
                            _taskProcessor.DeleteTask(state.CurrentTask.RobotTaskId);
                            state.CurrentTask = null;
                            state.RobotTaskTotalNum = 0;
                            state.CellBarcode = new List<string>();
                            return true;
                        }
                    }
                    return false;
                case "running":
                    state.OperStatus = "Running";
                    return true;
                case "pausing":
                    state.OperStatus = "Pausing";
                    return true;
                case "warming":
                    state.OperStatus = "Warming";
                    return true;
                case "emstoping":
                    state.OperStatus = "Emstoping";
                    return true;
                case "runmode,1":
                    state.RobotRunMode = 1;
                    return true;
                case "runmode,2":
                    state.RobotRunMode = 2;
                    return true;
                case "controlmode,1":
                    state.RobotControlMode = 1;
                    return true;
                case "controlmode,2":
                    state.RobotControlMode = 2;
                    return true;
                case "armobject,1":
                    state.RobotArmObject = 1;
                    return true;
                case "armobject,0":
                    state.RobotArmObject = 0;
                    return true;
                default:
                    return false;
            }
        }
        /// <summary>
        /// åˆ¤æ–­æ˜¯å¦ä¸ºå‰ç¼€å‘½ä»¤
        /// </summary>
        private static bool IsPrefixCommand(string message)
        {
            return message.StartsWith("pickfinished") || message.StartsWith("putfinished");
            return null;
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using WIDESEA_Core;
using WIDESEAWCS_Common;
using WIDESEAWCS_Common.HttpEnum;
@@ -10,29 +10,30 @@
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_Tasks.SocketServer;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// æœºæ¢°æ‰‹ä»»åŠ¡å¤„ç†å™¨ - è´Ÿè´£æœºæ¢°æ‰‹ä»»åŠ¡æ‰§è¡Œå’Œå¤„ç†
    /// æœºå™¨äººä»»åŠ¡å¤„ç†å™¨ï¼šè´Ÿè´£ä»»åŠ¡èŽ·å–ã€ä¸‹å‘ã€å…¥åº“ä»»åŠ¡å›žä¼ åŠåº“å­˜ DTO æž„建。
    /// </summary>
    public class RobotTaskProcessor
    {
        private readonly TcpSocketServer _tcpSocket;
        // é€šè¿‡ç½‘关访问 Socket,避免业务层直接依赖 TcpSocketServer。
        private readonly ISocketClientGateway _socketClientGateway;
        private readonly RobotStateManager _stateManager;
        private readonly IRobotTaskService _robotTaskService;
        private readonly ITaskService _taskService;
        private readonly HttpClientHelper _httpClientHelper;
        public RobotTaskProcessor(
            TcpSocketServer tcpSocket,
            ISocketClientGateway socketClientGateway,
            RobotStateManager stateManager,
            IRobotTaskService robotTaskService,
            ITaskService taskService,
            HttpClientHelper httpClientHelper)
        {
            _tcpSocket = tcpSocket;
            _socketClientGateway = socketClientGateway;
            _stateManager = stateManager;
            _robotTaskService = robotTaskService;
            _taskService = taskService;
@@ -40,7 +41,7 @@
        }
        /// <summary>
        /// èŽ·å–æœºæ¢°æ‰‹ä»»åŠ¡
        /// æŒ‰è®¾å¤‡ç¼–码获取当前机器人任务。
        /// </summary>
        public Dt_RobotTask? GetTask(RobotCraneDevice robotCrane)
        {
@@ -48,7 +49,7 @@
        }
        /// <summary>
        /// èŽ·å–æœºæ¢°æ‰‹ä»»åŠ¡
        /// åˆ é™¤æœºå™¨äººä»»åŠ¡ã€‚
        /// </summary>
        public bool? DeleteTask(int ID)
        {
@@ -56,26 +57,27 @@
        }
        /// <summary>
        /// å‘送机械手取货命令
        /// ä¸‹å‘取货指令(Pickbattery)到机器人客户端。
        /// </summary>
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state)
        {
            string taskString = $"Pickbattery,{task.RobotSourceAddress}";
            // å‘送任务指令
            bool result = await _tcpSocket.SendToClientAsync(state.IPAddress, taskString);
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                // æ›´æ–°ç¼“存中的状态(使用安全更新防止并发覆盖)
                // ä¿æŒåŽŸè¯­ä¹‰ï¼šä»…åœ¨çŠ¶æ€å®‰å…¨å†™å…¥æˆåŠŸåŽå†æ›´æ–°ä»»åŠ¡çŠ¶æ€ã€‚
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
        }
        /// <summary>
        /// å¤„理入库任务
        /// å¤„理入库任务回传(拆盘/组盘/换盘场景)。
        /// </summary>
        public async Task<bool> HandleInboundTaskAsync(RobotSocketState state, bool useSourceAddress)
        {
@@ -92,7 +94,6 @@
            string SourceAddress = currentTask.RobotTargetAddressLineCode;
            string TargetAddress = currentTask.RobotSourceAddressLineCode;
            string PalletCode = string.Empty;
            // ç›´æŽ¥è½¬æ¢ä¸ºæžšä¸¾ç±»åž‹è¿›è¡Œæ¯”较
            var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
            if (useSourceAddress)
@@ -134,6 +135,7 @@
                PalletType = 1,
                TaskType = taskType
            };
            var result = _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.CreateTaskInboundAsync), taskDto.ToJson());
            if (!result.Data.Status && result.IsSuccess)
            {
@@ -142,10 +144,12 @@
            WMSTaskDTO taskDTO = JsonConvert.DeserializeObject<WMSTaskDTO>(result.Data.Data.ToJson() ?? string.Empty) ?? new WMSTaskDTO();
            var content = _taskService.ReceiveWMSTask(new List<WMSTaskDTO> { taskDTO });
            if (!content.Status) return false;
            if (!content.Status)
            {
                return false;
            }
            var taskInfo = JsonConvert.DeserializeObject<Dt_Task>(content.Data.ToJson() ?? string.Empty) ?? new Dt_Task();
            string sourceAddress = taskDTO.SourceAddress;
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress));
@@ -161,11 +165,12 @@
                    return true;
                }
            }
            return false;
        }
        /// <summary>
        /// æž„建库存DTO
        /// æž„建库存回传 DTO。
        /// </summary>
        public static StockDTO BuildStockDTO(RobotSocketState state, int[] positions)
        {
@@ -189,7 +194,7 @@
        }
        /// <summary>
        /// è°ƒç”¨æ‹†ç›˜API
        /// è°ƒç”¨æ‹†ç›˜ API。
        /// </summary>
        public HttpResponseResult<WebResponseContent> PostSplitPalletAsync(StockDTO stockDTO)
        {
@@ -197,7 +202,7 @@
        }
        /// <summary>
        /// è°ƒç”¨ç»„盘或换盘API
        /// è°ƒç”¨ç»„盘/换盘 API。
        /// </summary>
        public HttpResponseResult<WebResponseContent> PostGroupPalletAsync(string configKey, StockDTO stockDTO)
        {
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,138 @@
using System.Net.Sockets;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_DTO.TaskInfo;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks.Workflow
{
    /// <summary>
    /// å‰ç¼€å‘½ä»¤å¤„理:迁移原 RobotMessageHandler çš„ pickfinished/putfinished åˆ†æ”¯ã€‚
    /// </summary>
    public class RobotPrefixCommandHandler : IRobotPrefixCommandHandler
    {
        private readonly IRobotTaskService _robotTaskService;
        private readonly RobotTaskProcessor _taskProcessor;
        private readonly RobotStateManager _stateManager;
        private readonly ISocketClientGateway _socketClientGateway;
        public RobotPrefixCommandHandler(
            IRobotTaskService robotTaskService,
            RobotTaskProcessor taskProcessor,
            RobotStateManager stateManager,
            ISocketClientGateway socketClientGateway)
        {
            _robotTaskService = robotTaskService;
            _taskProcessor = taskProcessor;
            _stateManager = stateManager;
            _socketClientGateway = socketClientGateway;
        }
        public bool IsPrefixCommand(string message)
        {
            return message.StartsWith("pickfinished") || message.StartsWith("putfinished");
        }
        public async Task HandleAsync(string message, RobotSocketState state, TcpClient client)
        {
            try
            {
                var parts = message.Split(',');
                if (parts.Length < 1 || state.CurrentTask == null)
                {
                    return;
                }
                var cmd = parts[0].ToLowerInvariant();
                int[] positions = parts.Skip(1)
                    .Select(p => int.TryParse(p, out int value) ? value : (int?)null)
                    .Where(v => v.HasValue && v.Value != 0)
                    .Select(v => v!.Value)
                    .ToArray();
                var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId);
                if (cmd.StartsWith("pickfinished"))
                {
                    await HandlePickFinishedAsync(state, positions, task);
                }
                else if (cmd.StartsWith("putfinished"))
                {
                    await HandlePutFinishedAsync(state, positions, task);
                }
                await _socketClientGateway.SendMessageAsync(client, message);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}");
            }
        }
        private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
        {
            if (state.IsSplitPallet)
            {
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                state.LastPickPositions = positions;
                var result = _taskProcessor.PostSplitPalletAsync(stockDTO);
                if (result.Data.Status && result.IsSuccess)
                {
                    state.CurrentAction = "PickFinished";
                }
            }
            else
            {
                state.CurrentAction = "PickFinished";
            }
            state.LastPickPositions = positions;
            if (task != null)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode();
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.Repository.UpdateDataAsync(task);
                }
            }
        }
        private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task)
        {
            bool putSuccess = true;
            if (state.IsGroupPallet)
            {
                state.LastPutPositions = positions;
                var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions);
                var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode()
                    ? nameof(ConfigKey.ChangePalletAsync)
                    : nameof(ConfigKey.GroupPalletAsync);
                var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO);
                putSuccess = result.Data.Status && result.IsSuccess;
            }
            if (putSuccess)
            {
                state.CurrentAction = "PutFinished";
                state.RobotTaskTotalNum += positions.Length;
                if (task != null)
                {
                    task.RobotTaskTotalNum += positions.Length;
                }
            }
            if (task != null)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode();
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.Repository.UpdateDataAsync(task);
                }
            }
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotSimpleCommandHandler.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,128 @@
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks.Workflow
{
    /// <summary>
    /// ç®€å•命令处理:仅迁移原 RobotMessageHandler ä¸­çš„命令分支,不改变业务语义。
    /// </summary>
    public class RobotSimpleCommandHandler : IRobotSimpleCommandHandler
    {
        private readonly RobotTaskProcessor _taskProcessor;
        public RobotSimpleCommandHandler(RobotTaskProcessor taskProcessor)
        {
            _taskProcessor = taskProcessor;
        }
        public async Task<bool> HandleAsync(string message, RobotSocketState state)
        {
            switch (message)
            {
                case "homing":
                    state.OperStatus = "Homing";
                    return true;
                case "homed":
                    state.OperStatus = "Homed";
                    return true;
                case "picking":
                    state.CurrentAction = "Picking";
                    return true;
                case "puting":
                    state.CurrentAction = "Putting";
                    return true;
                case "allpickfinished":
                {
                    state.CurrentAction = "AllPickFinished";
                    var currentTask = state.CurrentTask;
                    if (currentTask == null)
                    {
                        return false;
                    }
                    var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                    if (robotTaskType == RobotTaskTypeEnum.SplitPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
                    {
                        if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: true))
                        {
                            _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                            return true;
                        }
                    }
                    return false;
                }
                case "allputfinished":
                {
                    state.CurrentAction = "AllPutFinished";
                    var currentTask = state.CurrentTask;
                    if (currentTask == null)
                    {
                        return false;
                    }
                    var robotTaskType = (RobotTaskTypeEnum)currentTask.RobotTaskType;
                    if (robotTaskType == RobotTaskTypeEnum.GroupPallet || robotTaskType == RobotTaskTypeEnum.ChangePallet)
                    {
                        if (await _taskProcessor.HandleInboundTaskAsync(state, useSourceAddress: false))
                        {
                            _taskProcessor.DeleteTask(currentTask.RobotTaskId);
                            state.CurrentTask = null;
                            state.RobotTaskTotalNum = 0;
                            state.CellBarcode = new List<string>();
                            return true;
                        }
                    }
                    return false;
                }
                case "running":
                    state.OperStatus = "Running";
                    return true;
                case "pausing":
                    state.OperStatus = "Pausing";
                    return true;
                case "warming":
                    state.OperStatus = "Warming";
                    return true;
                case "emstoping":
                    state.OperStatus = "Emstoping";
                    return true;
                case "runmode,1":
                    state.RobotRunMode = 1;
                    return true;
                case "runmode,2":
                    state.RobotRunMode = 2;
                    return true;
                case "controlmode,1":
                    state.RobotControlMode = 1;
                    return true;
                case "controlmode,2":
                    state.RobotControlMode = 2;
                    return true;
                case "armobject,1":
                    state.RobotArmObject = 1;
                    return true;
                case "armobject,0":
                    state.RobotArmObject = 0;
                    return true;
                default:
                    return false;
            }
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotWorkflowOrchestrator.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,108 @@
using WIDESEA_Core;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks.Workflow
{
    /// <summary>
    /// RobotJob æµç¨‹ç¼–排器:迁移原 RobotJob çŠ¶æ€æœºåˆ†æ”¯ï¼Œé™ä½Ž Job ç±»å¤æ‚度。
    /// </summary>
    public class RobotWorkflowOrchestrator : IRobotWorkflowOrchestrator
    {
        private readonly RobotStateManager _stateManager;
        private readonly RobotClientManager _clientManager;
        private readonly RobotTaskProcessor _taskProcessor;
        private readonly IRobotTaskService _robotTaskService;
        public RobotWorkflowOrchestrator(
            RobotStateManager stateManager,
            RobotClientManager clientManager,
            RobotTaskProcessor taskProcessor,
            IRobotTaskService robotTaskService)
        {
            _stateManager = stateManager;
            _clientManager = clientManager;
            _taskProcessor = taskProcessor;
            _robotTaskService = robotTaskService;
        }
        public async Task ExecuteAsync(RobotSocketState latestState, Dt_RobotTask task, string ipAddress)
        {
            // ä¿æŒåŽŸæœ‰åˆ†æ”¯åˆ¤å®šæ¡ä»¶ä¸å˜ï¼Œç¡®ä¿è¡Œä¸ºä¸€è‡´ã€‚
            if (latestState.RobotRunMode == 2 && latestState.RobotControlMode == 1 && latestState.OperStatus != "Running")
            {
                if ((latestState.CurrentAction == "PickFinished" || latestState.CurrentAction == "AllPickFinished")
                    && latestState.RobotArmObject == 1
                    && task.RobotTaskState == TaskRobotStatusEnum.RobotPickFinish.GetHashCode())
                {
                    await HandlePickFinishedStateAsync(task, ipAddress);
                }
                else if ((latestState.CurrentAction == "PutFinished" || latestState.CurrentAction == "AllPutFinished")
                    && latestState.OperStatus == "Homed"
                    && latestState.RobotArmObject == 0
                    && (task.RobotTaskState == TaskRobotStatusEnum.RobotPutFinish.GetHashCode()
                    || task.RobotTaskState != TaskRobotStatusEnum.RobotExecuting.GetHashCode()))
                {
                    await HandlePutFinishedStateAsync(task, ipAddress);
                }
            }
        }
        private async Task HandlePickFinishedStateAsync(Dt_RobotTask task, string ipAddress)
        {
            string taskString = $"Putbattery,{task.RobotTargetAddress}";
            bool result = await _clientManager.SendToClientAsync(ipAddress, taskString);
            if (result)
            {
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                var stateToUpdate = _stateManager.GetState(ipAddress);
                if (stateToUpdate != null)
                {
                    stateToUpdate.CurrentTask = task;
                    if (_stateManager.TryUpdateStateSafely(ipAddress, stateToUpdate))
                    {
                        await _robotTaskService.UpdateRobotTaskAsync(task);
                    }
                }
            }
        }
        private async Task HandlePutFinishedStateAsync(Dt_RobotTask task, string ipAddress)
        {
            var stateForUpdate = _stateManager.GetState(ipAddress);
            if (stateForUpdate == null)
            {
                return;
            }
            if (!stateForUpdate.IsSplitPallet && !stateForUpdate.IsGroupPallet)
            {
                stateForUpdate.IsSplitPallet = task.RobotTaskType == RobotTaskTypeEnum.SplitPallet.GetHashCode();
                stateForUpdate.IsGroupPallet = task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode()
                    || task.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode();
            }
            if (task.RobotTaskType == RobotTaskTypeEnum.GroupPallet.GetHashCode())
            {
                const string prefix = "TRAY";
                string trayBarcode1 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
                string trayBarcode2 = RobotBarcodeGenerator.GenerateTrayBarcode(prefix);
                if (!string.IsNullOrEmpty(trayBarcode1) && !string.IsNullOrEmpty(trayBarcode2))
                {
                    stateForUpdate.CellBarcode.Add(trayBarcode1);
                    stateForUpdate.CellBarcode.Add(trayBarcode2);
                    await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
                }
            }
            else
            {
                await _taskProcessor.SendSocketRobotPickAsync(task, stateForUpdate);
            }
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/SocketServer/SocketClientGateway.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
using System.Net.Sockets;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
namespace WIDESEAWCS_Tasks.SocketServer
{
    /// <summary>
    /// TcpSocketServer çš„适配器实现,保持底层行为不变,仅做访问收口。
    /// </summary>
    public class SocketClientGateway : ISocketClientGateway
    {
        private readonly TcpSocketServer _tcpSocket;
        public SocketClientGateway(TcpSocketServer tcpSocket)
        {
            _tcpSocket = tcpSocket;
        }
        public Task<bool> SendToClientAsync(string clientId, string message)
        {
            return _tcpSocket.SendToClientAsync(clientId, message);
        }
        public Task SendMessageAsync(TcpClient client, string message)
        {
            return _tcpSocket.SendMessageAsync(client, message);
        }
        public IReadOnlyList<string> GetClientIds()
        {
            return _tcpSocket.GetClientIds();
        }
        public Task HandleClientAsync(TcpClient client, string clientId, CancellationToken cancellationToken, RobotSocketState robotCrane)
        {
            return _tcpSocket.HandleClientAsync(client, clientId, cancellationToken, robotCrane);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -1,24 +1,14 @@
using System;
using System.Collections.Generic;
using Quartz;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Routing;
using Quartz;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_Communicator;
using WIDESEAWCS_Core.Enums;
using WIDESEAWCS_Core.Helper;
using WIDESEAWCS_ITaskInfoRepository;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.DeviceBase;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
using WIDESEAWCS_QuartzJob.StackerCrane.Enum;
using WIDESEAWCS_TaskInfoService;
using WIDESEAWCS_QuartzJob.StackerCrane;
using WIDESEAWCS_Tasks.StackerCraneJob;
namespace WIDESEAWCS_Tasks
@@ -29,16 +19,24 @@
        private readonly ITaskService _taskService;
        private readonly ITaskExecuteDetailService _taskExecuteDetailService;
        private readonly ITaskRepository _taskRepository;
        private readonly IRouterService _routerService;
        private readonly StackerCraneCommandConfig _config;
        public CommonStackerCraneJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, ITaskRepository taskRepository, IRouterService routerService)
        private readonly StackerCraneCommandConfig _config;
        private readonly StackerCraneTaskSelector _taskSelector;
        private readonly StackerCraneCommandBuilder _commandBuilder;
        public CommonStackerCraneJob(
            ITaskService taskService,
            ITaskExecuteDetailService taskExecuteDetailService,
            ITaskRepository taskRepository,
            WIDESEAWCS_QuartzJob.Service.IRouterService routerService)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _taskRepository = taskRepository;
            _routerService = routerService;
            _config = LoadConfig();
            _taskSelector = new StackerCraneTaskSelector(taskService, routerService);
            _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _config);
        }
        /// <summary>
@@ -59,6 +57,7 @@
            {
                Console.WriteLine($"配置加载失败: {ex.Message},使用默认配置");
            }
            return new StackerCraneCommandConfig();
        }
@@ -74,21 +73,33 @@
                    return Task.CompletedTask;
                }
                // è®¢é˜…一次任务完成事件。
                if (!commonStackerCrane.IsEventSubscribed)
                {
                    commonStackerCrane.StackerCraneTaskCompletedEventHandler += CommonStackerCrane_StackerCraneTaskCompletedEventHandler;
                }
                if (commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                {
                    return Task.CompletedTask;
                }
                    commonStackerCrane.CheckStackerCraneTaskCompleted();
                    Dt_Task? task = GetTask(commonStackerCrane);
                    if (task != null)
                // ä»»åŠ¡é€‰æ‹©ä¸‹æ²‰åˆ°ä¸“ç”¨é€‰æ‹©å™¨ã€‚
                Dt_Task? task = _taskSelector.SelectTask(commonStackerCrane);
                if (task == null)
                    {
                        object? stackerCraneTaskCommand = ConvertToStackerCraneTaskCommand(task);
                        if (stackerCraneTaskCommand != null)
                    return Task.CompletedTask;
                }
                // å‘½ä»¤æž„建下沉到专用构建器。
                object? stackerCraneTaskCommand = _commandBuilder.ConvertToStackerCraneTaskCommand(task);
                if (stackerCraneTaskCommand == null)
                        {
                    return Task.CompletedTask;
                }
                            bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
                            if (sendFlag)
                            {
@@ -96,122 +107,26 @@
                                _taskService.UpdateTaskStatusToNext(task.TaskNum);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"CommonStackerCraneJob Error: {ex.Message}");
            }
            return Task.CompletedTask;
        }
        /// <summary>
        /// ä»»åŠ¡å®Œæˆäº‹ä»¶è®¢é˜…çš„æ–¹æ³•
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, WIDESEAWCS_QuartzJob.StackerCrane.StackerCraneTaskCompletedEventArgs e)
        private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, StackerCraneTaskCompletedEventArgs e)
        {
            CommonStackerCrane? commonStackerCrane = sender as CommonStackerCrane;
            if (commonStackerCrane != null)
            {
                //if (commonStackerCrane.GetValue<StackerCraneDBName, short>(StackerCraneDBName.WorkType) != 5)
                //{
                Console.Out.WriteLine("TaskCompleted" + e.TaskNum);
                _taskService.StackCraneTaskCompleted(e.TaskNum);
                commonStackerCrane.SetValue(StackerCraneDBName.WorkAction, 5);
                //}
            }
        }
        /// <summary>
        /// èŽ·å–ä»»åŠ¡
        /// </summary>
        /// <param name="commonStackerCrane">堆垛机对象</param>
        /// <returns></returns>
        private Dt_Task? GetTask(IStackerCrane commonStackerCrane)
        {
            Dt_Task? task = null;
            if (commonStackerCrane.LastTaskType == null)
            {
                task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
            }
            else
            {
                if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
                {
                    task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                    task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                }
                else
                {
                    task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
                }
            }
            if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                if (IsOutTaskStationAvailable(task))
                {
                    return task;
                }
                List<string> otherOutStationCodes = _routerService.QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
                    .Select(x => x.ChildPosi).ToList();
                List<Dt_Task> tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
                foreach (var alternativeTask in tasks)
                {
                    if (IsOutTaskStationAvailable(alternativeTask))
                    {
                        return alternativeTask;
                    }
                }
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
            }
            return task;
        }
        /// <summary>
        /// å‡ºåº“任务判断出库站台是否可用
        /// </summary>
        /// <param name="task">任务实体</param>
        /// <returns>如果站台可用返回true,否则返回false</returns>
        private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
                return false;
            }
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
            if (device == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
                return false;
            }
            CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            return conveyorLine.IsOccupied(router.ChildPosi);
        }
        /// <summary>
        /// ä»»åŠ¡å®žä½“è½¬æ¢æˆå‘½ä»¤Model
        /// </summary>
        public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
        {
            // æ ¹æ®é…ç½®åˆ¤æ–­å‘½ä»¤ç±»åž‹
            string commandType = GetCommandType(task.Roadway);
            // åˆ›å»ºå¹¶æž„建命令
            return commandType switch
            {
                "Formation" => BuildCommand(task, CreateFormationCommand(task)),
                _ => BuildCommand(task, CreateStandardCommand(task))
            };
        }
        private static bool SendStackerCraneCommand(IStackerCrane commonStackerCrane, object command)
@@ -222,181 +137,6 @@
                StackerCraneTaskCommand standardCommand => commonStackerCrane.SendCommand(standardCommand),
                _ => false
            };
        }
        /// <summary>
        /// æ ¹æ® Roadway èŽ·å–å‘½ä»¤ç±»åž‹
        /// </summary>
        private string GetCommandType(string roadway)
        {
            foreach (var mapping in _config.RoadwayCommandMapping)
            {
                if (roadway.Contains(mapping.Key))
                {
                    return mapping.Value;
                }
            }
            return _config.DefaultCommandType;
        }
        /// <summary>
        /// åˆ›å»ºæ ‡å‡†å †åž›æœºå‘½ä»¤
        /// </summary>
        private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task)
        {
            return new StackerCraneTaskCommand
            {
                //Barcode = task.PalletCode,
                TaskNum = task.TaskNum,
                WorkType = 1,
                WorkAction = 1
            };
        }
        /// <summary>
        /// åˆ›å»ºåˆ†å®¹å †åž›æœºå‘½ä»¤
        /// </summary>
        private static FormationStackerCraneTaskCommand CreateFormationCommand(Dt_Task task)
        {
            return new FormationStackerCraneTaskCommand
            {
                Barcode = task.PalletCode,
                TaskNum = task.TaskNum,
                WorkType = 1,
                WorkAction = 1,
                FireAlarm = 0,
                HeartBeat = 0,
                FieldName = string.Empty
            };
        }
        /// <summary>
        /// é€šç”¨å‘½ä»¤æž„建方法
        /// </summary>
        private T? BuildCommand<T>(Dt_Task task, T command) where T : class
        {
            TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
            return taskTypeGroup switch
            {
                TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),
                TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),
                TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),
                _ => command
            };
        }
        /// <summary>
        /// é€šç”¨å…¥åº“命令构建
        /// </summary>
        private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.CurrentAddress}】信息,无法获取对应的堆垛机取货站台信息");
                return null;
            }
            SetCommandProperty(command, "StartRow", Convert.ToInt16(router.SrmRow));
            SetCommandProperty(command, "StartColumn", Convert.ToInt16(router.SrmColumn));
            SetCommandProperty(command, "StartLayer", Convert.ToInt16(router.SrmLayer));
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"入库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            SetCommandProperty(command, "EndRow", endRow);
            SetCommandProperty(command, "EndColumn", endColumn);
            SetCommandProperty(command, "EndLayer", endLayer);
            return command;
        }
        /// <summary>
        /// é€šç”¨å‡ºåº“命令构建
        /// </summary>
        private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.TargetAddress}】信息,无法获取对应的堆垛机放货站台信息");
                return null;
            }
            SetCommandProperty(command, "EndRow", Convert.ToInt16(router.SrmRow));
            SetCommandProperty(command, "EndColumn", Convert.ToInt16(router.SrmColumn));
            SetCommandProperty(command, "EndLayer", Convert.ToInt16(router.SrmLayer));
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"出库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            SetCommandProperty(command, "StartRow", startRow);
            SetCommandProperty(command, "StartColumn", startColumn);
            SetCommandProperty(command, "StartLayer", startLayer);
            return command;
        }
        /// <summary>
        /// é€šç”¨ç§»åº“命令构建
        /// </summary>
        private T? BuildRelocationCommand<T>(Dt_Task task, T command) where T : class
        {
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            SetCommandProperty(command, "EndRow", endRow);
            SetCommandProperty(command, "EndColumn", endColumn);
            SetCommandProperty(command, "EndLayer", endLayer);
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            SetCommandProperty(command, "StartRow", startRow);
            SetCommandProperty(command, "StartColumn", startColumn);
            SetCommandProperty(command, "StartLayer", startLayer);
            return command;
        }
        /// <summary>
        /// ä½¿ç”¨åå°„设置命令属性
        /// </summary>
        private static void SetCommandProperty<T>(T command, string propertyName, object value) where T : class
        {
            var property = typeof(T).GetProperty(propertyName);
            property?.SetValue(command, value);
        }
        /// <summary>
        /// è§£æžåœ°å€å­—符串(格式:行-列-层)
        /// </summary>
        private bool TryParseAddress(string address, out short row, out short column, out short layer)
        {
            row = column = layer = 0;
            string[] parts = address.Split("-");
            if (parts.Length != 3)
            {
                return false;
            }
            return short.TryParse(parts[0], out row)
                && short.TryParse(parts[1], out column)
                && short.TryParse(parts[2], out layer);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,189 @@
using System;
using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
using WIDESEAWCS_Tasks.StackerCraneJob;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// å †åž›æœºå‘½ä»¤æž„建器:封装任务到命令对象的转换与地址解析。
    /// </summary>
    public class StackerCraneCommandBuilder
    {
        private readonly ITaskService _taskService;
        private readonly IRouterService _routerService;
        private readonly StackerCraneCommandConfig _config;
        public StackerCraneCommandBuilder(
            ITaskService taskService,
            IRouterService routerService,
            StackerCraneCommandConfig config)
        {
            _taskService = taskService;
            _routerService = routerService;
            _config = config;
        }
        public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
        {
            string commandType = GetCommandType(task.Roadway);
            return commandType switch
            {
                "Formation" => BuildCommand(task, CreateFormationCommand(task)),
                _ => BuildCommand(task, CreateStandardCommand(task))
            };
        }
        private string GetCommandType(string roadway)
        {
            foreach (var mapping in _config.RoadwayCommandMapping)
            {
                if (roadway.Contains(mapping.Key))
                {
                    return mapping.Value;
                }
            }
            return _config.DefaultCommandType;
        }
        private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task)
        {
            return new StackerCraneTaskCommand
            {
                TaskNum = task.TaskNum,
                WorkType = 1,
                WorkAction = 1
            };
        }
        private static FormationStackerCraneTaskCommand CreateFormationCommand(Dt_Task task)
        {
            return new FormationStackerCraneTaskCommand
            {
                Barcode = task.PalletCode,
                TaskNum = task.TaskNum,
                WorkType = 1,
                WorkAction = 1,
                FireAlarm = 0,
                HeartBeat = 0,
                FieldName = string.Empty
            };
        }
        private T? BuildCommand<T>(Dt_Task task, T command) where T : class
        {
            TaskTypeGroup taskTypeGroup = task.TaskType.GetTaskTypeGroup();
            return taskTypeGroup switch
            {
                TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),
                TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),
                TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),
                _ => command
            };
        }
        private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.CurrentAddress}】信息,无法获取对应的堆垛机取货站台信息");
                return null;
            }
            SetCommandProperty(command, "StartRow", Convert.ToInt16(router.SrmRow));
            SetCommandProperty(command, "StartColumn", Convert.ToInt16(router.SrmColumn));
            SetCommandProperty(command, "StartLayer", Convert.ToInt16(router.SrmLayer));
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"入库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            SetCommandProperty(command, "EndRow", endRow);
            SetCommandProperty(command, "EndColumn", endColumn);
            SetCommandProperty(command, "EndLayer", endLayer);
            return command;
        }
        private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.TargetAddress}】信息,无法获取对应的堆垛机放货站台信息");
                return null;
            }
            SetCommandProperty(command, "EndRow", Convert.ToInt16(router.SrmRow));
            SetCommandProperty(command, "EndColumn", Convert.ToInt16(router.SrmColumn));
            SetCommandProperty(command, "EndLayer", Convert.ToInt16(router.SrmLayer));
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"出库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            SetCommandProperty(command, "StartRow", startRow);
            SetCommandProperty(command, "StartColumn", startColumn);
            SetCommandProperty(command, "StartLayer", startLayer);
            return command;
        }
        private T? BuildRelocationCommand<T>(Dt_Task task, T command) where T : class
        {
            if (!TryParseAddress(task.NextAddress, out short endRow, out short endColumn, out short endLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务终点错误,终点:【{task.NextAddress}】");
                return null;
            }
            SetCommandProperty(command, "EndRow", endRow);
            SetCommandProperty(command, "EndColumn", endColumn);
            SetCommandProperty(command, "EndLayer", endLayer);
            if (!TryParseAddress(task.CurrentAddress, out short startRow, out short startColumn, out short startLayer))
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"移库任务起点错误,起点:【{task.CurrentAddress}】");
                return null;
            }
            SetCommandProperty(command, "StartRow", startRow);
            SetCommandProperty(command, "StartColumn", startColumn);
            SetCommandProperty(command, "StartLayer", startLayer);
            return command;
        }
        private static void SetCommandProperty<T>(T command, string propertyName, object value) where T : class
        {
            var property = typeof(T).GetProperty(propertyName);
            property?.SetValue(command, value);
        }
        private static bool TryParseAddress(string address, out short row, out short column, out short layer)
        {
            row = column = layer = 0;
            string[] parts = address.Split("-");
            if (parts.Length != 3)
            {
                return false;
            }
            return short.TryParse(parts[0], out row)
                && short.TryParse(parts[1], out column)
                && short.TryParse(parts[2], out layer);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,90 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoService;
using WIDESEAWCS_Model.Models;
using WIDESEAWCS_QuartzJob;
using WIDESEAWCS_QuartzJob.Models;
using WIDESEAWCS_QuartzJob.Service;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// å †åž›æœºä»»åŠ¡é€‰æ‹©å™¨ï¼šå°è£…ä»»åŠ¡æŒ‘é€‰ä¸Žç«™å°å¯ç”¨æ€§åˆ¤æ–­ã€‚
    /// </summary>
    public class StackerCraneTaskSelector
    {
        private readonly ITaskService _taskService;
        private readonly IRouterService _routerService;
        public StackerCraneTaskSelector(ITaskService taskService, IRouterService routerService)
        {
            _taskService = taskService;
            _routerService = routerService;
        }
        public Dt_Task? SelectTask(IStackerCrane commonStackerCrane)
        {
            Dt_Task? task;
            if (commonStackerCrane.LastTaskType == null)
            {
                task = _taskService.QueryStackerCraneTask(commonStackerCrane.DeviceCode);
            }
            else if (commonStackerCrane.LastTaskType.GetValueOrDefault().GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
                task ??= _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
            }
            else
            {
                task = _taskService.QueryStackerCraneOutTask(commonStackerCrane.DeviceCode);
            }
            if (task != null && task.TaskType.GetTaskTypeGroup() == TaskTypeGroup.OutbondGroup)
            {
                if (IsOutTaskStationAvailable(task))
                {
                    return task;
                }
                var otherOutStationCodes = _routerService
                    .QueryNextRoutes(commonStackerCrane.DeviceCode, task.NextAddress, task.TaskType)
                    .Select(x => x.ChildPosi)
                    .ToList();
                var tasks = _taskService.QueryStackerCraneOutTasks(commonStackerCrane.DeviceCode, otherOutStationCodes);
                foreach (var alternativeTask in tasks)
                {
                    if (IsOutTaskStationAvailable(alternativeTask))
                    {
                        return alternativeTask;
                    }
                }
                task = _taskService.QueryStackerCraneInTask(commonStackerCrane.DeviceCode);
            }
            return task;
        }
        private bool IsOutTaskStationAvailable([NotNull] Dt_Task task)
        {
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.NextAddress, task.TaskType);
            if (router == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到站台【{task.NextAddress}】信息,无法校验站台");
                return false;
            }
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceCode == router.ChildPosiDeviceCode);
            if (device == null)
            {
                _taskService.UpdateTaskExceptionMessage(task.TaskNum, $"未找到出库站台【{router.ChildPosiDeviceCode}】对应的通讯对象,无法判断出库站台是否被占用");
                return false;
            }
            CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            return conveyorLine.IsOccupied(router.ChildPosi);
        }
    }
}
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-common-stacker-crane-job-refactor-plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
# CommonStackerCraneJob Refactor Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** åœ¨ä¸æ”¹å˜ä¸šåŠ¡è¡Œä¸ºçš„å‰æä¸‹ï¼Œé™ä½Ž `CommonStackerCraneJob` å¤æ‚度,拆分“任务选择”和“命令构建”职责。
**Architecture:** ä¿ç•™ `CommonStackerCraneJob` ä½œä¸º Quartz Job å…¥å£ï¼Œåªè´Ÿè´£è°ƒåº¦æµç¨‹ï¼›å°†ä»»åŠ¡é€‰æ‹©é€»è¾‘å’Œå‘½ä»¤æž„å»ºé€»è¾‘åˆ†åˆ«ä¸‹æ²‰åˆ°åŒç›®å½•æ–°ç±»ã€‚é€šè¿‡æž„é€ å‡½æ•°å†…ç»„è£…ä¾èµ–ï¼ˆä¸ä¿®æ”¹å…¨å±€ DI),确保改动范围仅限 `StackerCraneJob` ç›®å½•。
**Tech Stack:** .NET 6, Quartz, çŽ°æœ‰ WCS Task/Router æœåŠ¡æŽ¥å£
---
## Chunk 1: ç»“构拆分与职责落位
### Task 1: æ‹†åˆ†ä»»åŠ¡é€‰æ‹©å™¨
**Files:**
- Create: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`
- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`
- [ ] Step 1: æ–°å»ºä»»åŠ¡é€‰æ‹©å™¨ç±»ï¼Œè¿ç§» `GetTask` ä¸Ž `IsOutTaskStationAvailable` é€»è¾‘
- [ ] Step 2: åœ¨ Job ä¸­é€šè¿‡ç§æœ‰å­—段持有任务选择器实例
- [ ] Step 3: å°† `GetTask` è°ƒç”¨æ”¹ä¸ºä»»åŠ¡é€‰æ‹©å™¨
### Task 2: æ‹†åˆ†å‘½ä»¤æž„建器
**Files:**
- Create: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs`
- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`
- [ ] Step 1: æ–°å»ºå‘½ä»¤æž„建器类,迁移 `ConvertToStackerCraneTaskCommand` ä¸Žå‘½ä»¤æž„建分支逻辑
- [ ] Step 2: ä¿ç•™é…ç½®è¯»å–在 Job ä¸­ï¼Œå‘½ä»¤æž„建器接受配置与服务依赖
- [ ] Step 3: Job ä¸­æ›¿æ¢åŽŸæœ‰å‘½ä»¤æž„å»ºè°ƒç”¨
## Chunk 2: æ¸…理与验证
### Task 3: Job å…¥å£ç˜¦èº«ä¸Žæ³¨é‡Šè¡¥å…¨
**Files:**
- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`
- [ ] Step 1: ç§»é™¤å·²è¿ç§»ç§æœ‰æ–¹æ³•及无用 using
- [ ] Step 2: ä¿ç•™å¹¶è¡¥å……关键流程注释(订阅、任务获取、命令下发)
### Task 4: ç¼–译验收
**Files:**
- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs`(如需微调)
- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneTaskSelector.cs`(如需微调)
- Modify: `WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs`(如需微调)
- [ ] Step 1: è¿è¡Œ `dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj -c Debug`
- [ ] Step 2: è‹¥å¤±è´¥ï¼ŒæŒ‰ç¼–译错误最小修复直到通过
- [ ] Step 3: è¾“出结果与改动文件清单
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-conveyorline-dispatch-handler-refactor-plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
# ConveyorLineDispatchHandler Refactor Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** åœ¨ä¸æ”¹å˜è°ƒåº¦è¡Œä¸ºçš„前提下,降低 `ConveyorLineDispatchHandler` å¤æ‚度,拆分“地址轮询策略”和“任务筛选逻辑”。
**Architecture:** ä¿ç•™ `ConveyorLineDispatchHandler` ä½œä¸ºæµç¨‹å…¥å£ï¼›å°†å¯æ›¿æ¢ç­–略(轮询地址选择)与任务过滤逻辑拆到同目录新类中,避免单文件承担全部职责。
**Tech Stack:** .NET 6, çŽ°æœ‰ TaskService/RouterService ä¸Ž DTO æ¨¡åž‹
---
## Chunk 1: ç»“构拆分
### Task 1: æ‹†åˆ†åœ°å€é€‰æ‹©ç­–ç•¥
**Files:**
- Create: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs`
- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs`
- [ ] Step 1: è¿ç§»è½®è¯¢åœ°å€é€‰æ‹©ä¸ŽçŠ¶æ€ç»´æŠ¤é€»è¾‘
- [ ] Step 2: Handler ä¸­æ”¹ä¸ºè°ƒç”¨é€‰æ‹©å™¨
### Task 2: æ‹†åˆ†ä»»åŠ¡ç­›é€‰å™¨
**Files:**
- Create: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTaskFilter.cs`
- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs`
- [ ] Step 1: è¿ç§»ä»»åŠ¡è¿‡æ»¤æ¡ä»¶å’Œå€™é€‰ä»»åŠ¡æž„å»º
- [ ] Step 2: Handler ä¸­æ”¹ä¸ºè°ƒç”¨è¿‡æ»¤å™¨
## Chunk 2: æ¸…理与验证
### Task 3: å…¥å£ç˜¦èº«ä¸Žæ³¨é‡Š
**Files:**
- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineDispatchHandler.cs`
- [ ] Step 1: åˆ é™¤è¿ç§»åŽç§æœ‰æ–¹æ³•
- [ ] Step 2: ä¿ç•™å…³é”®æµç¨‹æ³¨é‡Šï¼Œè¯´æ˜Žè¡Œä¸ºä¿æŒç‚¹
### Task 4: ç¼–译验收
**Files:**
- Modify: `WIDESEAWCS_Tasks/ConveyorLineNewJob/*.cs`(如需微调)
- [ ] Step 1: è¿è¡Œ `dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj -c Debug`
- [ ] Step 2: æœ€å°ä¿®å¤ç›´åˆ°é€šè¿‡
Code/WCS/WIDESEAWCS_Server/docs/superpowers/plans/2026-03-11-formation-stacker-crane-job-refactor-plan.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
# FormationCommonStackerCraneJob Refactor Implementation Plan
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** åœ¨ä¸æ”¹å˜ä¸šåŠ¡è¡Œä¸ºçš„å‰æä¸‹ï¼Œé™ä½Ž `FormationCommonStackerCraneJob` å¤æ‚度,拆分“任务选择”和“命令构建”职责。
**Architecture:** ä¿ç•™ `FormationCommonStackerCraneJob` ä½œä¸º Quartz Job å…¥å£ï¼Œä»…负责调度;将任务选择和命令构建下沉到同目录新类,通过构造函数组装依赖,改动限定在 `FormationStackerCraneJob` ç›®å½•。
**Tech Stack:** .NET 6, Quartz, çŽ°æœ‰ WCS Task/Router æœåŠ¡æŽ¥å£
---
## Chunk 1: ç»“构拆分
### Task 1: æ‹†åˆ†ä»»åŠ¡é€‰æ‹©å™¨
**Files:**
- Create: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneTaskSelector.cs`
- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs`
- [ ] Step 1: è¿ç§»ä»»åŠ¡æŒ‘é€‰å’Œç«™å°å¯ç”¨æ€§é€»è¾‘
- [ ] Step 2: Job ä¸­æ”¹ä¸ºè°ƒç”¨ä»»åŠ¡é€‰æ‹©å™¨
### Task 2: æ‹†åˆ†å‘½ä»¤æž„建器
**Files:**
- Create: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationStackerCraneCommandBuilder.cs`
- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs`
- [ ] Step 1: è¿ç§»å‘½ä»¤è½¬æ¢ä¸Žåœ°å€è§£æžé€»è¾‘
- [ ] Step 2: Job ä¸­æ”¹ä¸ºè°ƒç”¨å‘½ä»¤æž„建器
## Chunk 2: æ¸…理与验证
### Task 3: Job å…¥å£ç˜¦èº«
**Files:**
- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/FormationCommonStackerCraneJob.cs`
- [ ] Step 1: åˆ é™¤è¿ç§»åŽçš„私有方法和无用 using
- [ ] Step 2: è¡¥å…³é”®æµç¨‹æ³¨é‡Š
### Task 4: ç¼–译验收
**Files:**
- Modify: `WIDESEAWCS_Tasks/FormationStackerCraneJob/*.cs`(如需微调)
- [ ] Step 1: è¿è¡Œ `dotnet build WIDESEAWCS_Tasks/WIDESEAWCS_Tasks.csproj -c Debug`
- [ ] Step 2: æœ€å°ä¿®å¤ç›´åˆ°é€šè¿‡