using System.Collections.Concurrent; using System.Diagnostics; using System.Net.Sockets; using System.Text.Json; using IM.InitCommon.Management; using MySqlConnector; using StackExchange.Redis; namespace IM.Admin.Services; public sealed class HealthSampler(IConfiguration config, IServiceScopeFactory scopes) { private readonly ConcurrentDictionary successes = new(); public async Task Read(CancellationToken ct) { using var scope = scopes.CreateScope(); var client = scope.ServiceProvider.GetRequiredService(); var tasks = new[] { "admin", "user", "contact", "group", "message", "file", "connector" }.Select(name => Check(name, async token => { var response = await client.Send(name, "/internal/management/health", ct: token); if (response.GetProperty("status").GetString() != "healthy") throw new InvalidOperationException(); return response.TryGetProperty("configVersion", out var version) ? version.GetInt64() : (long?)null; }, ct)).ToList(); tasks.Add(Check("MySQL", async token => { await using var db = new MySqlConnection(config.GetConnectionString("Admin")); await db.OpenAsync(token); return null; }, ct)); tasks.Add(Check("Redis", async token => { var options = ConfigurationOptions.Parse(config.GetConnectionString("Redis") ?? throw new InvalidOperationException()); options.ConnectTimeout = 2000; options.AbortOnConnectFail = true; using var redis = await ConnectionMultiplexer.ConnectAsync(options).WaitAsync(token); await redis.GetDatabase().PingAsync().WaitAsync(token); return null; }, ct)); tasks.Add(Check("RabbitMQ(TCP)", async token => { using var tcp = new TcpClient(); await tcp.ConnectAsync(config["Management:RabbitHost"] ?? "rabbitmq", config.GetValue("Management:RabbitPort", 5672), token); return null; }, ct)); tasks.Add(Check("Consul", async token => { using var http = new HttpClient(); using var response = await http.GetAsync((config["Management:ConsulUrl"] ?? throw new InvalidOperationException()).TrimEnd('/') + "/v1/status/leader", token); response.EnsureSuccessStatusCode(); return null; }, ct)); return await Task.WhenAll(tasks); } async Task Check(string name, Func> check, CancellationToken ct) { using var timeout = CancellationTokenSource.CreateLinkedTokenSource(ct); timeout.CancelAfter(TimeSpan.FromSeconds(3)); var sw = Stopwatch.StartNew(); try { var version = await check(timeout.Token); var now = DateTime.UtcNow; successes[name] = now; return new(name, "healthy", Math.Round(sw.Elapsed.TotalMilliseconds), now, now, null, version); } catch { return new(name, "unavailable", null, DateTime.UtcNow, successes.TryGetValue(name, out var time) ? time : null, "连接或就绪检查失败,请检查服务日志"); } } }