wanshenmean
2026-03-27 bf2aa9dd56432a74940ca1bb08fb4d7eaee37045
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/StackerCraneJob/CommonStackerCraneJob.cs
@@ -1,402 +1,313 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Routing;
using Microsoft.Extensions.Logging;
using Quartz;
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
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;
using WIDESEA_Core;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_QuartzJob.Service;
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// 堆垛机任务作业(Quartz Job)- 核心调度逻辑
    /// </summary>
    /// <remarks>
    /// Quartz 定时任务,负责堆垛机的任务调度。
    /// 使用 [DisallowConcurrentExecution] 禁止并发执行,确保同一堆垛机的任务串行处理。
    ///
    /// 核心职责:
    /// 1. 从配置文件加载命令类型映射
    /// 2. 检查堆垛机任务完成状态
    /// 3. 选择合适的任务(委托给 StackerCraneTaskSelector)
    /// 4. 构建命令对象(委托给 StackerCraneCommandBuilder)
    /// 5. 发送命令到堆垛机
    /// 6. 处理任务完成事件
    ///
    /// 架构设计:
    /// - StackerCraneTaskSelector:负责选择合适的任务
    /// - StackerCraneCommandBuilder:负责将任务转换为命令对象
    /// - CommonStackerCraneJob:负责调度流程控制
    /// </remarks>
    [DisallowConcurrentExecution]
    public class CommonStackerCraneJob : IJob
    {
        /// <summary>
        /// 任务服务
        /// </summary>
        private readonly ITaskService _taskService;
        /// <summary>
        /// 任务执行明细服务
        /// </summary>
        private readonly ITaskExecuteDetailService _taskExecuteDetailService;
        /// <summary>
        /// 任务仓储
        /// </summary>
        private readonly ITaskRepository _taskRepository;
        private readonly IRouterService _routerService;
        /// <summary>
        /// 堆垛机命令配置
        /// </summary>
        /// <remarks>
        /// 包含巷道与命令类型的映射关系。
        /// 从 JSON 文件加载。
        /// </remarks>
        private readonly StackerCraneCommandConfig _config;
        public CommonStackerCraneJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, ITaskRepository taskRepository, IRouterService routerService)
        /// <summary>
        /// 堆垛机任务选择器
        /// </summary>
        /// <remarks>
        /// 负责选择合适的任务进行执行。
        /// </remarks>
        private readonly StackerCraneTaskSelector _taskSelector;
        /// <summary>
        /// 堆垛机命令构建器
        /// </summary>
        /// <remarks>
        /// 负责将任务转换为堆垛机可执行的命令对象。
        /// </remarks>
        private readonly StackerCraneCommandBuilder _commandBuilder;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger<CommonStackerCraneJob> _logger;
        /// <summary>
        /// 堆垛机设备编码
        /// </summary>
        private string _deviceCode = string.Empty;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="taskService">任务服务</param>
        /// <param name="taskExecuteDetailService">任务执行明细服务</param>
        /// <param name="taskRepository">任务仓储</param>
        /// <param name="routerService">路由服务</param>
        /// <param name="httpClientHelper">HTTP 客户端帮助类</param>
        /// <param name="logger">日志记录器</param>
        public CommonStackerCraneJob(
            ITaskService taskService,
            ITaskExecuteDetailService taskExecuteDetailService,
            ITaskRepository taskRepository,
            IRouterService routerService,
            HttpClientHelper httpClientHelper,
            ILogger<CommonStackerCraneJob> logger)
        {
            _taskService = taskService;
            _taskExecuteDetailService = taskExecuteDetailService;
            _taskRepository = taskRepository;
            _routerService = routerService;
            _logger = logger;
            // 加载配置文件
            _config = LoadConfig();
            // 初始化任务选择器
            _taskSelector = new StackerCraneTaskSelector(taskService, routerService, httpClientHelper, _logger);
            // 初始化命令构建器
            _commandBuilder = new StackerCraneCommandBuilder(taskService, routerService, _config, _logger);
        }
        /// <summary>
        /// 加载配置(优先级:配置文件 > 默认配置)
        /// 加载配置文件(优先级:配置文件 > 默认配置)
        /// </summary>
        /// <remarks>
        /// 从应用程序目录下的 StackerCraneJob/stackercrane-command-config.json 读取配置。
        /// 如果文件不存在或解析失败,使用默认配置。
        /// </remarks>
        /// <returns>堆垛机命令配置</returns>
        private static StackerCraneCommandConfig LoadConfig()
        {
            try
            {
                // 构造配置文件路径
                string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "StackerCraneJob", "stackercrane-command-config.json");
                if (File.Exists(configPath))
                {
                    // 读取并解析 JSON 配置
                    string json = File.ReadAllText(configPath);
                    return System.Text.Json.JsonSerializer.Deserialize<StackerCraneCommandConfig>(json) ?? new StackerCraneCommandConfig();
                }
            }
            catch (Exception ex)
            {
                // 配置加载失败,使用默认配置
                Console.WriteLine($"配置加载失败: {ex.Message},使用默认配置");
            }
            return new StackerCraneCommandConfig();
        }
        /// <summary>
        /// Quartz Job 的执行入口
        /// </summary>
        /// <remarks>
        /// 执行流程:
        /// 1. 从 JobDataMap 获取堆垛机设备信息
        /// 2. 订阅任务完成事件(仅第一次执行时)
        /// 3. 检查堆垛机任务完成状态
        /// 4. 检查是否可以发送任务
        /// 5. 选择任务
        /// 6. 构建命令
        /// 7. 发送命令
        /// 8. 更新任务状态和堆垛机状态
        /// </remarks>
        /// <param name="context">Quartz 作业执行上下文</param>
        public Task Execute(IJobExecutionContext context)
        {
            try
            {
                Console.WriteLine(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + " CommonStackerCraneJob Start");
                // 从 JobDataMap 获取堆垛机设备参数
                bool flag = context.JobDetail.JobDataMap.TryGetValue("JobParams", out object? value);
                if (!flag || value is not IStackerCrane commonStackerCrane)
                {
                    // 参数无效,直接返回
                    _logger.LogWarning("Execute:参数无效");
                    QuartzLogger.Warn("Execute:参数无效", "CommonStackerCraneJob");
                    return Task.CompletedTask;
                }
                _deviceCode = commonStackerCrane.DeviceCode;
                // ========== 订阅任务完成事件(全局只订阅一次) ==========
                if (!commonStackerCrane.IsEventSubscribed)
                {
                    // 绑定任务完成事件处理方法
                    commonStackerCrane.StackerCraneTaskCompletedEventHandler += CommonStackerCrane_StackerCraneTaskCompletedEventHandler;
                    _logger.LogInformation("Execute:订阅任务完成事件,设备: {DeviceCode}", _deviceCode);
                    QuartzLogger.Info($"订阅任务完成事件", _deviceCode);
                }
                if (commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                {
                    commonStackerCrane.CheckStackerCraneTaskCompleted();
                // ========== 检查堆垛机任务完成状态 ==========
                commonStackerCrane.CheckStackerCraneTaskCompleted();
                _logger.LogDebug("Execute:检查任务完成状态,设备: {DeviceCode}", _deviceCode);
                QuartzLogger.Debug($"检查任务完成状态,设备: {_deviceCode}", _deviceCode);
                    Dt_Task? task = GetTask(commonStackerCrane);
                    if (task != null)
                    {
                        object? stackerCraneTaskCommand = ConvertToStackerCraneTaskCommand(task);
                        if (stackerCraneTaskCommand != null)
                        {
                            bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
                            if (sendFlag)
                            {
                                commonStackerCrane.LastTaskType = task.TaskType;
                                _taskService.UpdateTaskStatusToNext(task.TaskNum);
                            }
                        }
                    }
                // ========== 检查是否可以发送新任务 ==========
                if (!commonStackerCrane.IsCanSendTask(commonStackerCrane.Communicator, commonStackerCrane.DeviceProDTOs, commonStackerCrane.DeviceProtocolDetailDTOs))
                {
                    // 堆垛机不可用(如正在执行上一任务),直接返回
                    _logger.LogDebug("Execute:堆垛机不可用,设备: {DeviceCode}", _deviceCode);
                    QuartzLogger.Debug($"堆垛机不可用,设备: {_deviceCode}", _deviceCode);
                    return Task.CompletedTask;
                }
                // ========== 选择任务 ==========
                // 任务选择下沉到专用选择器
                Dt_Task? task = _taskSelector.SelectTask(commonStackerCrane);
                if (task == null)
                {
                    // 没有可用任务
                    _logger.LogDebug("Execute:没有可用任务,设备: {DeviceCode}", _deviceCode);
                    QuartzLogger.Debug($"没有可用任务,设备: {_deviceCode}", _deviceCode);
                    return Task.CompletedTask;
                }
                _logger.LogInformation("Execute:选择任务,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                QuartzLogger.Info($"选择任务,任务号: {task.TaskNum}", _deviceCode);
                // ========== 构建命令 ==========
                // 命令构建下沉到专用构建器
                object? stackerCraneTaskCommand = _commandBuilder.ConvertToStackerCraneTaskCommand(task);
                if (stackerCraneTaskCommand == null)
                {
                    // 命令构建失败
                    _logger.LogWarning("Execute:命令构建失败,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                    QuartzLogger.Warn($"命令构建失败,任务号: {task.TaskNum}", _deviceCode);
                    return Task.CompletedTask;
                }
                // ========== 发送命令 ==========
                bool sendFlag = SendStackerCraneCommand(commonStackerCrane, stackerCraneTaskCommand);
                if (sendFlag)
                {
                    // 发送成功,更新状态
                    commonStackerCrane.LastTaskType = task.TaskType;
                    _taskService.UpdateTaskStatusToNext(task.TaskNum);
                    _logger.LogInformation("Execute:命令发送成功,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                    QuartzLogger.Info($"命令发送成功,任务号: {task.TaskNum}", _deviceCode);
                }
                else
                {
                    _logger.LogError("Execute:命令发送失败,设备: {DeviceCode},任务号: {TaskNum}", _deviceCode, task.TaskNum);
                    QuartzLogger.Error($"命令发送失败", _deviceCode);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"CommonStackerCraneJob Error: {ex.Message}");
                // 记录异常
                _logger.LogError(ex, "Execute:执行异常,设备: {DeviceCode}", _deviceCode);
                QuartzLogger.Error($"执行异常: {ex.Message}", _deviceCode, ex);
            }
            return Task.CompletedTask;
        }
        /// <summary>
        /// 任务完成事件订阅的方法
        /// 堆垛机任务完成事件处理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, WIDESEAWCS_QuartzJob.StackerCrane.StackerCraneTaskCompletedEventArgs e)
        /// <remarks>
        /// 当堆垛机报告任务完成时调用此方法。
        /// 处理:
        /// 1. 记录日志
        /// 2. 更新任务状态为完成
        /// 3. 清除堆垛机的作业指令
        /// </remarks>
        /// <param name="sender">事件发送者(堆垛机设备)</param>
        /// <param name="e">事件参数,包含完成的任务号</param>
        private void CommonStackerCrane_StackerCraneTaskCompletedEventHandler(object? sender, StackerCraneTaskCompletedEventArgs e)
        {
            CommonStackerCrane? commonStackerCrane = sender as CommonStackerCrane;
            if (commonStackerCrane != null)
            IStackerCrane? stackerCrane = sender as IStackerCrane;
            if (stackerCrane != null)
            {
                //if (commonStackerCrane.GetValue<StackerCraneDBName, short>(StackerCraneDBName.WorkType) != 5)
                //{
                    Console.Out.WriteLine("TaskCompleted" + e.TaskNum);
                    _taskService.StackCraneTaskCompleted(e.TaskNum);
                    commonStackerCrane.SetValue(StackerCraneDBName.WorkType, 5);
                //}
                // 记录日志
                _logger.LogInformation("CommonStackerCrane_StackerCraneTaskCompletedEventHandler:任务完成,任务号: {TaskNum}", e.TaskNum);
                QuartzLogger.Info($"任务完成,任务号: {e.TaskNum}", stackerCrane.DeviceCode);
                // 更新任务状态为完成
                _taskService.StackCraneTaskCompleted(e.TaskNum);
                // 清除堆垛机的作业指令(设置为 2,表示空闲)
                stackerCrane.SetValue(StackerCraneDBName.WorkAction, 2);
            }
        }
        /// <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))
            };
        }
        /// <remarks>
        /// 根据命令类型调用相应的发送方法。
        /// 支持标准命令和成型命令两种格式。
        /// </remarks>
        /// <param name="commonStackerCrane">堆垛机设备对象</param>
        /// <param name="command">命令对象</param>
        /// <returns>发送是否成功</returns>
        private static bool SendStackerCraneCommand(IStackerCrane commonStackerCrane, object command)
        {
            return command switch
            {
                // 成型命令(如 HC 巷道)
                FormationStackerCraneTaskCommand formationCommand => commonStackerCrane.SendCommand(formationCommand),
                // 标准命令(如 GW、CW 巷道)
                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);
        }
    }
}
}