using System.Net.Sockets; using WIDESEAWCS_Common.HttpEnum; using WIDESEAWCS_Common.TaskEnum; using WIDESEAWCS_DTO.TaskInfo; using WIDESEAWCS_ITaskInfoService; using WIDESEAWCS_Model.Models; using WIDESEAWCS_Tasks.Workflow.Abstractions; namespace WIDESEAWCS_Tasks.Workflow { /// /// 前缀命令处理器 /// /// /// 迁移原 RobotMessageHandler 的 pickfinished/putfinished 分支。 /// /// 前缀命令是指以特定前缀开头的命令,后面跟随逗号分隔的参数。 /// 格式:{前缀},{参数1},{参数2},... /// /// 当前支持的前缀命令: /// - pickfinished: 取货完成,后面跟随完成的位置编号列表 /// - putfinished: 放货完成,后面跟随完成的位置编号列表 /// /// 这些命令通常包含取货或放货的位置信息,需要解析并更新状态。 /// public class RobotPrefixCommandHandler : IRobotPrefixCommandHandler { /// /// 机器人任务服务 /// /// /// 用于查询和更新任务记录。 /// private readonly IRobotTaskService _robotTaskService; /// /// 任务处理器 /// /// /// 用于处理取货/放货完成时的业务逻辑,如调用拆盘/组盘 API。 /// private readonly RobotTaskProcessor _taskProcessor; /// /// 状态管理器 /// /// /// 用于安全更新机器人的状态。 /// private readonly RobotStateManager _stateManager; /// /// Socket 网关 /// /// /// 用于向客户端发送响应消息。 /// private readonly ISocketClientGateway _socketClientGateway; /// /// 构造函数 /// /// 任务服务 /// 任务处理器 /// 状态管理器 /// Socket 网关 public RobotPrefixCommandHandler( IRobotTaskService robotTaskService, RobotTaskProcessor taskProcessor, RobotStateManager stateManager, ISocketClientGateway socketClientGateway) { _robotTaskService = robotTaskService; _taskProcessor = taskProcessor; _stateManager = stateManager; _socketClientGateway = socketClientGateway; } /// /// 检查消息是否为前缀命令 /// /// /// 前缀命令必须以 "pickfinished" 或 "putfinished" 开头(不区分大小写)。 /// /// 消息内容(小写形式) /// 如果是指缀命令返回 true public bool IsPrefixCommand(string message) { // 检查消息是否以 pickfinished 或 putfinished 开头 return message.StartsWith("pickfinished") || message.StartsWith("putfinished"); } /// /// 处理前缀命令 /// /// /// 处理流程: /// 1. 解析消息,提取位置参数 /// 2. 查询当前任务 /// 3. 根据命令类型调用相应的处理方法 /// 4. 回写原消息到客户端 /// /// 消息格式:{命令前缀},{位置1},{位置2},... /// 示例:pickfinished,1,2,3 表示取货完成,位置 1、2、3 的货物已取走 /// /// 原始消息内容 /// 机器人当前状态 /// TCP 客户端连接,用于发送响应 public async Task HandleAsync(string message, RobotSocketState state, TcpClient client) { try { // 按逗号分隔消息,提取命令和参数 // 例如:pickfinished,1,2,3 -> ["pickfinished", "1", "2", "3"] var parts = message.Split(','); // 检查消息格式是否有效:至少要有命令前缀,且状态中有当前任务 if (parts.Length < 1 || state.CurrentTask == null) { return; } // 提取命令前缀并转换为小写 var cmd = parts[0].ToLowerInvariant(); // 解析位置参数(跳过命令前缀,处理后面的数字) // 过滤掉无法解析为数字或值为 0 的位置 int[] positions = parts.Skip(1) .Select(p => int.TryParse(p, out int value) ? value : (int?)null) // 尝试解析为整数 .Where(v => v.HasValue && v.Value != 0) // 过滤掉 null 和 0 .Select(v => v!.Value) // 提取值(已知非 null) .ToArray(); // 从数据库重新查询当前任务(确保获取最新状态) var task = await _robotTaskService.Repository.QueryFirstAsync(x => x.RobotTaskId == state.CurrentTask.RobotTaskId); // 根据命令前缀分发处理 if (cmd.StartsWith("pickfinished")) { // 处理取货完成 await HandlePickFinishedAsync(state, positions, task); } else if (cmd.StartsWith("putfinished")) { // 处理放货完成 await HandlePutFinishedAsync(state, positions, task); } // 回写原消息到客户端(保持原有行为) await _socketClientGateway.SendMessageAsync(client, message); } catch (Exception ex) { // 捕获并记录异常,防止异常向上传播导致消息处理中断 Console.WriteLine($"RobotJob MessageReceived Error: {ex.Message}"); } } /// /// 处理取货完成(pickfinished)命令 /// /// /// 处理逻辑: /// 1. 如果是拆盘任务,构建库存 DTO 并调用拆盘 API /// 2. 更新当前动作为"取货完成" /// 3. 记录取货完成的位置 /// 4. 更新任务状态为"机器人取货完成" /// 5. 安全更新状态到 Redis /// /// 机器人当前状态 /// 取货完成的位置编号数组 /// 机器人任务记录 private async Task HandlePickFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task) { // 如果是拆盘任务 if (state.IsSplitPallet) { // 构建库存 DTO,包含位置信息和托盘条码 var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); // 记录取货完成的位置 state.LastPickPositions = positions; // 调用拆盘 API var result = _taskProcessor.PostSplitPalletAsync(stockDTO); // 如果 API 调用成功 if (result.Data.Status && result.IsSuccess) { // 更新当前动作为"取货完成" state.CurrentAction = "PickFinished"; } } else { // 非拆盘任务,直接更新动作 state.CurrentAction = "PickFinished"; } // 记录取货完成的位置(无论是否拆盘都记录) state.LastPickPositions = positions; // 如果任务存在 if (task != null) { // 更新任务状态为"机器人取货完成" task.RobotTaskState = TaskRobotStatusEnum.RobotPickFinish.GetHashCode(); // 安全更新状态到 Redis,确保更新成功后再更新数据库 if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.Repository.UpdateDataAsync(task); } } } /// /// 处理放货完成(putfinished)命令 /// /// /// 处理逻辑: /// 1. 如果是组盘任务,构建库存 DTO 并调用组盘/换盘 API /// 2. 如果组盘成功,增加任务计数 /// 3. 更新当前动作为"放货完成" /// 4. 更新任务状态为"机器人放货完成" /// 5. 安全更新状态到 Redis /// /// 机器人当前状态 /// 放货完成的位置编号数组 /// 机器人任务记录 private async Task HandlePutFinishedAsync(RobotSocketState state, int[] positions, Dt_RobotTask? task) { // 假设放货成功(如果后续 API 调用失败也不回退计数) bool putSuccess = true; // 如果是组盘任务(包含换盘) if (state.IsGroupPallet) { // 记录放货完成的位置 state.LastPutPositions = positions; // 构建库存 DTO var stockDTO = RobotTaskProcessor.BuildStockDTO(state, positions); // 根据任务类型决定调用哪个 API // 换盘任务调用 ChangePalletAsync,组盘任务调用 GroupPalletAsync var configKey = state.CurrentTask?.RobotTaskType == RobotTaskTypeEnum.ChangePallet.GetHashCode() ? nameof(ConfigKey.ChangePalletAsync) : nameof(ConfigKey.GroupPalletAsync); // 调用组盘/换盘 API var result = _taskProcessor.PostGroupPalletAsync(configKey, stockDTO); // 检查 API 返回状态 putSuccess = result.Data.Status && result.IsSuccess; } // 如果放货成功 if (putSuccess) { // 更新当前动作为"放货完成" state.CurrentAction = "PutFinished"; // 增加任务计数(累加本次完成的数量) state.RobotTaskTotalNum += positions.Length; // 如果任务存在,同步更新任务的计数 if (task != null) { task.RobotTaskTotalNum -= positions.Length; } } // 如果任务存在 if (task != null) { // 更新任务状态为"机器人放货完成" task.RobotTaskState = TaskRobotStatusEnum.RobotPutFinish.GetHashCode(); // 安全更新状态到 Redis if (_stateManager.TryUpdateStateSafely(state.IPAddress, state)) { await _robotTaskService.Repository.UpdateDataAsync(task); } } } } }