feat(auth): add Redis authorization caching

This commit is contained in:
2026-08-01 12:20:31 +08:00
parent 84c2b0b21d
commit 46abf4d62f
36 changed files with 21757 additions and 35 deletions

View File

@@ -7,6 +7,7 @@ using Tiku.Infrastructure;
using Tiku.Infrastructure.Assets;
using Tiku.Infrastructure.Storage;
using Tiku.Infrastructure.Observability;
using Tiku.Infrastructure.Security;
using OpenTelemetry.Metrics;
namespace Tiku.Worker;
@@ -29,8 +30,21 @@ internal static class WorkerDependencyInjection
"Database connection is required outside Development. Configure ConnectionStrings:Database or DATABASE_URL."));
builder.Services.AddApplication();
builder.Services.AddInfrastructure(connectionString);
var redisConnectionString = builder.Configuration.GetConnectionString("Redis") ?? builder.Configuration["REDIS_URL"];
builder.Services.AddOptions<AuthorizationCacheOptions>()
.Bind(builder.Configuration.GetSection(AuthorizationCacheOptions.SectionName));
if (!string.IsNullOrWhiteSpace(redisConnectionString))
{
builder.Services.AddRedisSecurity(redisConnectionString, builder.Environment.EnvironmentName);
}
else if (builder.Environment.IsProduction())
{
throw new InvalidOperationException(
"Redis is required in Production for authorization cache invalidation retries.");
}
var otlpEndpoint = builder.Configuration["OpenTelemetry:OtlpEndpoint"];
var telemetry = builder.Services.AddOpenTelemetry().WithMetrics(metrics => metrics.AddMeter(WorkerTelemetry.MeterName));
var telemetry = builder.Services.AddOpenTelemetry().WithMetrics(metrics =>
metrics.AddMeter(WorkerTelemetry.MeterName, AuthorizationCacheTelemetry.MeterName));
if (Uri.TryCreate(otlpEndpoint, UriKind.Absolute, out var endpoint))
{
telemetry.WithMetrics(metrics => metrics.AddOtlpExporter(options => options.Endpoint = endpoint));
@@ -109,6 +123,7 @@ internal static class WorkerDependencyInjection
builder.Services.AddHostedService<SaasSubscriptionWorker>();
builder.Services.AddHostedService<FeatureUsageWorker>();
builder.Services.AddHostedService<BackgroundJobsWorker>();
builder.Services.AddHostedService<AuthorizationCacheInvalidationWorker>();
return builder;
}

View File

@@ -262,3 +262,21 @@ internal sealed class BackgroundJobsWorker(
.ProcessPendingAsync($"{workerId}:{index}", batchSize, includeImmediateJobs: true, cancellationToken: cancellationToken);
}
}
internal sealed class AuthorizationCacheInvalidationWorker(
IServiceScopeFactory scopeFactory,
IPeriodicProcessorLock processorLock,
WorkerStateReporter stateReporter,
IOptions<WorkerOptions> options,
ILogger<AuthorizationCacheInvalidationWorker> logger)
: PeriodicWorker(logger, processorLock, stateReporter, "authorization-cache-invalidations",
TimeSpan.FromSeconds(options.Value.JobPollSeconds), options.Value.Enabled)
{
protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
{
await using var scope = scopeFactory.CreateAsyncScope();
InitializeSystem(scope.ServiceProvider, "Authorization cache invalidation worker");
return await scope.ServiceProvider.GetRequiredService<IAuthorizationCacheInvalidationProcessor>()
.ProcessPendingAsync(cancellationToken: cancellationToken);
}
}