SubTaskCompletedDomainEventHandler.cs 6.08 KB
using Microsoft.Extensions.Logging;
using Rcs.Application.Services;
using Rcs.Application.Services.Protocol;
using Rcs.Domain.Entities;
using Rcs.Domain.Entities.DomainEvents.RobotSubTask;
using Rcs.Domain.Repositories;
using TaskStatus = Rcs.Domain.Entities.TaskStatus;

namespace Rcs.Infrastructure.MessageBus.Handlers.Events.RobotSubTask;

/// <summary>
/// 子任务完成领域事件处理器
/// 负责:清理VDA路径缓存、清空机器人缓存Path、判断是否所有子任务完成、级联触发父任务完成、释放交通管制锁
/// @author zzy
/// </summary>
public class SubTaskCompletedDomainEventHandler
{
    private readonly ILogger<SubTaskCompletedDomainEventHandler> _logger;
    private readonly IRobotTaskRepository _taskRepository;
    private readonly IRobotRepository _robotRepository;
    private readonly IRobotCacheService _robotCacheService;
    private readonly IUnifiedTrafficControlService _trafficControlService;
    private readonly IProtocolServiceFactory _protocolServiceFactory;

    public SubTaskCompletedDomainEventHandler(
        ILogger<SubTaskCompletedDomainEventHandler> logger,
        IRobotTaskRepository taskRepository,
        IRobotRepository robotRepository,
        IRobotCacheService robotCacheService,
        IUnifiedTrafficControlService trafficControlService,
        IProtocolServiceFactory protocolServiceFactory)
    {
        _logger = logger;
        _taskRepository = taskRepository;
        _robotRepository = robotRepository;
        _robotCacheService = robotCacheService;
        _trafficControlService = trafficControlService;
        _protocolServiceFactory = protocolServiceFactory;
    }

    /// <summary>
    /// 处理子任务完成事件
    /// 1. 清理该子任务的VDA路径缓存
    /// 2. 清空机器人Redis缓存中的Path字段
    /// 3. 判断所有子任务是否完成
    /// 4. 全部完成时:释放交通管制锁 → 触发父任务完成
    /// @author zzy
    /// </summary>
    public async System.Threading.Tasks.Task Handle(SubTaskCompletedDomainEvent domainEvent)
    {
        _logger.LogInformation(
            "[子任务完成] SubTaskId={SubTaskId}, TaskId={TaskId}, RobotId={RobotId}",
            domainEvent.SubTaskId, domainEvent.TaskId, domainEvent.RobotId);

        // 1. 清理该子任务的VDA路径缓存(通过协议服务清理)
        try
        {
            if (domainEvent.RobotId != Guid.Empty)
            {
                var robot = await _robotRepository.GetByIdAsync(domainEvent.RobotId);
                if (robot != null)
                {
                    var protocolService = _protocolServiceFactory.GetService(robot);
                    await protocolService.ClearAllVdaPathCacheAsync(domainEvent.RobotId);
                }
                else
                {
                    _logger.LogWarning("[子任务完成] 清理VDA路径缓存失败,机器人不存在: RobotId={RobotId}", domainEvent.RobotId);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[子任务完成] 清理VDA路径缓存异常: RobotId={RobotId}, SubTaskId={SubTaskId}",
                domainEvent.RobotId, domainEvent.SubTaskId);
        }

        // 2. 清空机器人Redis缓存中的Path字段
        await ClearRobotCachePathAsync(domainEvent.RobotId);
        // 3. 释放该机器人的所有交通管制锁
        if (domainEvent.RobotId != Guid.Empty)
        {
            var releasedCount = await _trafficControlService.ReleaseAllRobotLocksAsync(domainEvent.RobotId);
        }
        // 4. 获取父任务及所有子任务
        var task = await _taskRepository.GetByIdWithDetailsAsync(domainEvent.TaskId);
        if (task == null)
        {
            _logger.LogWarning("[子任务完成] 父任务不存在: TaskId={TaskId}", domainEvent.TaskId);
            return;
        }

        // 幂等保护:父任务已完成则跳过
        if (task.Status == TaskStatus.Completed)
        {
            _logger.LogDebug("[子任务完成] 父任务已完成,跳过: TaskId={TaskId}", domainEvent.TaskId);
            return;
        }

        // 5. 判断是否所有子任务都已完成
        var allSubTasksCompleted = task.SubTasks.All(st => st.Status == TaskStatus.Completed);

        if (allSubTasksCompleted)
        {
            _logger.LogInformation(
                "[子任务完成] 所有子任务已完成,触发父任务完成: TaskId={TaskId}, TaskCode={TaskCode}",
                task.TaskId, task.TaskCode);
            // 6. 触发父任务完成(会触发 TaskCompletedDomainEvent -> 更新库位状态)
            task.Completed(domainEvent.RobotId);
            await _taskRepository.UpdateAsync(task);
            await _taskRepository.SaveChangesAsync();
        }
        else
        {
            _logger.LogInformation(
                "[子任务完成] 还有未完成的子任务: TaskId={TaskId}, 已完成={Completed}/{Total}",
                task.TaskId,
                task.SubTasks.Count(st => st.Status == TaskStatus.Completed),
                task.SubTasks.Count);
        }
    }

    /// <summary>
    /// 清空机器人Redis缓存中的Path字段
    /// 子任务完成后机器人不再沿该路径行驶,需清空Path以避免前端显示过期路径
    /// @author zzy
    /// </summary>
    /// <param name="robotId">机器人ID</param>
    private async System.Threading.Tasks.Task ClearRobotCachePathAsync(Guid robotId)
    {
        try
        {
            if (robotId == Guid.Empty) return;

            var robot = await _robotRepository.GetByIdAsync(robotId);
            if (robot == null)
            {
                _logger.LogWarning("[子任务完成] 清空缓存Path失败,机器人不存在: RobotId={RobotId}", robotId);
                return;
            }

            var result = await _robotCacheService.SetLocationValueAsync(
                robot.RobotManufacturer, robot.RobotSerialNumber, "Path", "");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[子任务完成] 清空机器人缓存Path异常: RobotId={RobotId}", robotId);
        }
    }
}