using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Tiku.Api.Contracts; using Tiku.Application.Jobs; using Tiku.Application.Security; using Tiku.Application.PlatformAdmin.Operations; using Tiku.Domain.Operations; namespace Tiku.Api.Controllers; [ApiController] [Tags("平台端-运维")] [Route("api/platform/operations")] [Authorize(Policy = BackendPermissions.PlatformOperationsView)] public sealed class PlatformOperationsController( IBackgroundJobOperations backgroundJobService, ICurrentUser currentUser, IPlatformOperationsQueryService operationsQueries) : ControllerBase { [HttpGet("health")] [EndpointSummary("查询受保护的依赖深度健康状态")] public async Task> Health(CancellationToken cancellationToken) { var health = await operationsQueries.GetHealthAsync(cancellationToken); return Ok(new { status = health.Healthy ? "healthy" : "degraded", database = health.Database, redis = new { configured = health.RedisConfigured, ready = health.RedisReady }, worker = new { ready = health.WorkerReady, lastHeartbeatAt = health.LastHeartbeatAt }, clamAv = health.ClamAv, storage = new { provider = health.StorageProvider, configured = health.StorageConfigured }, checkedAt = health.CheckedAt }); } [HttpGet("workers")] [EndpointSummary("查询 Worker 与周期循环状态")] public async Task> Workers(CancellationToken cancellationToken) { var items = await operationsQueries.GetWorkersAsync(cancellationToken); return Ok(new { staleAfterSeconds = 120, items = items.Select(item => new { item.WorkerId, item.Processor, item.StartedAt, item.LastHeartbeatAt, item.LastIterationStartedAt, item.LastIterationCompletedAt, item.LastSucceededAt, item.LastError, item.IsRunning, stale = item.Stale }) }); } [HttpGet("job-metrics")] [EndpointSummary("查询后台任务队列指标")] public async Task> JobMetrics(CancellationToken cancellationToken) { var metrics = await operationsQueries.GetJobMetricsAsync(cancellationToken); return Ok(new { counts = metrics.Counts.Select(item => new { status = item.Status, count = item.Count }), oldestPendingAt = metrics.OldestPendingAt, queueAgeSeconds = metrics.QueueAgeSeconds, expiredLeases = metrics.ExpiredLeases, checkedAt = metrics.CheckedAt }); } [HttpGet("governance-metrics")] [EndpointSummary("查询审批、配置与通知治理指标")] public async Task> GovernanceMetrics(CancellationToken cancellationToken) { var metrics = await operationsQueries.GetGovernanceMetricsAsync(cancellationToken); return Ok(new { approvals = metrics.Approvals.Select(item => new { status = item.Status, count = item.Count }), expiredPending = metrics.ExpiredPending, configurationDrafts = metrics.ConfigurationDrafts, notifications = metrics.Notifications.Select(item => new { status = item.Status, count = item.Count }), checkedAt = metrics.CheckedAt }); } [HttpGet("jobs")] [EndpointSummary("查询全平台后台任务")] public async Task>> Jobs( [FromQuery] Guid? tenantId, [FromQuery] string? jobType, [FromQuery] BackgroundJobStatus? status, [FromQuery] int? limit, CancellationToken cancellationToken) => Ok(await backgroundJobService.ListPlatformAsync( tenantId, jobType, status, limit ?? 100, cancellationToken)); [HttpGet("jobs/{jobId:guid}")] [EndpointSummary("查询平台后台任务详情")] public async Task> Job(Guid jobId, CancellationToken cancellationToken) { var item = await backgroundJobService.GetAsync(jobId, null, cancellationToken); return item is null ? NotFound() : Ok(item); } [HttpPost("jobs/{jobId:guid}/cancel")] [Authorize(Policy = BackendPermissions.PlatformOperationsManage)] [EndpointSummary("取消平台后台任务")] public async Task> CancelJob( Guid jobId, CancelBackgroundJobDto request, CancellationToken cancellationToken) => Ok(await backgroundJobService.RequestCancellationAsync( jobId, null, ResolveUserId(), request.Reason, cancellationToken)); [HttpPost("jobs/{jobId:guid}/retry")] [Authorize(Policy = BackendPermissions.PlatformOperationsManage)] [EndpointSummary("重试平台后台任务")] public async Task> RetryJob(Guid jobId, CancellationToken cancellationToken) => Ok(await backgroundJobService.RetryAsync(jobId, null, ResolveUserId(), cancellationToken)); private Guid ResolveUserId() => currentUser.UserId ?? throw new InvalidOperationException("Current platform user was not resolved."); }