using MapsterMapper;
|
using Microsoft.Extensions.Configuration;
|
using Quartz;
|
using SqlSugar;
|
using System.Text.Json;
|
using WIDESEA_Core;
|
using WIDESEAWCS_Common.TaskEnum;
|
using WIDESEAWCS_Core;
|
using WIDESEAWCS_Core.Helper;
|
using WIDESEAWCS_DTO.TaskInfo;
|
using WIDESEAWCS_ITaskInfoService;
|
using WIDESEAWCS_Model.Models;
|
using WIDESEAWCS_QuartzJob;
|
using WIDESEAWCS_QuartzJob.Service;
|
|
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;
|
|
/// <summary>
|
/// 构造函数
|
/// </summary>
|
/// <param name="taskService">任务服务</param>
|
/// <param name="taskExecuteDetailService">任务执行明细服务</param>
|
/// <param name="routerService">路由服务</param>
|
/// <param name="mapper">对象映射器</param>
|
/// <param name="httpClientHelper">HTTP 客户端帮助类</param>
|
public CommonConveyorLineNewJob(ITaskService taskService, ITaskExecuteDetailService taskExecuteDetailService, IRouterService routerService, IMapper mapper, HttpClientHelper httpClientHelper)
|
{
|
_taskService = taskService;
|
_taskExecuteDetailService = taskExecuteDetailService;
|
_routerService = routerService;
|
_mapper = mapper;
|
_httpClientHelper = httpClientHelper;
|
|
// 初始化调度处理器
|
_conveyorLineDispatch = new ConveyorLineDispatchHandler(_taskService, _taskExecuteDetailService, _routerService, _mapper);
|
}
|
|
/// <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} 没有子设备");
|
return Task.CompletedTask;
|
}
|
|
// 创建并行选项,限制最大并发数
|
var parallelOptions = new ParallelOptions
|
{
|
// 限制并发数:子设备数量和 CPU 核心数*2 的较小值
|
MaxDegreeOfParallelism = Math.Min(childDeviceCodes.Count, Environment.ProcessorCount * 2),
|
};
|
|
// 并行处理每个子设备
|
Parallel.For(0, childDeviceCodes.Count, parallelOptions, i =>
|
{
|
string childDeviceCode = childDeviceCodes[i];
|
var correlationId = Guid.NewGuid().ToString("N");
|
try
|
{
|
// 读取该位置的 PLC 命令数据
|
ConveyorLineTaskCommandNew command = conveyorLine.ReadCustomer<ConveyorLineTaskCommandNew>(childDeviceCode);
|
|
// 如果命令为空,跳过
|
if (command == null)
|
{
|
return;
|
}
|
|
// 如果 WCS_ACK 为 1,先清除(表示处理过上一次请求)
|
if (command.WCS_ACK == 1)
|
conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 0, childDeviceCode);
|
|
// ========== 检查特定位置是否有托盘 ==========
|
// 从配置中读取需要检查托盘的位置列表
|
var checkPalletPositions = App.Configuration.GetSection("CheckPalletPositions")
|
.Get<List<CheckPalletPosition>>() ?? new List<CheckPalletPosition>();
|
|
// 如果当前设备在检查列表中
|
if (checkPalletPositions.Any(x => x.Code == childDeviceCode))
|
{
|
// 检查输送线状态(是否有托盘)
|
if (command.CV_State.ObjToBool())
|
{
|
// 检查该位置是否已有任务
|
var existingTask = _taskService.Repository.QueryFirst(x => x.TargetAddress == childDeviceCode);
|
if (existingTask.IsNullOrEmpty())
|
{
|
// 没有任务,向 WMS 请求出库托盘任务
|
var position = checkPalletPositions.FirstOrDefault(x => x.Code == childDeviceCode);
|
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());
|
if (wmsTask != null)
|
_taskService.ReceiveWMSTask(wmsTask);
|
}
|
}
|
}
|
}
|
|
// ========== 检查 PLC_STB 标志 ==========
|
// 只有当 PLC_STB 为 1 时才处理任务
|
if (command.PLC_STB != 1) return;
|
|
// ========== 处理无托盘条码的情况 ==========
|
// 无托盘条码时,请求出库任务
|
if (command.Barcode.IsNullOrEmpty() || command.Barcode.Replace("\0", "") == "")
|
{
|
_conveyorLineDispatch.RequestOutbound(conveyorLine, command, childDeviceCode);
|
return;
|
}
|
|
// ========== 处理已有任务号的情况 ==========
|
if (command.TaskNo > 0)
|
{
|
// 查询正在执行的任务
|
Dt_Task task = _taskService.QueryExecutingConveyorLineTask(command.TaskNo, childDeviceCode);
|
if (!task.IsNullOrEmpty())
|
{
|
// 处理任务状态(根据状态分发到不同方法)
|
ProcessTaskState(conveyorLine, command, task, childDeviceCode);
|
return;
|
}
|
}
|
}
|
catch (Exception innerEx)
|
{
|
// 记录异常,但不影响其他子设备的处理
|
Console.Error.WriteLine($"{DateTime.UtcNow:O} [{childDeviceCode}] CorrelationId={correlationId} {innerEx}");
|
}
|
});
|
}
|
}
|
catch (Exception ex)
|
{
|
// 记录整体异常
|
Console.Error.WriteLine(ex);
|
}
|
return Task.CompletedTask;
|
}
|
|
/// <summary>
|
/// 处理任务状态
|
/// </summary>
|
/// <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; // 出库完成
|
|
// 获取当前任务状态
|
int state = task.TaskStatus;
|
|
// 判断当前子设备是否为目标地址
|
bool isTargetAddress = task.TargetAddress == childDeviceCode;
|
|
// 根据状态分发处理
|
switch (state)
|
{
|
case InExecuting:
|
// 入库执行中,调用下一地址处理
|
_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;
|
}
|
}
|
}
|
}
|