RobotStatusPushService.cs 5.26 KB
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Rcs.Api.Hubs;
using Rcs.Application.DTOs;
using Rcs.Application.Services;
using Rcs.Domain.Repositories;
using Rcs.Shared.Utils;

namespace Rcs.Api.BackgroundServices
{
    /// <summary>
    /// 机器人状态推送后台服务 - 定时推送机器人实时状态到前端
    /// @author zzy
    /// </summary>
    public class RobotStatusPushService : BackgroundService
    {
        private readonly ILogger<RobotStatusPushService> _logger;
        private readonly IHubContext<RobotStatusHub> _hubContext;
        private readonly IServiceScopeFactory _scopeFactory;
        private readonly TimeSpan _pushInterval = TimeSpan.FromMilliseconds(200);
        private readonly TimeSpan _locationPushInterval = TimeSpan.FromSeconds(2);
        private DateTime _lastLocationPush = DateTime.MinValue;

        public RobotStatusPushService(
            ILogger<RobotStatusPushService> logger,
            IHubContext<RobotStatusHub> hubContext,
            IServiceScopeFactory scopeFactory)
        {
            _logger = logger;
            _hubContext = hubContext;
            _scopeFactory = scopeFactory;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            _logger.LogInformation("[RobotStatusPush] 服务启动");

            while (!stoppingToken.IsCancellationRequested)
            {
                try
                {
                    await PushRobotStatusAsync(stoppingToken);
                    await PushStorageLocationsAsync(stoppingToken);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "[RobotStatusPush] 推送异常");
                }

                await Task.Delay(_pushInterval, stoppingToken);
            }
        }

        /// <summary>
        /// 推送机器人状态到订阅的客户端(纯缓存读取,不查数据库)
        /// @author zzy
        /// </summary>
        private async Task PushRobotStatusAsync(CancellationToken cancellationToken)
        {
            using var scope = _scopeFactory.CreateScope();
            var cacheService = scope.ServiceProvider.GetRequiredService<IRobotCacheService>();

            // 从缓存获取所有启用的机器人
            var activeRobots = await cacheService.GetAllActiveRobotCacheAsync();

            // 按地图分组
            var robotsByMap = activeRobots
                .Where(r => r.Location?.MapId.HasValue == true)
                .GroupBy(r => r.Location!.MapId!.Value);

            foreach (var mapGroup in robotsByMap)
            {
                var statusList = mapGroup.Select(r => new RobotRealtimeStatusDto
                {
                    RobotId = r.Basic.RobotId,
                    RobotType = r.Basic.RobotType,
                    RobotCode = r.Basic.RobotCode,
                    RobotName = r.Basic.RobotName,
                    X = r.Location?.X,
                    Y = r.Location?.Y,
                    Theta = AngleConverter.ToDegrees(r.Location?.Theta),
                    Status = r.Status != null ? (int)r.Status.Status : 1,
                    Online = r.Status != null ? (int)r.Status.Online : 2,
                    BatteryLevel = r.Status?.BatteryLevel,
                    Driving = r.Status?.Driving ?? false,
                    Charging = r.Status?.Charging ?? false,
                    Errors = r.Status?.Errors,
                    Path = r.Location?.Path
                }).ToList();

                await _hubContext.Clients.Group($"map_{mapGroup.Key}")
                    .SendAsync("RobotStatusUpdate", statusList, cancellationToken);
            }
        }

        /// <summary>
        /// 推送库位状态到订阅的客户端(按地图分组,降频推送)
        /// @author zzy
        /// </summary>
        private async Task PushStorageLocationsAsync(CancellationToken cancellationToken)
        {
            // 降频:每2秒推送一次库位状态
            if (DateTime.Now - _lastLocationPush < _locationPushInterval)
                return;
            _lastLocationPush = DateTime.Now;

            using var scope = _scopeFactory.CreateScope();
            var locationRepo = scope.ServiceProvider.GetRequiredService<IStorageLocationRepository>();
            var mapRepo = scope.ServiceProvider.GetRequiredService<IMapRepository>();

            // 获取所有启用的地图
            var maps = await mapRepo.GetAllAsync(cancellationToken);
            foreach (var map in maps.Where(m => m.Active))
            {
                var locations = await locationRepo.GetByMapIdAsync(map.MapId, cancellationToken);
                var locationList = locations.Select(l => new
                {
                    locationId = l.LocationId.ToString(),
                    mapNodeId = l.MapNodeId?.ToString(),
                    locationCode = l.LocationCode,
                    locationName = l.LocationName,
                    status = (int)l.Status
                }).ToList();

                await _hubContext.Clients.Group($"map_{map.MapId}")
                    .SendAsync("StorageLocationsUpdate", locationList, cancellationToken);
            }
        }
    }
}