87 lines
3.4 KiB
C#
87 lines
3.4 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|