wanshenmean
2026-03-26 8e42d0c1b7ae36cff2e7c69999117911a4b6f300
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/StackerCraneCommandBuilder.cs
@@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics.CodeAnalysis;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_ITaskInfoService;
@@ -10,14 +10,40 @@
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// 堆垛机命令构建器:封装任务到命令对象的转换与地址解析。
    /// 堆垛机命令构建器 - 封装任务到命令对象的转换与地址解析
    /// </summary>
    /// <remarks>
    /// 核心职责:
    /// 1. 根据巷道类型选择命令格式(标准命令/成型命令)
    /// 2. 构建入库、出库、移库任务的命令对象
    /// 3. 解析任务地址(行-列-层格式)到命令坐标
    /// 4. 查询路由信息,确定堆垛机的取货/放货站台
    ///
    /// 地址格式约定:地址字符串格式为 "行-列-层",例如 "1-2-3" 表示第1行、第2列、第3层。
    /// </remarks>
    public class StackerCraneCommandBuilder
    {
        /// <summary>
        /// 任务服务
        /// </summary>
        private readonly ITaskService _taskService;
        /// <summary>
        /// 路由服务
        /// </summary>
        private readonly IRouterService _routerService;
        /// <summary>
        /// 堆垛机命令配置
        /// </summary>
        private readonly StackerCraneCommandConfig _config;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="taskService">任务服务</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="config">命令配置</param>
        public StackerCraneCommandBuilder(
            ITaskService taskService,
            IRouterService routerService,
@@ -28,16 +54,36 @@
            _config = config;
        }
        /// <summary>
        /// 将任务转换为堆垛机命令
        /// </summary>
        /// <remarks>
        /// 根据巷道类型选择不同的命令构建策略。
        /// </remarks>
        /// <param name="task">任务对象</param>
        /// <returns>堆垛机命令对象,转换失败返回 null</returns>
        public object? ConvertToStackerCraneTaskCommand([NotNull] Dt_Task task)
        {
            // 根据巷道获取命令类型
            string commandType = GetCommandType(task.Roadway);
            // 根据命令类型调用相应的构建方法
            return commandType switch
            {
                "Formation" => BuildCommand(task, CreateFormationCommand(task)),
                _ => BuildCommand(task, CreateStandardCommand(task))
                "Formation" => BuildCommand(task, CreateFormationCommand(task)),  // 成型命令
                _ => BuildCommand(task, CreateStandardCommand(task))              // 标准命令
            };
        }
        /// <summary>
        /// 根据巷道获取命令类型
        /// </summary>
        /// <remarks>
        /// 遍历配置中的映射关系,找到匹配的命令类型。
        /// 如果不匹配任何映射,返回默认命令类型。
        /// </remarks>
        /// <param name="roadway">巷道编码</param>
        /// <returns>命令类型(Standard 或 Formation)</returns>
        private string GetCommandType(string roadway)
        {
            foreach (var mapping in _config.RoadwayCommandMapping)
@@ -51,45 +97,88 @@
            return _config.DefaultCommandType;
        }
        /// <summary>
        /// 创建标准命令
        /// </summary>
        /// <remarks>
        /// 用于标准堆垛机(GW、CW 开头巷道)。
        /// </remarks>
        /// <param name="task">任务对象</param>
        /// <returns>标准命令对象</returns>
        private static StackerCraneTaskCommand CreateStandardCommand(Dt_Task task)
        {
            return new StackerCraneTaskCommand
            {
                TaskNum = task.TaskNum,
                WorkType = 1,
                WorkAction = 1
                TaskNum = task.TaskNum,   // 任务号
                WorkType = 1,             // 作业类型
                WorkAction = 1            // 作业指令:开始执行
            };
        }
        /// <summary>
        /// 创建成型命令
        /// </summary>
        /// <remarks>
        /// 用于成型堆垛机(HC 开头巷道)。
        /// 包含条码字段,用于电池追溯。
        /// </remarks>
        /// <param name="task">任务对象</param>
        /// <returns>成型命令对象</returns>
        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
                Barcode = task.PalletCode,   // 托盘条码
                TaskNum = task.TaskNum,      // 任务号
                WorkType = 1,               // 作业类型
                WorkAction = 1,             // 作业指令:开始执行
                FireAlarm = 0,              // 火警:正常
                HeartBeat = 0,              // 心跳
                FieldName = string.Empty     // 保留字段
            };
        }
        /// <summary>
        /// 构建命令(通用)
        /// </summary>
        /// <remarks>
        /// 根据任务类型(入库/出库/移库)调用相应的构建方法。
        /// </remarks>
        /// <typeparam name="T">命令类型</typeparam>
        /// <param name="task">任务对象</param>
        /// <param name="command">初始命令对象</param>
        /// <returns>填充好的命令对象</returns>
        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
                TaskTypeGroup.InboundGroup => BuildInboundCommand(task, command),    // 入库
                TaskTypeGroup.OutbondGroup => BuildOutboundCommand(task, command),  // 出库
                TaskTypeGroup.RelocationGroup => BuildRelocationCommand(task, command),  // 移库
                _ => command  // 未知类型,返回原命令
            };
        }
        /// <summary>
        /// 构建入库命令
        /// </summary>
        /// <remarks>
        /// 入库任务需要:
        /// 1. 查询堆垛机取货站台(根据当前地址和任务类型)
        /// 2. 设置起始坐标(来自站台)
        /// 3. 解析目标地址,设置终点坐标
        /// </remarks>
        /// <typeparam name="T">命令类型</typeparam>
        /// <param name="task">任务对象</param>
        /// <param name="command">命令对象</param>
        /// <returns>填充好的命令对象</returns>
        private T? BuildInboundCommand<T>(Dt_Task task, T command) where T : class
        {
            // 确定任务类型(空托盘用特殊类型 100)
            int taskType = 0;
            if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
            {
@@ -97,23 +186,29 @@
            }
            else
                taskType = task.TaskType;
            // 查询堆垛机取货站台路由
            Dt_Router? router = _routerService.QueryNextRoute(task.CurrentAddress, task.Roadway, 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);
@@ -121,8 +216,22 @@
            return command;
        }
        /// <summary>
        /// 构建出库命令
        /// </summary>
        /// <remarks>
        /// 出库任务需要:
        /// 1. 查询堆垛机放货站台(根据目标地址和任务类型)
        /// 2. 设置终点坐标(来自站台)
        /// 3. 解析起始地址,设置起点坐标
        /// </remarks>
        /// <typeparam name="T">命令类型</typeparam>
        /// <param name="task">任务对象</param>
        /// <param name="command">命令对象</param>
        /// <returns>填充好的命令对象</returns>
        private T? BuildOutboundCommand<T>(Dt_Task task, T command) where T : class
        {
            // 确定任务类型
            int taskType = 0;
            if (task.TaskType == (int)TaskOutboundTypeEnum.OutEmpty)
            {
@@ -131,6 +240,7 @@
            else
                taskType = task.TaskType;
            // 查询堆垛机放货站台路由
            Dt_Router? router = _routerService.QueryNextRoute(task.Roadway, task.TargetAddress, taskType);
            if (router == null)
            {
@@ -138,16 +248,19 @@
                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);
@@ -155,24 +268,41 @@
            return command;
        }
        /// <summary>
        /// 构建移库命令
        /// </summary>
        /// <remarks>
        /// 移库任务需要:
        /// 1. 解析目标地址(新的库位)
        /// 2. 解析起始地址(原来的库位)
        /// 3. 设置起止坐标
        /// </remarks>
        /// <typeparam name="T">命令类型</typeparam>
        /// <param name="task">任务对象</param>
        /// <param name="command">命令对象</param>
        /// <returns>填充好的命令对象</returns>
        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);
@@ -180,22 +310,45 @@
            return command;
        }
        /// <summary>
        /// 设置命令属性值(通过反射)
        /// </summary>
        /// <remarks>
        /// 使用反射动态设置命令对象的属性值。
        /// </remarks>
        /// <typeparam name="T">命令类型</typeparam>
        /// <param name="command">命令对象</param>
        /// <param name="propertyName">属性名称</param>
        /// <param name="value">属性值</param>
        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>
        /// <remarks>
        /// 地址格式:行-列-层,例如 "1-2-3" 表示第1行、第2列、第3层。
        /// </remarks>
        /// <param name="address">地址字符串</param>
        /// <param name="row">解析出的行坐标</param>
        /// <param name="column">解析出的列坐标</param>
        /// <param name="layer">解析出的层坐标</param>
        /// <returns>解析成功返回 true</returns>
        private static bool TryParseAddress(string address, out short row, out short column, out short layer)
        {
            row = column = layer = 0;
            string[] parts = address.Split("-");
            // 按 "-" 分隔地址
            string[] parts = address.Split('-');
            if (parts.Length != 3)
            {
                return false;
            }
            // 解析各部分为 short 类型
            return short.TryParse(parts[0], out row)
                && short.TryParse(parts[1], out column)
                && short.TryParse(parts[2], out layer);