first commit
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
using Fitness_core_BE.Infra.Tenancy;
|
||||
using Fitness_core_BE.Interfaces;
|
||||
|
||||
namespace Fitness_core_BE.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Periodically locks (and un-locks) member fingerprint access based on each client's
|
||||
/// configurable Client.LockAfterDaysOverdue setting (req 7). Reuses the existing
|
||||
/// AccessDeviceService.LockCustomer/UnlockCustomer (BlackListMember/WhiteListMember)
|
||||
/// pipeline — this job only decides *who* should be locked, not *how*.
|
||||
///
|
||||
/// This is a background service with no HttpContext, so TenantConnectionResolver's normal
|
||||
/// per-request tenant resolution can never apply here -- a DI-scoped TenantDbContext always
|
||||
/// resolves to the same single fallback schema regardless of which client is being checked.
|
||||
/// Every client's overdue-check therefore opens its OWN schema directly via
|
||||
/// TenantSchemaIterator instead of using the scope's injected TenantDbContext.
|
||||
///
|
||||
/// The per-client algorithm itself lives in OverdueLockRunner so
|
||||
/// AdminBackgroundJobsController's manual "trigger now" endpoint can call the exact same
|
||||
/// code path instead of duplicating it -- this class only owns the 6-hour loop and the
|
||||
/// central-only client lookup.
|
||||
/// </summary>
|
||||
public class OverdueLockJob : BackgroundService
|
||||
{
|
||||
private static readonly TimeSpan Interval = TimeSpan.FromHours(6);
|
||||
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<OverdueLockJob> _logger;
|
||||
|
||||
public OverdueLockJob(IServiceScopeFactory scopeFactory, ILogger<OverdueLockJob> logger)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await RunOnceAsync(stoppingToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "OverdueLockJob run failed.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(Interval, stoppingToken);
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// Shutting down.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RunOnceAsync(CancellationToken ct)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var iterator = scope.ServiceProvider.GetRequiredService<TenantSchemaIterator>();
|
||||
var runner = scope.ServiceProvider.GetRequiredService<OverdueLockRunner>();
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Central-only lookup -- safe to use the scope's normal DI-resolved repository,
|
||||
// since GetClientsWithOverdueLockPolicyAsync only ever touches CentralDbContext.
|
||||
var bootstrapRepo = scope.ServiceProvider.GetRequiredService<IOverdueLockRepository>();
|
||||
var clients = await bootstrapRepo.GetClientsWithOverdueLockPolicyAsync(ct);
|
||||
|
||||
foreach (var client in clients)
|
||||
{
|
||||
try
|
||||
{
|
||||
await runner.RunForClientAsync(client, now, iterator, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "OverdueLockJob failed for client {ClientId}.", client.ClientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user