forked from xiongyuxing/tiku-backend.net
115 lines
4.5 KiB
C#
115 lines
4.5 KiB
C#
using Microsoft.Extensions.Options;
|
|
using Tiku.Application.Jobs;
|
|
using Tiku.Application.PlatformBilling;
|
|
using Tiku.Application.Security;
|
|
using Tiku.Application.Tenancy;
|
|
using Tiku.Infrastructure.Messaging;
|
|
|
|
namespace Tiku.Worker;
|
|
|
|
internal abstract class PeriodicWorkerService(
|
|
ILogger logger,
|
|
TimeSpan interval) : BackgroundService
|
|
{
|
|
protected abstract Task<int> ProcessAsync(CancellationToken cancellationToken);
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
var processed = await ProcessAsync(stoppingToken);
|
|
if (processed > 0)
|
|
{
|
|
logger.LogInformation("{Worker} processed {Count} items.", GetType().Name, processed);
|
|
}
|
|
}
|
|
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
|
{
|
|
break;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
logger.LogError(exception, "{Worker} iteration failed.", GetType().Name);
|
|
}
|
|
|
|
await Task.Delay(interval, stoppingToken);
|
|
}
|
|
}
|
|
|
|
protected static void InitializeSystem(IServiceProvider services, string reason) =>
|
|
services.GetRequiredService<ITenantContextInitializer>().InitializeSystem(null, reason);
|
|
}
|
|
|
|
internal sealed class TenantDomainWorker(
|
|
IServiceScopeFactory scopeFactory,
|
|
IOptions<DomainLifecycleOptions> options,
|
|
ILogger<TenantDomainWorker> logger)
|
|
: PeriodicWorkerService(logger, TimeSpan.FromSeconds(Math.Clamp(options.Value.PollSeconds, 10, 3600)))
|
|
{
|
|
protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
|
|
{
|
|
await using var scope = scopeFactory.CreateAsyncScope();
|
|
InitializeSystem(scope.ServiceProvider, "Tenant domain DNS and TLS lifecycle worker");
|
|
return await scope.ServiceProvider.GetRequiredService<ITenantDomainLifecycleService>()
|
|
.ProcessPendingAsync(cancellationToken);
|
|
}
|
|
}
|
|
|
|
internal sealed class SaasSubscriptionWorker(
|
|
IServiceScopeFactory scopeFactory,
|
|
ILogger<SaasSubscriptionWorker> logger)
|
|
: PeriodicWorkerService(logger, TimeSpan.FromSeconds(60))
|
|
{
|
|
protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
|
|
{
|
|
await using var scope = scopeFactory.CreateAsyncScope();
|
|
InitializeSystem(scope.ServiceProvider, "SaaS subscription lifecycle discovery worker");
|
|
return await scope.ServiceProvider.GetRequiredService<ISaasSubscriptionLifecycleService>()
|
|
.ProcessDueAsync(cancellationToken: cancellationToken);
|
|
}
|
|
}
|
|
|
|
internal sealed class FeatureUsageWorker(
|
|
IServiceScopeFactory scopeFactory,
|
|
IOptions<FeatureUsageReconciliationOptions> options,
|
|
ILogger<FeatureUsageWorker> logger)
|
|
: PeriodicWorkerService(logger, TimeSpan.FromMinutes(Math.Clamp(options.Value.IntervalMinutes, 1, 1440)))
|
|
{
|
|
protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
|
|
{
|
|
await using var scope = scopeFactory.CreateAsyncScope();
|
|
InitializeSystem(scope.ServiceProvider, "Tenant feature usage reconciliation worker");
|
|
return await scope.ServiceProvider.GetRequiredService<IFeatureUsageReconciliationService>()
|
|
.ProcessDueAsync(cancellationToken);
|
|
}
|
|
}
|
|
|
|
internal sealed class BackgroundJobsWorker(
|
|
IServiceScopeFactory scopeFactory,
|
|
MessagingOptions messagingOptions,
|
|
ILogger<BackgroundJobsWorker> logger)
|
|
: PeriodicWorkerService(logger, TimeSpan.FromSeconds(2))
|
|
{
|
|
private readonly string workerId = $"{Environment.MachineName}:{Guid.NewGuid():N}";
|
|
|
|
protected override async Task<int> ProcessAsync(CancellationToken cancellationToken)
|
|
{
|
|
var workers = Enumerable.Range(0, 4).Select(index => ProcessPartitionAsync(index, cancellationToken));
|
|
return (await Task.WhenAll(workers)).Sum();
|
|
}
|
|
|
|
private async Task<int> ProcessPartitionAsync(int index, CancellationToken cancellationToken)
|
|
{
|
|
await using var scope = scopeFactory.CreateAsyncScope();
|
|
InitializeSystem(scope.ServiceProvider, "Background job lease worker");
|
|
return await scope.ServiceProvider.GetRequiredService<IBackgroundJobService>()
|
|
.ProcessPendingAsync(
|
|
$"{workerId}:{index}",
|
|
5,
|
|
includeImmediateJobs: !messagingOptions.IsConfigured,
|
|
cancellationToken: cancellationToken);
|
|
}
|
|
}
|