diff --git a/ModeusSchedule.Abstractions/IPlugin.cs b/ModeusSchedule.Abstractions/IPlugin.cs index 1af4538..002c39a 100644 --- a/ModeusSchedule.Abstractions/IPlugin.cs +++ b/ModeusSchedule.Abstractions/IPlugin.cs @@ -10,4 +10,8 @@ public interface IPlugin // Регистрация маршрутов (Minimal API) плагина после Build() void MapEndpoints(IEndpointRouteBuilder endpoints); -} \ No newline at end of file + + // Инвалидация или обновление внутренних кэшей плагина. + Task InvalidateCacheAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) => + Task.CompletedTask; +} diff --git a/README.md b/README.md index 4bca825..f16dbbb 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,7 @@ - загружает большой список сотрудников; - группирует записи по ФИО; - сохраняет результат на диск; +- после успешного обновления предлагает всем плагинам инвалидировать свои кэши; - повторяет попытки при временных сбоях. Если сервис ещё не инициализирован, `GET /api/schedule/searchemployee` возвращает `503`. @@ -131,7 +132,11 @@ Quartz job [UpdateJwtJob](/home/serega404/Рабочий стол/SfeduSchedule/ На старте хост ищет все `*.plugin.dll` внутри `data/Plugins` и загружает найденные сборки как плагины. Плагин должен реализовывать интерфейс [IPlugin](/home/serega404/Рабочий стол/SfeduSchedule/ModeusSchedule.Abstractions/IPlugin.cs), чтобы: - зарегистрировать свои сервисы в DI через `ConfigureServices`; -- добавить minimal API маршруты через `MapEndpoints`. +- добавить minimal API маршруты через `MapEndpoints`; +- опционально обновить или очистить свои кэши через `InvalidateCacheAsync`. + +`InvalidateCacheAsync` имеет default-реализацию, поэтому плагины, собранные до добавления этого метода, +продолжают загружаться. Ошибка инвалидации одного плагина логируется и не мешает вызвать остальные. MVC-контроллеры из плагинов тоже подключаются автоматически через `ApplicationPart`. diff --git a/SfeduSchedule/Jobs/UpdateEmployeesJob.cs b/SfeduSchedule/Jobs/UpdateEmployeesJob.cs index 0c98d23..8d77a7c 100644 --- a/SfeduSchedule/Jobs/UpdateEmployeesJob.cs +++ b/SfeduSchedule/Jobs/UpdateEmployeesJob.cs @@ -12,7 +12,8 @@ namespace SfeduSchedule.Jobs; public class UpdateEmployeesJob( ILogger logger, ModeusEmployeeService employeeService, - ModeusService modeusService) : IJob + ModeusService modeusService, + PluginCacheInvalidator pluginCacheInvalidator) : IJob { private const int MaxAttempts = 5; // Максимальное число попыток private const int DelaySeconds = 50; // Задержка между попытками в секундах @@ -47,6 +48,7 @@ public class UpdateEmployeesJob( await employeeService.SetEmployees(employees); logger.LogInformationHere($"Получено {employees.Count} сотрудников из Modeus."); + await pluginCacheInvalidator.InvalidateAsync(jobContext.CancellationToken); return; } catch (OperationCanceledException ex) @@ -75,4 +77,4 @@ public class UpdateEmployeesJob( await Task.Delay(TimeSpan.FromSeconds(DelaySeconds), jobContext.CancellationToken); } } -} \ No newline at end of file +} diff --git a/SfeduSchedule/Program.cs b/SfeduSchedule/Program.cs index c0062f1..1f66445 100644 --- a/SfeduSchedule/Program.cs +++ b/SfeduSchedule/Program.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Identity.Web; using Microsoft.OpenApi.Models; +using ModeusSchedule.Abstractions; using Prometheus; using Quartz; using Quartz.Listener; @@ -133,11 +134,14 @@ foreach (var p in loadedPlugins) // DI из плагина p.Instance.ConfigureServices(builder.Services); + builder.Services.AddSingleton(p.Instance); // Подключаем контроллеры плагина mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(p.Assembly)); } +builder.Services.AddSingleton(); + var updateJwtJob = new JobKey("UpdateJWTJob"); builder.Services.AddQuartz(q => diff --git a/SfeduSchedule/Services/PluginCacheInvalidator.cs b/SfeduSchedule/Services/PluginCacheInvalidator.cs new file mode 100644 index 0000000..26342a8 --- /dev/null +++ b/SfeduSchedule/Services/PluginCacheInvalidator.cs @@ -0,0 +1,32 @@ +using ModeusSchedule.Abstractions; + +namespace SfeduSchedule.Services; + +public sealed class PluginCacheInvalidator( + IEnumerable plugins, + IServiceScopeFactory scopeFactory, + ILogger logger) +{ + public async Task InvalidateAsync(CancellationToken cancellationToken) + { + foreach (var plugin in plugins) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + await using var scope = scopeFactory.CreateAsyncScope(); + await plugin.InvalidateCacheAsync(scope.ServiceProvider, cancellationToken); + logger.LogInformation("Кэш плагина {PluginName} инвалидирован", plugin.Name); + } + catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested) + { + throw; + } + catch (Exception ex) + { + logger.LogError(ex, "Не удалось инвалидировать кэш плагина {PluginName}", plugin.Name); + } + } + } +}