wanshenmean
2026-03-26 8e42d0c1b7ae36cff2e7c69999117911a4b6f300
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/Workflow/RobotPrefixCommandHandler.cs
@@ -1,4 +1,4 @@
using System.Net.Sockets;
using System.Net.Sockets;
using WIDESEAWCS_Common.HttpEnum;
using WIDESEAWCS_Common.TaskEnum;
using WIDESEAWCS_DTO.TaskInfo;
@@ -9,15 +9,61 @@
namespace WIDESEAWCS_Tasks.Workflow
{
    /// <summary>
    /// 前缀命令处理:迁移原 RobotMessageHandler 的 pickfinished/putfinished 分支。
    /// 前缀命令处理器
    /// </summary>
    /// <remarks>
    /// 迁移原 RobotMessageHandler 的 pickfinished/putfinished 分支。
    ///
    /// 前缀命令是指以特定前缀开头的命令,后面跟随逗号分隔的参数。
    /// 格式:{前缀},{参数1},{参数2},...
    ///
    /// 当前支持的前缀命令:
    /// - pickfinished: 取货完成,后面跟随完成的位置编号列表
    /// - putfinished: 放货完成,后面跟随完成的位置编号列表
    ///
    /// 这些命令通常包含取货或放货的位置信息,需要解析并更新状态。
    /// </remarks>
    public class RobotPrefixCommandHandler : IRobotPrefixCommandHandler
    {
        /// <summary>
        /// 机器人任务服务
        /// </summary>
        /// <remarks>
        /// 用于查询和更新任务记录。
        /// </remarks>
        private readonly IRobotTaskService _robotTaskService;
        /// <summary>
        /// 任务处理器
        /// </summary>
        /// <remarks>
        /// 用于处理取货/放货完成时的业务逻辑,如调用拆盘/组盘 API。
        /// </remarks>
        private readonly RobotTaskProcessor _taskProcessor;
        /// <summary>
        /// 状态管理器
        /// </summary>
        /// <remarks>
        /// 用于安全更新机器人的状态。
        /// </remarks>
        private readonly RobotStateManager _stateManager;
        /// <summary>
        /// Socket 网关
        /// </summary>
        /// <remarks>
        /// 用于向客户端发送响应消息。
        /// </remarks>
        private readonly ISocketClientGateway _socketClientGateway;
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="robotTaskService">任务服务</param>
        /// <param name="taskProcessor">任务处理器</param>
        /// <param name="stateManager">状态管理器</param>
        /// <param name="socketClientGateway">Socket 网关</param>
        public RobotPrefixCommandHandler(
            IRobotTaskService robotTaskService,
            RobotTaskProcessor taskProcessor,
@@ -30,69 +76,137 @@
            _socketClientGateway = socketClientGateway;
        }
        /// <summary>
        /// 检查消息是否为前缀命令
        /// </summary>
        /// <remarks>
        /// 前缀命令必须以 "pickfinished" 或 "putfinished" 开头(不区分大小写)。
        /// </remarks>
        /// <param name="message">消息内容(小写形式)</param>
        /// <returns>如果是指缀命令返回 true</returns>
        public bool IsPrefixCommand(string message)
        {
            // 检查消息是否以 pickfinished 或 putfinished 开头
            return message.StartsWith("pickfinished") || message.StartsWith("putfinished");
        }
        /// <summary>
        /// 处理前缀命令
        /// </summary>
        /// <remarks>
        /// 处理流程:
        /// 1. 解析消息,提取位置参数
        /// 2. 查询当前任务
        /// 3. 根据命令类型调用相应的处理方法
        /// 4. 回写原消息到客户端
        ///
        /// 消息格式:{命令前缀},{位置1},{位置2},...
        /// 示例:pickfinished,1,2,3 表示取货完成,位置 1、2、3 的货物已取走
        /// </remarks>
        /// <param name="message">原始消息内容</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="client">TCP 客户端连接,用于发送响应</param>
        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)
                    .Select(v => v!.Value)
                    .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}");
            }
        }
        /// <summary>
        /// 处理取货完成(pickfinished)命令
        /// </summary>
        /// <remarks>
        /// 处理逻辑:
        /// 1. 如果是拆盘任务,构建库存 DTO 并调用拆盘 API
        /// 2. 更新当前动作为"取货完成"
        /// 3. 记录取货完成的位置
        /// 4. 更新任务状态为"机器人取货完成"
        /// 5. 安全更新状态到 Redis
        /// </remarks>
        /// <param name="state">机器人当前状态</param>
        /// <param name="positions">取货完成的位置编号数组</param>
        /// <param name="task">机器人任务记录</param>
        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);
@@ -100,34 +214,70 @@
            }
        }
        /// <summary>
        /// 处理放货完成(putfinished)命令
        /// </summary>
        /// <remarks>
        /// 处理逻辑:
        /// 1. 如果是组盘任务,构建库存 DTO 并调用组盘/换盘 API
        /// 2. 如果组盘成功,增加任务计数
        /// 3. 更新当前动作为"放货完成"
        /// 4. 更新任务状态为"机器人放货完成"
        /// 5. 安全更新状态到 Redis
        /// </remarks>
        /// <param name="state">机器人当前状态</param>
        /// <param name="positions">放货完成的位置编号数组</param>
        /// <param name="task">机器人任务记录</param>
        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);