ExecuteRobotSubTaskCommandHandler.cs
7.8 KB
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
using MassTransit;
using Microsoft.Extensions.Logging;
using Rcs.Application.Common;
using Rcs.Application.MessageBus.Commands;
using Rcs.Application.Services;
using Rcs.Application.Services.Protocol;
using Rcs.Domain.Repositories;
using Rcs.Infrastructure.PathFinding.Services;
using TaskStatus = Rcs.Domain.Entities.TaskStatus;
namespace Rcs.Infrastructure.MessageBus.Handlers.Commands;
/// <summary>
/// 执行子任务命令处理器
/// 处理逻辑与 SubTaskCompletedDomainEventHandler 中前置清理保持一致:
/// 清理 VDA 路径缓存、清空机器人缓存 Path、释放交通控制锁
/// </summary>
public class ExecuteRobotSubTaskCommandHandler : IConsumer<ExecuteRobotSubTaskCommand>
{
private readonly ILogger<ExecuteRobotSubTaskCommandHandler> _logger;
private readonly IRobotSubTaskRepository _robotSubTaskRepository;
private readonly IRobotTaskRepository _robotTaskRepository;
private readonly IRobotRepository _robotRepository;
private readonly IRobotCacheService _robotCacheService;
private readonly AgvPathService _agvPathService;
private readonly IProtocolServiceFactory _protocolServiceFactory;
public ExecuteRobotSubTaskCommandHandler(
ILogger<ExecuteRobotSubTaskCommandHandler> logger,
IRobotSubTaskRepository robotSubTaskRepository,
IRobotTaskRepository robotTaskRepository,
IRobotRepository robotRepository,
IRobotCacheService robotCacheService,
AgvPathService agvPathService,
IProtocolServiceFactory protocolServiceFactory)
{
_logger = logger;
_robotSubTaskRepository = robotSubTaskRepository;
_robotTaskRepository = robotTaskRepository;
_robotRepository = robotRepository;
_robotCacheService = robotCacheService;
_agvPathService = agvPathService;
_protocolServiceFactory = protocolServiceFactory;
}
public async Task Consume(ConsumeContext<ExecuteRobotSubTaskCommand> context)
{
var command = context.Message;
try
{
var subTask = await _robotSubTaskRepository.GetByIdWithDetailsAsync(command.SubTaskId, context.CancellationToken);
if (subTask == null)
{
await context.RespondAsync(ApiResponse.Failed($"未找到子任务ID为 {command.SubTaskId} 的任务"));
return;
}
var robotId = subTask.Task?.RobotId ?? subTask.RobotId ?? Guid.Empty;
var skipCancelForInboundTask = ShouldSkipCacheReleaseForInboundTask(subTask.Task);
var subTaskCode = $"{subTask.Task?.TaskCode ?? command.SubTaskId.ToString()}-{subTask.Sequence}";
_logger.LogInformation(
"[子任务重新执行] 入库豁免判定: Skip={Skip}, TaskCode={TaskCode}, TaskName={TaskName}, Source={Source}, TemplateCode={TemplateCode}, TemplateName={TemplateName}",
skipCancelForInboundTask,
subTask.Task?.TaskCode,
subTask.Task?.TaskName,
subTask.Task?.Source,
subTask.Task?.TaskTemplate?.TemplateCode,
subTask.Task?.TaskTemplate?.TemplateName);
try
{
if (robotId != Guid.Empty)
{
var robot = await _robotRepository.GetByIdAsync(robotId, context.CancellationToken);
if (robot != null)
{
if (skipCancelForInboundTask)
{
_logger.LogInformation(
"[子任务重新执行] {subTask}入库任务跳过清缓存释放",
subTaskCode);
}
else
{
_logger.LogInformation("[子任务重新执行] {subTask}清除缓存释放", subTaskCode);
var protocolService = _protocolServiceFactory.GetService(robot);
await protocolService.CancelRobotTasksAsync(robot);
}
}
else
{
_logger.LogWarning("[子任务重新执行] 清理VDA路径缓存失败,机器人不存在: RobotId={RobotId}, SubTaskId={SubTaskId}",
robotId, command.SubTaskId);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "[子任务重新执行] 清理VDA路径缓存异常: RobotId={RobotId}, SubTaskId={SubTaskId}",
robotId, command.SubTaskId);
}
// 删除旧子任务,以新SubTaskId重建,确保VDA5050 OrderId唯一性
// EF Core不允许修改被跟踪实体的主键,因此采用删旧建新策略
// @author zzy
var newSubTaskId = Guid.NewGuid();
var oldSubTaskId = subTask.SubTaskId;
await _robotSubTaskRepository.DeleteAsync(subTask, context.CancellationToken);
var newSubTask = new Domain.Entities.RobotSubTask
{
SubTaskId = newSubTaskId,
TaskId = subTask.TaskId,
RobotId = subTask.RobotId,
BeginNodeId = subTask.BeginNodeId,
EndNodeId = subTask.EndNodeId,
Sequence = subTask.Sequence,
Status = TaskStatus.Pending,
ExecutionCount = 0,
CreatedAt = subTask.CreatedAt,
UpdatedAt = DateTime.Now
};
await _robotSubTaskRepository.AddAsync(newSubTask, context.CancellationToken);
await _robotSubTaskRepository.SaveChangesAsync(context.CancellationToken);
_logger.LogInformation(
"[子任务重新执行] 子任务ID已重建: OldSubTaskId={OldSubTaskId}, NewSubTaskId={NewSubTaskId}, TaskId={TaskId}",
oldSubTaskId, newSubTaskId, subTask.TaskId);
await context.RespondAsync(ApiResponse.Successful("执行成功"));
}
catch (Exception ex)
{
_logger.LogError(ex, "执行子任务失败: SubTaskId={SubTaskId}", command.SubTaskId);
await context.RespondAsync(ApiResponse.Failed(ex.Message));
}
}
private static bool ShouldSkipCacheReleaseForInboundTask(Domain.Entities.RobotTask? task)
{
if (task == null)
{
return false;
}
var taskName = task.TaskName?.Trim();
var taskCode = task.TaskCode?.Trim();
var source = task.Source?.Trim();
var templateCode = task.TaskTemplate?.TemplateCode?.Trim();
var templateName = task.TaskTemplate?.TemplateName?.Trim();
// 优先按 WMS 约定识别:TaskName = WMS_{taskType},其中 taskType=1 视为入库
if (string.Equals(source, "WMS", StringComparison.OrdinalIgnoreCase)
&& !string.IsNullOrWhiteSpace(taskName)
&& taskName.StartsWith("WMS_", StringComparison.OrdinalIgnoreCase))
{
var suffix = taskName.Substring(4);
if (int.TryParse(suffix, out var taskType))
{
return taskType == 1;
}
}
var matchTargets = new[]
{
taskName,
taskCode,
source,
templateCode,
templateName
};
if (matchTargets.All(string.IsNullOrWhiteSpace))
{
return false;
}
var inboundKeywords = new[]
{
"入库",
"上架",
"INBOUND",
"PUTAWAY",
"PUT_AWAY",
"STORAGE_IN",
"STORE_IN"
};
return matchTargets
.Where(v => !string.IsNullOrWhiteSpace(v))
.Any(v => inboundKeywords.Any(k => v!.Contains(k, StringComparison.OrdinalIgnoreCase)));
}
}