RobotAlarmLogSyncHelper.cs 5.55 KB
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Rcs.Application.DTOs;
using Rcs.Application.Services;
using Rcs.Domain.Entities;
using Rcs.Infrastructure.DB.MsSql;

namespace Rcs.Infrastructure.MessageBus.Handlers.Commands;

internal static class RobotAlarmLogSyncHelper
{
    public static async Task SyncAsync(
        AppDbContext dbContext,
        IRobotCacheService robotCacheService,
        ILogger logger,
        CancellationToken cancellationToken)
    {
        try
        {
            var activeRobots = (await robotCacheService.GetAllActiveRobotCacheAsync()).ToList();
            var activeRobotCodes = activeRobots
                .Select(x => x.Basic.RobotCode)
                .Where(x => !string.IsNullOrWhiteSpace(x))
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToList();

            if (activeRobotCodes.Count == 0)
            {
                return;
            }

            var existingRobotAlarms = await dbContext.Set<AlarmLog>()
                .Where(x => x.SourceType == "robot" && activeRobotCodes.Contains(x.SourceCode!))
                .ToListAsync(cancellationToken);

            var now = DateTime.Now;
            var matchedIds = new HashSet<Guid>();

            foreach (var robot in activeRobots)
            {
                if (string.IsNullOrWhiteSpace(robot.Basic.RobotCode) || robot.Status?.Errors == null)
                {
                    continue;
                }

                foreach (var error in robot.Status.Errors.Where(HasErrorContent))
                {
                    var severity = MapRobotSeverity(error.ErrorLevel);
                    var message = BuildRobotMessage(error);
                    var occurredAt =
                      robot.Status.StateTimestampUtc?.ToLocalTime()
                      ?? (DateTime?)robot.Status.UpdatedAt
                      ?? now;

                    var existing = existingRobotAlarms.FirstOrDefault(x =>
                        x.SourceType == "robot" &&
                        x.SourceCode == robot.Basic.RobotCode &&
                        x.AlarmType == NormalizeText(error.ErrorType) &&
                        x.Level == severity &&
                        x.Message == message &&
                        x.ResolvedAt == null);

                    if (existing != null)
                    {
                        matchedIds.Add(existing.AlarmLogId);
                        continue;
                    }

                    var entity = new AlarmLog
                    {
                        AlarmLogId = Guid.NewGuid(),
                        AlarmCode = BuildRobotAlarmCode(robot.Basic.RobotCode, error),
                        AlarmType = NormalizeText(error.ErrorType),
                        Level = severity,
                        SourceType = "robot",
                        SourceCode = robot.Basic.RobotCode,
                        SourceName = robot.Basic.RobotName,
                        Title = BuildRobotTitle(robot.Basic.RobotName, error.ErrorType),
                        Message = message,
                        Details = NormalizeText(error.ErrorDescription),
                        ExtraData = JsonSerializer.Serialize(error),
                        OccurredAt = occurredAt,
                        CreatedAt = now
                    };

                    dbContext.Set<AlarmLog>().Add(entity);
                    existingRobotAlarms.Add(entity);
                    matchedIds.Add(entity.AlarmLogId);
                }
            }

            foreach (var alarm in existingRobotAlarms.Where(x => x.ResolvedAt == null && !matchedIds.Contains(x.AlarmLogId)))
            {
                alarm.ResolvedAt = now;
                alarm.UpdatedAt = now;
            }

            if (dbContext.ChangeTracker.HasChanges())
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "同步机器人报警日志失败");
        }
    }

    private static bool HasErrorContent(RobotError error)
    {
        return !string.IsNullOrWhiteSpace(error.ErrorType) ||
               !string.IsNullOrWhiteSpace(error.ErrorDescription);
    }

    private static string MapRobotSeverity(string? errorLevel)
    {
        return errorLevel?.Trim() switch
        {
            "4" => "critical",
            "3" => "warning",
            "2" => "warning",
            "1" => "info",
            _ => "warning"
        };
    }

    private static string BuildRobotAlarmCode(string robotCode, RobotError error)
    {
        var type = string.IsNullOrWhiteSpace(error.ErrorType) ? "UNKNOWN" : error.ErrorType.Trim().ToUpperInvariant();
        return $"ROBOT-{robotCode}-{type}";
    }

    private static string BuildRobotTitle(string robotName, string? errorType)
    {
        var prefix = string.IsNullOrWhiteSpace(robotName) ? "机器人" : robotName.Trim();
        var suffix = string.IsNullOrWhiteSpace(errorType) ? "报警" : errorType.Trim();
        return $"{prefix}{suffix}";
    }

    private static string BuildRobotMessage(RobotError error)
    {
        if (!string.IsNullOrWhiteSpace(error.ErrorDescription))
        {
            return error.ErrorDescription.Trim();
        }

        if (!string.IsNullOrWhiteSpace(error.ErrorType))
        {
            return error.ErrorType.Trim();
        }

        return "机器人报警";
    }

    private static string? NormalizeText(string? value)
    {
        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
    }
}