using MapsterMapper; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Quartz; using SqlSugar; using WIDESEA_Core; using WIDESEAWCS_Common.TaskEnum; using WIDESEAWCS_Core; using WIDESEAWCS_Core.Helper; using WIDESEAWCS_Core.LogHelper; using WIDESEAWCS_DTO.TaskInfo; using WIDESEAWCS_ITaskInfoService; using WIDESEAWCS_Model.Models; using WIDESEAWCS_QuartzJob; using WIDESEAWCS_QuartzJob.Service; using ManualInboundTaskHandler = WIDESEAWCS_Tasks.ConveyorLineNewJob.ManualInbound.ManualInboundTaskHandler; namespace WIDESEAWCS_Tasks { /// /// 输送线任务作业(Quartz Job)- 核心执行逻辑 /// /// /// Quartz 定时任务,负责处理输送线的任务调度。 /// 使用 [DisallowConcurrentExecution] 禁止并发执行,确保同一设备的任务串行处理。 /// /// 核心职责: /// 1. 获取输送线的所有子设备位置 /// 2. 并行处理每个子设备的消息 /// 3. 根据任务状态调用相应的调度方法 /// 4. 处理入库和出库两大类任务 /// /// 该 Job 通过 Parallel.For 并行处理多个子设备,提高处理效率。 /// [DisallowConcurrentExecution] public class CommonConveyorLineNewJob : IJob { /// /// 任务服务 /// private readonly ITaskService _taskService; /// /// 任务执行明细服务 /// private readonly ITaskExecuteDetailService _taskExecuteDetailService; /// /// 路由服务 /// private readonly IRouterService _routerService; /// /// 对象映射器 /// private readonly IMapper _mapper; /// /// 输送线调度处理器 /// /// /// 封装了输送线业务逻辑的处理方法。 /// private ConveyorLineDispatchHandler _conveyorLineDispatch; /// /// HTTP 客户端帮助类 /// /// /// 用于调用 WMS 系统的 HTTP 接口。 /// private readonly HttpClientHelper _httpClientHelper; /// /// 日志记录器 /// private readonly ILogger _logger; /// /// 目标地址到设备类型的映射 /// /// /// private static List AddressToDeviceType = new List { "11020", "11028" }; /// /// 构造函数 /// /// 任务服务 /// 任务执行明细服务 /// 路由服务 /// 对象映射器 /// HTTP 客户端帮助类 /// 日志记录器 public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger logger) { _taskService = taskService; _taskExecuteDetailService = taskExecuteDetailService; _routerService = routerService; _mapper = mapper; _httpClientHelper = httpClientHelper; _logger = logger; // 初始化调度处理器 _conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper, _logger); } /// /// Quartz Job 的执行入口 /// /// /// 执行流程: /// 1. 从 JobDataMap 获取输送线设备信息 /// 2. 获取该输送线的所有子设备位置列表 /// 3. 并行处理每个子设备的消息 /// 4. 检查托盘位置(特定配置的位置) /// 5. 根据任务状态分发到相应的处理方法 /// /// 并行处理提高了对多站台输送线的处理效率。 /// /// Quartz 作业执行上下文 public Task Execute(IJobExecutionContext context) { try { // 从 JobDataMap 获取输送线设备参数 CommonConveyorLine conveyorLine = (CommonConveyorLine)context.JobDetail.JobDataMap.Get("JobParams"); if (conveyorLine != null) { // 获取该输送线下的所有子设备位置编码 List childDeviceCodes = _routerService.QueryAllPositions(conveyorLine.DeviceCode); if (childDeviceCodes == null || childDeviceCodes.Count == 0) { // 没有子设备,直接返回 _logger.LogInformation("输送线 {DeviceCode} 没有子设备", conveyorLine.DeviceCode); QuartzLogger.Info($"输送线 {conveyorLine.DeviceCode} 没有子设备", conveyorLine.DeviceCode); return Task.CompletedTask; } // 创建并行选项,限制最大并发数 //var parallelOptions = new ParallelOptions //{ // // 限制并发数:子设备数量和 CPU 核心数*2 的较小值 // MaxDegreeOfParallelism = Math.Min(childDeviceCodes.Count, Environment.ProcessorCount * 2), //}; //_logger.LogDebug("Execute:开始并行处理输送线 {DeviceCode},子设备数量: {Count}", conveyorLine.DeviceCode, childDeviceCodes.Count); //QuartzLogger.Debug($"开始并行处理输送线,子设备数量: {childDeviceCodes.Count}", conveyorLine.DeviceCode); // 并行处理每个子设备 //Parallel.For(0, childDeviceCodes.Count, parallelOptions, i => foreach (var childDeviceCode in childDeviceCodes) { //string childDeviceCode = childDeviceCodes[i]; var correlationId = Guid.NewGuid().ToString("N"); try { // 读取该位置的 PLC 命令数据 ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer(childDeviceCode); // 如果命令为空,跳过 if (command == null) { _logger.LogDebug("Execute:子设备 {ChildDeviceCode} 命令为空,跳过", childDeviceCode); QuartzLogger.Debug($"子设备 {childDeviceCode} 命令为空,跳过", conveyorLine.DeviceCode); continue; } #region 检测是否需要空托盘 // ========== 检查特定位置是否有托盘 ========== // 从配置中读取需要检查托盘的位置列表 var checkPalletPositions = App.Configuration.GetSection("CheckPalletPositions") .Get>() ?? new List(); // 如果当前设备在检查列表中 if (checkPalletPositions.Any(x => x.Code == childDeviceCode)) { // 检查输送线状态(是否有托盘) if (command.CV_State == 2) { // 检查该位置是否已有任务 var existingTask = _taskService.Db.Queryable().First(x => x.TargetAddress == childDeviceCode); if (existingTask.IsNullOrEmpty()) { // 没有任务,向 WMS 请求出库托盘任务 var position = checkPalletPositions.FirstOrDefault(x => x.Code == childDeviceCode); _logger.LogInformation("Execute:检查托盘位置 {ChildDeviceCode},请求WMS出库托盘任务", childDeviceCode); QuartzLogger.Info($"检查托盘位置 {childDeviceCode},请求WMS出库托盘任务", conveyorLine.DeviceCode); var responseResult = _httpClientHelper.Post("GetOutBoundTrayTaskAsync", new CreateTaskDto() { WarehouseId = position.WarehouseId, TargetAddress = childDeviceCode }.Serialize()); // 如果请求成功,接收 WMS 返回的任务 if (responseResult.IsSuccess && responseResult.Data.Status) { var wmsTask = JsonConvert.DeserializeObject(responseResult.Data.Data.ToString()); List taskDTOs = new List { wmsTask }; if (wmsTask != null) _taskService.ReceiveWMSTask(taskDTOs); } } } } #endregion // ========== 检查 PLC_STB 标志 ========== // 只有当 PLC_STB 为 1 时才处理任务 if (command.PLC_STB != 1) { // 如果 WCS_ACK 为 1,先清除(表示处理过上一次请求) if (command.WCS_ACK == 1) conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)0, childDeviceCode); // 处理手动入库任务(起点为线体点位的任务) try { var task = _taskService.QueryManualInboundTask(childDeviceCode); if (task != null) { var handler = new ManualInboundTaskHandler(_taskService); handler.WriteTaskToPlc(conveyorLine, childDeviceCode, task); } } catch (Exception ex) { _logger.LogError(ex, "处理手动入库任务异常"); QuartzLogger.Error($"处理手动入库任务异常: {ex.Message}", "CommonConveyorLineNewJob", ex); } continue; } // ========== 处理无托盘条码的情况 ========== // 无托盘条码时,请求出库任务 if (command.Barcode.IsNullOrEmpty() || command.Barcode.Replace("\0", "") == "") { _logger.LogDebug("Execute:子设备 {ChildDeviceCode} 无托盘条码,请求出库任务", childDeviceCode); QuartzLogger.Debug($"子设备 {childDeviceCode} 无托盘条码,请求出库任务", conveyorLine.DeviceCode); _conveyorLineDispatch.RequestOutbound(conveyorLine, command, childDeviceCode); continue; } // ========== 处理已有任务号的情况 ========== if (command.TaskNo > 0 && !command.Barcode.IsNullOrEmpty()) { // 查询正在执行的任务 Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode); if (!task.IsNullOrEmpty()) { _logger.LogInformation("Execute:子设备 {ChildDeviceCode} 处理任务 {TaskNum},状态: {Status}", childDeviceCode, task.TaskNum, task.TaskStatus); QuartzLogger.Info($"处理任务 {task.TaskNum},状态: {task.TaskStatus}", conveyorLine.DeviceCode); // 处理任务状态(根据状态分发到不同方法) ProcessTaskState(conveyorLine, command, task, childDeviceCode); return Task.CompletedTask; } } } catch (Exception innerEx) { // 记录异常,但不影响其他子设备的处理 _logger.LogError(innerEx, "Execute:子设备 {ChildDeviceCode} 处理异常,CorrelationId: {CorrelationId}", childDeviceCode, correlationId); QuartzLogger.Error($"子设备处理异常: {innerEx.Message}", conveyorLine.DeviceCode, innerEx); } } } } catch (Exception ex) { // 记录整体异常 _logger.LogError(ex, "Execute:输送线 {DeviceCode} 执行异常", ex.Message); QuartzLogger.Error($"输送线执行异常: {ex.Message}", "CommonConveyorLineNewJob", ex); } return Task.CompletedTask; } /// /// 处理任务状态 /// /// /// 根据任务的当前状态,调用相应的调度方法: /// - InExecuting: 入库执行中 -> 调用 RequestInNextAddress /// - OutExecuting: 出库执行中 -> 根据是否到达目标地址调用对应方法 /// - InFinish: 入库完成 -> 调用 ConveyorLineInFinish /// - OutFinish: 出库完成 -> 调用 ConveyorLineOutFinish /// /// 输送线设备对象 /// PLC 命令数据 /// 任务对象 /// 子设备编码 private void ProcessTaskState(CommonConveyorLine conveyorLine, ConveyorLineTaskCommandNew command, Dt_Task task, string childDeviceCode) { // 定义任务状态常量 const int InExecuting = (int)TaskInStatusEnum.Line_InExecuting; // 入库执行中 const int OutExecuting = (int)TaskOutStatusEnum.Line_OutExecuting; // 出库执行中 const int InFinish = (int)TaskInStatusEnum.InFinish; // 入库完成 const int OutFinish = (int)TaskOutStatusEnum.OutFinish; // 出库完成 // 获取当前任务状态 int state = task.TaskStatus; // 判断当前子设备是否为目标地址 bool isTargetAddress = task.TargetAddress == childDeviceCode; // 根据状态分发处理 switch (state) { case InExecuting: // 入库执行中,调用下一地址处理 if (AddressToDeviceType.Contains(childDeviceCode)) // 到达目标地址,调用入库完成 _conveyorLineDispatch.ConveyorLineInFinish(conveyorLine, command, childDeviceCode); else // 未到达目标地址,调用入库下一地址处理 _conveyorLineDispatch.RequestInNextAddress(conveyorLine, command, childDeviceCode); break; case OutExecuting: // 出库执行中 if (isTargetAddress) // 到达目标地址,调用出库完成 _conveyorLineDispatch.ConveyorLineOutFinish(conveyorLine, command, childDeviceCode); else // 未到达目标地址,调用出库下一地址处理 _conveyorLineDispatch.RequestOutNextAddress(conveyorLine, command, childDeviceCode); break; case InFinish: // 入库完成 _conveyorLineDispatch.ConveyorLineInFinish(conveyorLine, command, childDeviceCode); break; case OutFinish: // 出库完成 _conveyorLineDispatch.ConveyorLineOutFinish(conveyorLine, command, childDeviceCode); break; } } } }