110 lines
5.0 KiB
C#
110 lines
5.0 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Options;
|
|
using Tiku.Application.Assets;
|
|
using Tiku.Application.PlatformAdmin.Operations;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Application.Storage;
|
|
using Tiku.Domain.Operations;
|
|
using Tiku.Domain.Platform;
|
|
using Tiku.Infrastructure.Persistence;
|
|
using Tiku.Infrastructure.Storage;
|
|
|
|
namespace Tiku.Infrastructure.PlatformAdmin.Operations;
|
|
|
|
internal sealed class PlatformOperationsQueryService(
|
|
TikuDbContext dbContext,
|
|
IRedisSecurityStore redisSecurityStore,
|
|
IAssetSecurityScanner assetSecurityScanner,
|
|
IObjectStorageService objectStorageService,
|
|
IOptions<AliyunOssOptions> aliyunOssOptions) : IPlatformOperationsQueryService
|
|
{
|
|
public async Task<PlatformDependencyHealth> GetHealthAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var database = await dbContext.Database.CanConnectAsync(cancellationToken);
|
|
var redis = !redisSecurityStore.IsConfigured || await redisSecurityStore.PingAsync(cancellationToken);
|
|
var clamAv = await assetSecurityScanner.CheckHealthAsync(cancellationToken);
|
|
var storageProvider = objectStorageService.ConfiguredDefaultProvider();
|
|
var storageConfigured = storageProvider switch
|
|
{
|
|
ObjectStorageProviders.AliyunOss => aliyunOssOptions.Value.IsConfigured,
|
|
ObjectStorageProviders.LocalDev => true,
|
|
_ => false
|
|
};
|
|
var heartbeat = await dbContext.WorkerHeartbeats.AsNoTracking()
|
|
.MaxAsync(item => (DateTimeOffset?)item.LastHeartbeatAt, cancellationToken);
|
|
var workerReady = heartbeat >= DateTimeOffset.UtcNow.AddMinutes(-2);
|
|
return new PlatformDependencyHealth(
|
|
database && redis && clamAv && workerReady && storageConfigured,
|
|
database,
|
|
redisSecurityStore.IsConfigured,
|
|
redis,
|
|
workerReady,
|
|
heartbeat,
|
|
clamAv,
|
|
storageProvider,
|
|
storageConfigured,
|
|
DateTimeOffset.UtcNow);
|
|
}
|
|
|
|
public async Task<IReadOnlyCollection<PlatformWorkerState>> GetWorkersAsync(
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var staleBefore = DateTimeOffset.UtcNow.AddMinutes(-2);
|
|
return await dbContext.WorkerHeartbeats.AsNoTracking()
|
|
.OrderBy(item => item.WorkerId)
|
|
.ThenBy(item => item.Processor)
|
|
.Select(item => new PlatformWorkerState(
|
|
item.WorkerId,
|
|
item.Processor,
|
|
item.StartedAt,
|
|
item.LastHeartbeatAt,
|
|
item.LastIterationStartedAt,
|
|
item.LastIterationCompletedAt,
|
|
item.LastSucceededAt,
|
|
item.LastError,
|
|
item.IsRunning,
|
|
item.LastHeartbeatAt < staleBefore))
|
|
.ToArrayAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<PlatformJobMetrics> GetJobMetricsAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var now = DateTimeOffset.UtcNow;
|
|
var counts = await dbContext.BackgroundJobs.AsNoTracking()
|
|
.GroupBy(item => item.Status)
|
|
.Select(group => new PlatformMetricCount(group.Key.ToString(), group.Count()))
|
|
.ToArrayAsync(cancellationToken);
|
|
var oldest = await dbContext.BackgroundJobs.AsNoTracking()
|
|
.Where(item => item.Status == BackgroundJobStatus.Pending)
|
|
.MinAsync(item => (DateTimeOffset?)item.CreatedAt, cancellationToken);
|
|
var expired = await dbContext.BackgroundJobs.AsNoTracking()
|
|
.CountAsync(item => item.Status == BackgroundJobStatus.Processing && item.LockExpiresAt < now,
|
|
cancellationToken);
|
|
return new PlatformJobMetrics(
|
|
counts,
|
|
oldest,
|
|
oldest.HasValue ? Math.Max(0, (now - oldest.Value).TotalSeconds) : 0,
|
|
expired,
|
|
now);
|
|
}
|
|
|
|
public async Task<PlatformGovernanceMetrics> GetGovernanceMetricsAsync(
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var now = DateTimeOffset.UtcNow;
|
|
var approvals = await dbContext.PlatformApprovalRequests.AsNoTracking()
|
|
.GroupBy(item => item.Status)
|
|
.Select(group => new PlatformMetricCount(group.Key.ToString(), group.Count()))
|
|
.ToArrayAsync(cancellationToken);
|
|
var expired = await dbContext.PlatformApprovalRequests.AsNoTracking()
|
|
.CountAsync(item => item.Status == PlatformApprovalRequestStatus.Pending && item.ExpiresAt <= now,
|
|
cancellationToken);
|
|
var drafts = await dbContext.PlatformConfigurationVersions.AsNoTracking()
|
|
.CountAsync(item => item.Status == PlatformConfigurationVersionStatus.Draft, cancellationToken);
|
|
var notifications = await dbContext.PlatformNotificationDeliveries.AsNoTracking()
|
|
.GroupBy(item => item.Status)
|
|
.Select(group => new PlatformMetricCount(group.Key.ToString(), group.Count()))
|
|
.ToArrayAsync(cancellationToken);
|
|
return new PlatformGovernanceMetrics(approvals, expired, drafts, notifications, now);
|
|
}
|
|
} |