wanshenmean
10 天以前 035f2a81a59532ac9f892dab9ade44304847b4fb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.Sockets;
using WIDESEAWCS_Common;
using WIDESEAWCS_Core.Caches;
using WIDESEAWCS_Core.LogHelper;
using WIDESEAWCS_Tasks.Workflow.Abstractions;
 
namespace WIDESEAWCS_Tasks
{
    /// <summary>
    /// 机器人消息处理器 - 消息路由入口
    /// </summary>
    /// <remarks>
    /// 核心职责:
    /// 1. 缓存状态读取:从 Redis 中获取机器人最新的状态
    /// 2. 命令分发:根据消息类型分发给不同的处理器
    ///    - 简单命令(如 homing、running):由 <see cref="IRobotSimpleCommandHandler"/> 处理
    ///    - 前缀命令(如 pickfinished、putfinished):由 <see cref="IRobotPrefixCommandHandler"/> 处理
    /// 3. 回包触发:将原始消息回写到客户端
    ///
    /// 这是消息处理管道的入口点,由 TcpSocketServer 的 MessageReceived 事件触发。
    /// </remarks>
    public class RobotMessageHandler : IRobotMessageRouter
    {
        /// <summary>
        /// Socket 客户端网关接口
        /// </summary>
        /// <remarks>
        /// 用于向客户端发送响应消息。
        /// </remarks>
        private readonly ISocketClientGateway _socketClientGateway;
 
        /// <summary>
        /// 机械手状态管理器
        /// </summary>
        /// <remarks>
        /// 用于读取和更新机器人的状态。
        /// </remarks>
        private readonly RobotStateManager _stateManager;
 
        /// <summary>
        /// 缓存服务
        /// </summary>
        /// <remarks>
        /// 直接使用缓存服务检查状态是否存在。
        /// </remarks>
        private readonly ICacheService _cache;
 
        /// <summary>
        /// 简单命令处理器
        /// </summary>
        /// <remarks>
        /// 处理简单的状态更新命令,如运行状态、模式切换等。
        /// </remarks>
        private readonly IRobotSimpleCommandHandler _simpleCommandHandler;
 
        /// <summary>
        /// 前缀命令处理器
        /// </summary>
        /// <remarks>
        /// 处理带参数的前缀命令,如 pickfinished(取货完成)、putfinished(放货完成)。
        /// </remarks>
        private readonly IRobotPrefixCommandHandler _prefixCommandHandler;
 
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger<RobotJob> _logger;
 
        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="socketClientGateway">Socket 网关</param>
        /// <param name="stateManager">状态管理器</param>
        /// <param name="cache">缓存服务</param>
        /// <param name="simpleCommandHandler">简单命令处理器</param>
        /// <param name="prefixCommandHandler">前缀命令处理器</param>
        /// <param name="logger">日志记录器</param>
        public RobotMessageHandler(
            ISocketClientGateway socketClientGateway,
            RobotStateManager stateManager,
            ICacheService cache,
            IRobotSimpleCommandHandler simpleCommandHandler,
            IRobotPrefixCommandHandler prefixCommandHandler,
            ILogger<RobotJob> logger)
        {
            _socketClientGateway = socketClientGateway;
            _stateManager = stateManager;
            _cache = cache;
            _simpleCommandHandler = simpleCommandHandler;
            _prefixCommandHandler = prefixCommandHandler;
            _logger = logger;
        }
 
        /// <summary>
        /// 处理接收到的消息
        /// </summary>
        /// <remarks>
        /// 处理流程:
        /// 1. 记录日志(记录原始消息内容)
        /// 2. 验证缓存中是否存在该设备的状态
        /// 3. 尝试用简单命令处理器处理(状态更新类命令)
        ///    - 如果处理成功,回写原消息并更新状态
        /// 4. 如果不是简单命令,检查是否是前缀命令(pickfinished/putfinished)
        ///    - 如果是,调用前缀命令处理器处理
        /// 5. 保持原有行为:简单命令和前缀命令都回写原消息
        ///
        /// 注意:此方法可能在 TCP 消息接收的上下文中被频繁调用,需注意性能。
        /// </remarks>
        /// <param name="message">原始消息字符串</param>
        /// <param name="isJson">消息是否为 JSON 格式(当前未使用)</param>
        /// <param name="client">TCP 客户端连接</param>
        /// <param name="state">机器人当前状态</param>
        /// <returns>响应消息,如果无需回复则返回 null</returns>
        public async Task<string?> HandleMessageReceivedAsync(string message, bool isJson, TcpClient client, RobotSocketState state)
        {
            // 记录接收到的消息日志
            _logger.LogInformation($"接收到客户端【{state.RobotCrane.DeviceName}】发送消息【{message}】");
            QuartzLogger.Info($"接收到客户端消息【{message}】", state.RobotCrane.DeviceName);
 
            // 构建缓存键,检查 Redis 中是否存在该设备的状态
            var cacheKey = $"{RedisPrefix.Code}:{RedisName.SocketDevices}:{client.Client.RemoteEndPoint}";
 
            // 如果缓存中不存在或状态为 null,忽略此消息
            if (!_cache.TryGetValue(cacheKey, out RobotSocketState? cachedState) || cachedState == null)
            {
                return null;
            }
 
            // 使用缓存中获取的状态
            var activeState = cachedState;
 
            // 将消息转换为小写(用于简单命令匹配)
            string messageLower = message.ToLowerInvariant();
 
            // 尝试用简单命令处理器处理
            // 简单命令包括:homing、homed、running、pausing、runmode、controlmode 等
            if (await _simpleCommandHandler.HandleAsync(messageLower, activeState))
            {
                // 处理成功后,将原消息回写到客户端(保持原有行为)
                await _socketClientGateway.SendMessageAsync(client, message);
                _logger.LogInformation($"发送消息【{message}】");
                QuartzLogger.Info($"发送消息:【{message}】", state.RobotCrane.DeviceName);
 
                // 安全更新状态到 Redis
                _stateManager.TryUpdateStateSafely(activeState.IPAddress, activeState);
                return null;
            }
 
            // 如果不是简单命令,检查是否是前缀命令
            // 前缀命令包括:pickfinished、putfinished(后面跟逗号分隔的位置参数)
            if (_prefixCommandHandler.IsPrefixCommand(messageLower))
            {
                // 调用前缀命令处理器
                // 前缀命令处理器会解析位置参数并更新状态
                await _prefixCommandHandler.HandleAsync(message, activeState, client);
            }
 
            // 默认返回 null,不产生响应消息
            return null;
        }
    }
}