feat: добавил инвалидацию кэшей плагинов
Create and publish a Docker image / Publish image (push) Successful in 1m53s

This commit is contained in:
serega404 committed 2026-09-15 22:36:33 +03:00
1 parent 01bab77900
commit c590a8a6c7
5 files changed
+51 -4

No files matched your search

+5 -1
View File
@@ -10,4 +10,8 @@ public interface IPlugin
// Регистрация маршрутов (Minimal API) плагина после Build() // Регистрация маршрутов (Minimal API) плагина после Build()
void MapEndpoints(IEndpointRouteBuilder endpoints); void MapEndpoints(IEndpointRouteBuilder endpoints);
}
// Инвалидация или обновление внутренних кэшей плагина.
Task InvalidateCacheAsync(IServiceProvider serviceProvider, CancellationToken cancellationToken) =>
Task.CompletedTask;
}
+6 -1
View File
@@ -89,6 +89,7 @@
- загружает большой список сотрудников; - загружает большой список сотрудников;
- группирует записи по ФИО; - группирует записи по ФИО;
- сохраняет результат на диск; - сохраняет результат на диск;
- после успешного обновления предлагает всем плагинам инвалидировать свои кэши;
- повторяет попытки при временных сбоях. - повторяет попытки при временных сбоях.
Если сервис ещё не инициализирован, `GET /api/schedule/searchemployee` возвращает `503`. Если сервис ещё не инициализирован, `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), чтобы: На старте хост ищет все `*.plugin.dll` внутри `data/Plugins` и загружает найденные сборки как плагины. Плагин должен реализовывать интерфейс [IPlugin](/home/serega404/Рабочий стол/SfeduSchedule/ModeusSchedule.Abstractions/IPlugin.cs), чтобы:
- зарегистрировать свои сервисы в DI через `ConfigureServices`; - зарегистрировать свои сервисы в DI через `ConfigureServices`;
- добавить minimal API маршруты через `MapEndpoints`. - добавить minimal API маршруты через `MapEndpoints`;
- опционально обновить или очистить свои кэши через `InvalidateCacheAsync`.
`InvalidateCacheAsync` имеет default-реализацию, поэтому плагины, собранные до добавления этого метода,
продолжают загружаться. Ошибка инвалидации одного плагина логируется и не мешает вызвать остальные.
MVC-контроллеры из плагинов тоже подключаются автоматически через `ApplicationPart`. MVC-контроллеры из плагинов тоже подключаются автоматически через `ApplicationPart`.
+4 -2
View File
@@ -12,7 +12,8 @@ namespace SfeduSchedule.Jobs;
public class UpdateEmployeesJob( public class UpdateEmployeesJob(
ILogger<UpdateEmployeesJob> logger, ILogger<UpdateEmployeesJob> logger,
ModeusEmployeeService employeeService, ModeusEmployeeService employeeService,
ModeusService modeusService) : IJob ModeusService modeusService,
PluginCacheInvalidator pluginCacheInvalidator) : IJob
{ {
private const int MaxAttempts = 5; // Максимальное число попыток private const int MaxAttempts = 5; // Максимальное число попыток
private const int DelaySeconds = 50; // Задержка между попытками в секундах private const int DelaySeconds = 50; // Задержка между попытками в секундах
@@ -47,6 +48,7 @@ public class UpdateEmployeesJob(
await employeeService.SetEmployees(employees); await employeeService.SetEmployees(employees);
logger.LogInformationHere($"Получено {employees.Count} сотрудников из Modeus."); logger.LogInformationHere($"Получено {employees.Count} сотрудников из Modeus.");
await pluginCacheInvalidator.InvalidateAsync(jobContext.CancellationToken);
return; return;
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
@@ -75,4 +77,4 @@ public class UpdateEmployeesJob(
await Task.Delay(TimeSpan.FromSeconds(DelaySeconds), jobContext.CancellationToken); await Task.Delay(TimeSpan.FromSeconds(DelaySeconds), jobContext.CancellationToken);
} }
} }
} }
+4
View File
@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Identity.Web; using Microsoft.Identity.Web;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using ModeusSchedule.Abstractions;
using Prometheus; using Prometheus;
using Quartz; using Quartz;
using Quartz.Listener; using Quartz.Listener;
@@ -133,11 +134,14 @@ foreach (var p in loadedPlugins)
// DI из плагина // DI из плагина
p.Instance.ConfigureServices(builder.Services); p.Instance.ConfigureServices(builder.Services);
builder.Services.AddSingleton<IPlugin>(p.Instance);
// Подключаем контроллеры плагина // Подключаем контроллеры плагина
mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(p.Assembly)); mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(p.Assembly));
} }
builder.Services.AddSingleton<PluginCacheInvalidator>();
var updateJwtJob = new JobKey("UpdateJWTJob"); var updateJwtJob = new JobKey("UpdateJWTJob");
builder.Services.AddQuartz(q => builder.Services.AddQuartz(q =>
@@ -0,0 +1,32 @@
using ModeusSchedule.Abstractions;
namespace SfeduSchedule.Services;
public sealed class PluginCacheInvalidator(
IEnumerable<IPlugin> plugins,
IServiceScopeFactory scopeFactory,
ILogger<PluginCacheInvalidator> 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);
}
}
}
}