using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Application.Security; using Tiku.Infrastructure.Persistence; using Microsoft.EntityFrameworkCore; using MassTransit.EntityFrameworkCoreIntegration; using Microsoft.Extensions.Diagnostics.HealthChecks; using Tiku.Infrastructure.Messaging; namespace Tiku.Api.Controllers; [ApiController] [AllowAnonymous] [Produces("application/json")] [Route("api/health")] public sealed class HealthController( TikuDbContext dbContext, IRedisSecurityStore redisSecurityStore, MessagingOptions messagingOptions, HealthCheckService healthCheckService) : ControllerBase { [HttpGet] [EndpointSummary("健康检查")] [EndpointDescription("用于本地、CI 和部署环境 smoke test 的轻量健康检查。")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult Get() { return Ok(new HealthResponseDto( "ok", "tiku-api", DateTimeOffset.UtcNow)); } [HttpGet("ready")] [EndpointSummary("依赖就绪检查")] public async Task> Ready(CancellationToken cancellationToken) { var database = await dbContext.Database.CanConnectAsync(cancellationToken); var redis = !redisSecurityStore.IsConfigured || await redisSecurityStore.PingAsync(cancellationToken); var rabbitHealth = await healthCheckService.CheckHealthAsync( registration => registration.Tags.Contains("ready"), cancellationToken); var rabbitMq = !messagingOptions.IsConfigured || rabbitHealth.Status == HealthStatus.Healthy; var outboxPending = database ? await dbContext.Set().CountAsync(cancellationToken) : -1; var outboxOldestSentTime = database ? await dbContext.Set() .Select(message => (DateTime?)message.SentTime) .MinAsync(cancellationToken) : null; var outboxOldestAgeSeconds = outboxOldestSentTime is null ? 0 : Math.Max(0, (DateTimeOffset.UtcNow - new DateTimeOffset(outboxOldestSentTime.Value)).TotalSeconds); var outboxAlert = outboxPending >= messagingOptions.OutboxBacklogAlertCount || outboxOldestAgeSeconds >= messagingOptions.OutboxOldestMessageAlertSeconds; var ready = database && redis && rabbitMq; var response = new { status = ready ? "ready" : "not_ready", database, redis = new { configured = redisSecurityStore.IsConfigured, ready = redis }, rabbitMq = new { configured = messagingOptions.IsConfigured, ready = rabbitMq }, outbox = new { pending = outboxPending, oldestAgeSeconds = Math.Round(outboxOldestAgeSeconds, 1), alert = outboxAlert, backlogAlertCount = messagingOptions.OutboxBacklogAlertCount, oldestMessageAlertSeconds = messagingOptions.OutboxOldestMessageAlertSeconds }, checkedAt = DateTimeOffset.UtcNow }; return ready ? Ok(response) : StatusCode(StatusCodes.Status503ServiceUnavailable, response); } }