wanshenmean
4 小时以前 ba9c1994b95624b88ef606ec00394990d8f2009f
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
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
{
    /// <summary>
    /// 前缀命令处理器
    /// </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,
            RobotStateManager stateManager,
            ISocketClientGateway socketClientGateway)
        {
            _robotTaskService = robotTaskService;
            _taskProcessor = taskProcessor;
            _stateManager = stateManager;
            _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)  // 过滤掉 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);
                }
            }
        }
 
        /// <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);
                }
            }
        }
    }
}