| | |
| | | #region << 版 本 注 释 >> |
| | | |
| | | /*---------------------------------------------------------------- |
| | | * 命名空间:WIDESEAWCS_Tasks.ConveyorLineJob |
| | | * 创建者:胡童庆 |
| | | * 创建时间:2024/8/2 16:13:36 |
| | | * 版本:V1.0.0 |
| | | * 描述: |
| | | * |
| | | * ---------------------------------------------------------------- |
| | | * 修改人: |
| | | * 修改时间: |
| | | * 版本:V1.0.1 |
| | | * 修改说明: |
| | | * |
| | | *----------------------------------------------------------------*/ |
| | | |
| | | #endregion << 版 本 注 释 >> |
| | | |
| | | using MapsterMapper; |
| | | using Microsoft.Extensions.Configuration; |
| | | using Microsoft.Extensions.Logging; |
| | | using Newtonsoft.Json; |
| | | using Quartz; |
| | | using SqlSugar; |
| | | using System.Text.Json; |
| | | 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; |
| | |
| | | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | | /// <summary> |
| | | /// 输送线任务作业(Quartz Job)- 核心执行逻辑 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// Quartz 定时任务,负责处理输送线的任务调度。 |
| | | /// 使用 [DisallowConcurrentExecution] 禁止并发执行,确保同一设备的任务串行处理。 |
| | | /// |
| | | /// 核心职责: |
| | | /// 1. 获取输送线的所有子设备位置 |
| | | /// 2. 并行处理每个子设备的消息 |
| | | /// 3. 根据任务状态调用相应的调度方法 |
| | | /// 4. 处理入库和出库两大类任务 |
| | | /// |
| | | /// 该 Job 通过 Parallel.For 并行处理多个子设备,提高处理效率。 |
| | | /// </remarks> |
| | | [DisallowConcurrentExecution] |
| | | public class CommonConveyorLineNewJob : IJob |
| | | { |
| | | /// <summary> |
| | | /// 任务服务 |
| | | /// </summary> |
| | | private readonly ITaskService _taskService; |
| | | |
| | | /// <summary> |
| | | /// 任务执行明细服务 |
| | | /// </summary> |
| | | private readonly ITaskExecuteDetailService _taskExecuteDetailService; |
| | | |
| | | /// <summary> |
| | | /// 路由服务 |
| | | /// </summary> |
| | | private readonly IRouterService _routerService; |
| | | |
| | | /// <summary> |
| | | /// 对象映射器 |
| | | /// </summary> |
| | | private readonly IMapper _mapper; |
| | | |
| | | /// <summary> |
| | | /// 输送线调度处理器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 封装了输送线业务逻辑的处理方法。 |
| | | /// </remarks> |
| | | private ConveyorLineDispatchHandler _conveyorLineDispatch; |
| | | |
| | | /// <summary> |
| | | /// HTTP 客户端帮助类 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于调用 WMS 系统的 HTTP 接口。 |
| | | /// </remarks> |
| | | private readonly HttpClientHelper _httpClientHelper; |
| | | |
| | | public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper) |
| | | /// <summary> |
| | | /// 日志记录器 |
| | | /// </summary> |
| | | private readonly ILogger<CommonConveyorLineNewJob> _logger; |
| | | |
| | | /// <summary> |
| | | /// 目标地址到设备类型的映射 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// </remarks> |
| | | private static List<string> AddressToDeviceType = new List<string> { "11020", "11028" }; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="taskService">任务服务</param> |
| | | /// <param name="taskExecuteDetailService">任务执行明细服务</param> |
| | | /// <param name="routerService">路由服务</param> |
| | | /// <param name="mapper">对象映射器</param> |
| | | /// <param name="httpClientHelper">HTTP 客户端帮助类</param> |
| | | /// <param name="logger">日志记录器</param> |
| | | public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper, ILogger<CommonConveyorLineNewJob> logger) |
| | | { |
| | | _taskService = taskService; |
| | | _taskExecuteDetailService = taskExecuteDetailService; |
| | | _routerService = routerService; |
| | | _mapper = mapper; |
| | | _conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper); |
| | | _httpClientHelper = httpClientHelper; |
| | | _logger = logger; |
| | | |
| | | // 初始化调度处理器 |
| | | _conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper, _logger); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// Quartz Job 的执行入口 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 执行流程: |
| | | /// 1. 从 JobDataMap 获取输送线设备信息 |
| | | /// 2. 获取该输送线的所有子设备位置列表 |
| | | /// 3. 并行处理每个子设备的消息 |
| | | /// 4. 检查托盘位置(特定配置的位置) |
| | | /// 5. 根据任务状态分发到相应的处理方法 |
| | | /// |
| | | /// 并行处理提高了对多站台输送线的处理效率。 |
| | | /// </remarks> |
| | | /// <param name="context">Quartz 作业执行上下文</param> |
| | | public Task Execute(IJobExecutionContext context) |
| | | { |
| | | try |
| | | { |
| | | // 从 JobDataMap 获取输送线设备参数 |
| | | CommonConveyorLine conveyorLine = (CommonConveyorLine)context.JobDetail.JobDataMap.Get("JobParams"); |
| | | if (conveyorLine != null) |
| | | { |
| | | // 获取该输送线下的所有子设备位置编码 |
| | | List<string> childDeviceCodes = _routerService.QueryAllPositions(conveyorLine.DeviceCode); |
| | | if (childDeviceCodes == null || childDeviceCodes.Count == 0) |
| | | { |
| | | Console.WriteLine($"输送线 {conveyorLine.DeviceCode} 没有子设备"); |
| | | // 没有子设备,直接返回 |
| | | _logger.LogInformation("输送线 {DeviceCode} 没有子设备", conveyorLine.DeviceCode); |
| | | QuartzLogger.Info($"输送线 {conveyorLine.DeviceCode} 没有子设备", conveyorLine.DeviceCode); |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | // 创建并行选项 |
| | | var parallelOptions = new ParallelOptions |
| | | // 创建并行选项,限制最大并发数 |
| | | //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) |
| | | { |
| | | MaxDegreeOfParallelism = Math.Min(childDeviceCodes.Count, Environment.ProcessorCount * 2), // 合理限制并发数 |
| | | }; |
| | | Parallel.For(0, childDeviceCodes.Count, parallelOptions, i => |
| | | { |
| | | string childDeviceCode = childDeviceCodes[i]; |
| | | //string childDeviceCode = childDeviceCodes[i]; |
| | | var correlationId = Guid.NewGuid().ToString("N"); |
| | | try |
| | | { |
| | | // 读取该位置的 PLC 命令数据 |
| | | ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(childDeviceCode); |
| | | |
| | | |
| | | // 如果命令为空,跳过 |
| | | if (command == null) |
| | | { |
| | | return; |
| | | _logger.LogDebug("Execute:子设备 {ChildDeviceCode} 命令为空,跳过", childDeviceCode); |
| | | QuartzLogger.Debug($"子设备 {childDeviceCode} 命令为空,跳过", conveyorLine.DeviceCode); |
| | | continue; |
| | | } |
| | | |
| | | #region 检查特定位置是否有托盘 |
| | | |
| | | // ========== 检查特定位置是否有托盘 ========== |
| | | // 从配置中读取需要检查托盘的位置列表 |
| | | var checkPalletPositions = App.Configuration.GetSection("CheckPalletPositions") |
| | | .Get<List<CheckPalletPosition>>() ?? new List<CheckPalletPosition>(); |
| | | |
| | | // 如果当前设备在检查列表中 |
| | | if (checkPalletPositions.Any(x => x.Code == childDeviceCode)) |
| | | { |
| | | if (command.CV_State.ObjToBool()) |
| | | // 检查输送线状态(是否有托盘) |
| | | if (command.CV_State == 2) |
| | | { |
| | | var existingTask = _taskService.Repository.QueryFirst(x => x.TargetAddress == childDeviceCode); |
| | | // 检查该位置是否已有任务 |
| | | var existingTask = _taskService.Db.Queryable<Dt_Task>().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<WebResponseContent>("GetOutBoundTrayTaskAsync", new CreateTaskDto() |
| | | { |
| | | WarehouseId = position.WarehouseId, |
| | | TargetAddress = childDeviceCode |
| | | }.Serialize()); |
| | | |
| | | // 如果请求成功,接收 WMS 返回的任务 |
| | | if (responseResult.IsSuccess && responseResult.Data.Status) |
| | | { |
| | | var wmsTask = JsonSerializer.Deserialize<List<WMSTaskDTO>>(responseResult.Data.Data.Serialize()); |
| | | var wmsTask = JsonConvert.DeserializeObject<WMSTaskDTO>(responseResult.Data.Data.ToString()); |
| | | List<WMSTaskDTO> taskDTOs = new List<WMSTaskDTO> { wmsTask }; |
| | | if (wmsTask != null) |
| | | _taskService.ReceiveWMSTask(wmsTask); |
| | | _taskService.ReceiveWMSTask(taskDTOs); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | #endregion |
| | | |
| | | if (command.PLC_STB != 1) return;//PLC_STB=1时才处理任务 |
| | | |
| | | if (command.Barcode.IsNullOrEmpty()) |
| | | // ========== 检查 PLC_STB 标志 ========== |
| | | // 只有当 PLC_STB 为 1 时才处理任务 |
| | | if (command.PLC_STB != 1) |
| | | { |
| | | //无托盘号时 |
| | | _conveyorLineDispatch.RequestOutbound(conveyorLine, command, childDeviceCode); |
| | | return; |
| | | // 如果 WCS_ACK 为 1,先清除(表示处理过上一次请求) |
| | | if (command.WCS_ACK == 1) |
| | | conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)0, childDeviceCode); |
| | | continue; |
| | | } |
| | | |
| | | if (command.TaskNo > 0) |
| | | // ========== 处理无托盘条码的情况 ========== |
| | | // 无托盘条码时,请求出库任务 |
| | | 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); |
| | | //_conveyorLineDispatch.RequestInbound(conveyorLine, command, childDeviceCode); |
| | | return; |
| | | return Task.CompletedTask; |
| | | } |
| | | |
| | | } |
| | | } |
| | | catch (Exception innerEx) |
| | | { |
| | | Console.Error.WriteLine($"{DateTime.UtcNow:O} [{childDeviceCode}] CorrelationId={correlationId} {innerEx}"); |
| | | // 记录异常,但不影响其他子设备的处理 |
| | | _logger.LogError(innerEx, "Execute:子设备 {ChildDeviceCode} 处理异常,CorrelationId: {CorrelationId}", childDeviceCode, correlationId); |
| | | QuartzLogger.Error($"子设备处理异常: {innerEx.Message}", conveyorLine.DeviceCode, innerEx); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Console.Error.WriteLine(ex); |
| | | // 记录整体异常 |
| | | _logger.LogError(ex, "Execute:输送线 {DeviceCode} 执行异常", ex.Message); |
| | | QuartzLogger.Error($"输送线执行异常: {ex.Message}", "CommonConveyorLineNewJob", ex); |
| | | } |
| | | return Task.CompletedTask; |
| | | } |
| | |
| | | /// <summary> |
| | | /// 处理任务状态 |
| | | /// </summary> |
| | | /// <param name="conveyorLine">输送线实例对象</param> |
| | | /// <param name="command">读取的请求信息</param> |
| | | /// <param name="task">子设备编号</param> |
| | | /// <param name="childDeviceCode"></param> |
| | | /// <remarks> |
| | | /// 根据任务的当前状态,调用相应的调度方法: |
| | | /// - InExecuting: 入库执行中 -> 调用 RequestInNextAddress |
| | | /// - OutExecuting: 出库执行中 -> 根据是否到达目标地址调用对应方法 |
| | | /// - InFinish: 入库完成 -> 调用 ConveyorLineInFinish |
| | | /// - OutFinish: 出库完成 -> 调用 ConveyorLineOutFinish |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="command">PLC 命令数据</param> |
| | | /// <param name="task">任务对象</param> |
| | | /// <param name="childDeviceCode">子设备编码</param> |
| | | 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; |
| | | // 定义任务状态常量 |
| | | 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 (isTargetAddress) |
| | | // _conveyorLineDispatch.ConveyorLineInFinish(conveyorLine, command, childDeviceCode); |
| | | //else |
| | | _conveyorLineDispatch.RequestInNextAddress(conveyorLine, command, childDeviceCode); |
| | | // 入库执行中,调用下一地址处理 |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |