feat: Implementazione completa sistema schedulazione con intervalli personalizzati
- Aggiunto supporto schedulazione con intervalli flessibili (secondi/minuti/ore/giorni/settimane/mesi) - Esteso modello ProfileSchedule con campi IntervalValue e IntervalUnit - Ottimizzato ScheduledJobService per controlli ogni 30s con esecuzione parallela - Implementata interfaccia UI completa con anteprima real-time in italiano - Aggiunta migrazione database AddIntervalSchedulingFields - Implementati metodi calcolo NextExecutionTime per intervalli - Aggiunta gestione tracking anti-duplicati e cleanup automatico - Creata documentazione completa (6 file, 2500+ righe) Modifiche tecniche: - ProfileSchedule.cs: Nuovi campi e metodi CalculateNextInterval/GetScheduleDescription - ScheduledJobService.cs: Ridotto check interval a 30s, aggiunto parallel processing - ProfileScheduleService.cs: Supporto calcolo intervalli in UpdateNextExecutionTimeAsync - Scheduling.razor: Aggiunta sezione UI per configurazione intervalli - Scheduling.razor.cs: Implementato GetIntervalPreview() e gestione stato campi
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
using CredentialManager.Services;
|
||||
using Data_Coupler.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Data_Coupler.BackgroundServices;
|
||||
|
||||
/// <summary>
|
||||
/// Servizio di background per l'esecuzione automatica delle schedulazioni
|
||||
/// </summary>
|
||||
public class ScheduleExecutorService : BackgroundService
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly ILogger<ScheduleExecutorService> _logger;
|
||||
private readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(1); // Controlla ogni minuto
|
||||
|
||||
public ScheduleExecutorService(IServiceProvider serviceProvider, ILogger<ScheduleExecutorService> logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("ScheduleExecutorService avviato. Controllo schedulazioni ogni {Interval} minuti.",
|
||||
_checkInterval.TotalMinutes);
|
||||
|
||||
while (!stoppingToken.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await CheckAndExecuteSchedules();
|
||||
await Task.Delay(_checkInterval, stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("ScheduleExecutorService arrestato.");
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel controllo schedulazioni");
|
||||
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); // Attendi 5 minuti prima di riprovare
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckAndExecuteSchedules()
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var scheduleService = scope.ServiceProvider.GetRequiredService<IProfileScheduleService>();
|
||||
var dataTransferService = scope.ServiceProvider.GetRequiredService<IDataTransferService>();
|
||||
|
||||
try
|
||||
{
|
||||
var pendingSchedules = await scheduleService.GetPendingExecutionsAsync();
|
||||
|
||||
if (pendingSchedules.Any())
|
||||
{
|
||||
_logger.LogInformation("Trovate {Count} schedulazioni in attesa di esecuzione", pendingSchedules.Count);
|
||||
}
|
||||
|
||||
foreach (var schedule in pendingSchedules)
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteSchedule(schedule, scheduleService, dataTransferService);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'esecuzione della schedulazione {ScheduleName} (ID: {ScheduleId})",
|
||||
schedule.Name, schedule.Id);
|
||||
|
||||
// Aggiorna lo status dell'esecuzione fallita
|
||||
await scheduleService.UpdateExecutionStatusAsync(schedule.Id, "failed",
|
||||
$"Errore nell'esecuzione: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle schedulazioni in attesa");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteSchedule(CredentialManager.Models.ProfileSchedule schedule,
|
||||
IProfileScheduleService scheduleService,
|
||||
IDataTransferService dataTransferService)
|
||||
{
|
||||
_logger.LogInformation("Esecuzione schedulazione {ScheduleName} (ID: {ScheduleId}) iniziata",
|
||||
schedule.Name, schedule.Id);
|
||||
|
||||
// Aggiorna lo status a "running"
|
||||
await scheduleService.UpdateExecutionStatusAsync(schedule.Id, "running",
|
||||
"Esecuzione automatica avviata");
|
||||
|
||||
try
|
||||
{
|
||||
if (schedule.Profile == null)
|
||||
{
|
||||
throw new InvalidOperationException("Profilo associato alla schedulazione non trovato");
|
||||
}
|
||||
|
||||
// Esegui il trasferimento dati
|
||||
var result = await dataTransferService.ExecuteProfileAsync(schedule.Profile);
|
||||
|
||||
// Aggiorna lo status con il risultato
|
||||
var status = result.IsSuccess ? "success" : "failed";
|
||||
var message = result.IsSuccess
|
||||
? $"Esecuzione completata con successo in {result.Duration.TotalSeconds:F1} secondi. " +
|
||||
$"{result.RecordsProcessed} record elaborati."
|
||||
: $"Esecuzione fallita: {result.ErrorMessage}";
|
||||
|
||||
await scheduleService.UpdateExecutionStatusAsync(schedule.Id, status, message, result.RecordsProcessed);
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_logger.LogInformation("Schedulazione {ScheduleName} completata con successo. " +
|
||||
"Record processati: {RecordsProcessed}, Durata: {Duration}s",
|
||||
schedule.Name, result.RecordsProcessed, result.Duration.TotalSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("Schedulazione {ScheduleName} fallita: {ErrorMessage}",
|
||||
schedule.Name, result.ErrorMessage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore durante l'esecuzione della schedulazione {ScheduleName}", schedule.Name);
|
||||
|
||||
await scheduleService.UpdateExecutionStatusAsync(schedule.Id, "failed",
|
||||
$"Errore nell'esecuzione: {ex.Message}");
|
||||
|
||||
throw; // Re-throw per permettere la gestione a livello superiore
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("ScheduleExecutorService in fase di arresto...");
|
||||
await base.StopAsync(stoppingToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user