ExecuteRobotSubTaskCommandHandler.cs 7.8 KB
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)));
    }
}