VdaStrictBindingStartupGuardService.cs 2.58 KB
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Rcs.Domain.Settings;
using StackExchange.Redis;

namespace Rcs.Infrastructure.Services;

/// <summary>
/// VDA5050 严格 binding 切换启动守卫。
/// 若检测到停机切换前遗留的旧路径缓存/锁数据,则直接阻止服务启动。
/// </summary>
public sealed class VdaStrictBindingStartupGuardService : IHostedService
{
    private readonly ILogger<VdaStrictBindingStartupGuardService> _logger;
    private readonly IConnectionMultiplexer _redis;
    private readonly AppSettings _settings;

    public VdaStrictBindingStartupGuardService(
        ILogger<VdaStrictBindingStartupGuardService> logger,
        IConnectionMultiplexer redis,
        IOptions<AppSettings> settings)
    {
        _logger = logger;
        _redis = redis;
        _settings = settings.Value;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        var endPoints = _redis.GetEndPoints();
        if (endPoints.Length == 0)
        {
            _logger.LogWarning("[VDA严格守卫] 未发现 Redis 端点,跳过严格 binding 启动检查。");
            return Task.CompletedTask;
        }

        var patterns = new[]
        {
            $"{_settings.Redis.KeyPrefixes.VdaPath}:*",
            $"{_settings.Redis.KeyPrefixes.NodeLockPrefix}:*",
            $"{_settings.Redis.KeyPrefixes.EdgeLockPrefix}:*",
            $"{_settings.Redis.KeyPrefixes.RobotLockResourcesPrefix}:*"
        };

        foreach (var endPoint in endPoints)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var server = _redis.GetServer(endPoint);
            if (!server.IsConnected || server.IsReplica)
            {
                continue;
            }

            foreach (var pattern in patterns)
            {
                var leakedKeys = server.Keys(pattern: pattern, pageSize: 50)
                    .Take(5)
                    .Select(key => key.ToString())
                    .ToList();

                if (leakedKeys.Count == 0)
                {
                    continue;
                }

                var message =
                    $"[VDA严格守卫] 检测到停机切换前遗留的 Redis 键,拒绝启动。Pattern={pattern}, SampleKeys={string.Join(",", leakedKeys)}";
                _logger.LogCritical(message);
                throw new InvalidOperationException(message);
            }
        }

        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}