Compare commits
4 Commits
a9c0bb1d52
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f08d6af4c7 | |||
| 46bdc07910 | |||
| 46c50dc8e2 | |||
| c3535cafe9 |
@@ -8,5 +8,4 @@ public static class GlobalConsts
|
|||||||
public static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
public static readonly JsonSerializerOptions JsonSerializerOptions = new()
|
||||||
{ PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
{ PropertyNamingPolicy = JsonNamingPolicy.CamelCase, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping };
|
||||||
|
|
||||||
public static string JwtFilePath { get; set; } = "data/jwt.txt";
|
|
||||||
}
|
}
|
||||||
30
SfeduSchedule/AppConsts.cs
Normal file
30
SfeduSchedule/AppConsts.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
namespace SfeduSchedule;
|
||||||
|
|
||||||
|
public static class AppConsts
|
||||||
|
{
|
||||||
|
// Quartz Jobs Cron expressions
|
||||||
|
public const string UpdateJwtCronEnv = "UPDATE_JWT_CRON";
|
||||||
|
public const string UpdateEmployeeCronEnv = "UPDATE_EMPLOYEES_CRON";
|
||||||
|
|
||||||
|
// Modeus
|
||||||
|
public const string PreinstalledJwtTokenEnv = "TOKEN";
|
||||||
|
public const string ModeusUrlEnv = "MODEUS_URL";
|
||||||
|
public const string ModeusDefaultUrl = "https://sfedu.modeus.org/";
|
||||||
|
|
||||||
|
// Telegram
|
||||||
|
public const string TgChatIdEnv = "TG_CHAT_ID";
|
||||||
|
public const string TgTokenEnv = "TG_TOKEN";
|
||||||
|
|
||||||
|
// RateLimiter
|
||||||
|
public const string PermitLimitEnv = "PERMIT_LIMIT";
|
||||||
|
public const string TimeLimitEnv = "TIME_LIMIT";
|
||||||
|
|
||||||
|
// MS Auth
|
||||||
|
public const string AuthUrlEnv = "AUTH_URL";
|
||||||
|
public const string AuthApiKeyEnv = "AUTH_API_KEY";
|
||||||
|
|
||||||
|
// File paths
|
||||||
|
public const string JwtFileName = "jwt.txt";
|
||||||
|
public const string EmployeesFileName = "employees.json";
|
||||||
|
public const string DataFolderName = "data";
|
||||||
|
}
|
||||||
@@ -44,10 +44,14 @@ public class ScheduleController(ModeusService modeusService, ModeusEmployeeServi
|
|||||||
/// <response code="200">Возвращает список сотрудников с их GUID</response>
|
/// <response code="200">Возвращает список сотрудников с их GUID</response>
|
||||||
/// <response code="404">Сотрудник не найден</response>
|
/// <response code="404">Сотрудник не найден</response>
|
||||||
/// <response code="401">Неавторизованный</response>
|
/// <response code="401">Неавторизованный</response>
|
||||||
|
/// <response code="503">Сервис сотрудников не инициализирован</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Route("searchemployee")]
|
[Route("searchemployee")]
|
||||||
public async Task<IActionResult> SearchEmployees([Required][MinLength(1)] string fullname)
|
public async Task<IActionResult> SearchEmployees([Required][MinLength(1)] string fullname)
|
||||||
{
|
{
|
||||||
|
if (!modeusEmployeeService.IsInitialized())
|
||||||
|
return StatusCode(503, "Сервис сотрудников не инициализирован, попробуйте позже.");
|
||||||
|
|
||||||
var employees = await modeusEmployeeService.GetEmployees(fullname, 10);
|
var employees = await modeusEmployeeService.GetEmployees(fullname, 10);
|
||||||
if (employees.Count == 0)
|
if (employees.Count == 0)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|||||||
78
SfeduSchedule/Jobs/UpdateEmployeesJob.cs
Normal file
78
SfeduSchedule/Jobs/UpdateEmployeesJob.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using Quartz;
|
||||||
|
using SfeduSchedule.Logging;
|
||||||
|
using SfeduSchedule.Services;
|
||||||
|
|
||||||
|
namespace SfeduSchedule.Jobs;
|
||||||
|
|
||||||
|
// TODO: Обновляет список сотрудников из modeus и сохраняет в локальный файл
|
||||||
|
// TODO: Нужно вынести функционал обновления сюда, а в сервисе оставить только загрузку с диска
|
||||||
|
// TODO: Нужно настроить выполнение задачи на часов 10 утра каждый день
|
||||||
|
// TODO: Нужно обработать событие когда данные о сотрудниках запрашиваются до первого обновления
|
||||||
|
|
||||||
|
public class UpdateEmployeesJob(
|
||||||
|
ILogger<UpdateEmployeesJob> logger,
|
||||||
|
ModeusEmployeeService employeeService,
|
||||||
|
ModeusService modeusService) : IJob
|
||||||
|
{
|
||||||
|
private const int MaxAttempts = 5; // Максимальное число попыток
|
||||||
|
private const int DelaySeconds = 50; // Задержка между попытками в секундах
|
||||||
|
private const int TimeoutSeconds = 60; // Таймаут для каждого запроса в секундах
|
||||||
|
|
||||||
|
public async Task Execute(IJobExecutionContext jobContext)
|
||||||
|
{
|
||||||
|
logger.LogInformation("Начало выполнения UpdateEmployeesJob");
|
||||||
|
|
||||||
|
for (var attempt = 1; attempt <= MaxAttempts; attempt++)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
logger.LogInformation("Попытка {Attempt}/{MaxAttempts} получения списка сотрудников из Modeus", attempt,
|
||||||
|
MaxAttempts);
|
||||||
|
|
||||||
|
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(TimeoutSeconds));
|
||||||
|
|
||||||
|
var employees = await modeusService.GetEmployeesAsync(cts.Token);
|
||||||
|
if (employees.Count == 0)
|
||||||
|
{
|
||||||
|
logger.LogWarningHere("Не удалось получить список сотрудников из Modeus.");
|
||||||
|
|
||||||
|
if (attempt == MaxAttempts)
|
||||||
|
{
|
||||||
|
logger.LogError("Достигнуто максимальное число попыток получения JWT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(DelaySeconds), jobContext.CancellationToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await employeeService.SetEmployees(employees);
|
||||||
|
logger.LogInformationHere($"Получено {employees.Count} сотрудников из Modeus.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
logger.LogWarning(ex, "Таймаут при получении JWT (попытка {Attempt}/{MaxAttempts})", attempt,
|
||||||
|
MaxAttempts);
|
||||||
|
|
||||||
|
if (attempt == MaxAttempts)
|
||||||
|
{
|
||||||
|
logger.LogError("Достигнут лимит по таймаутам при запросе JWT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(DelaySeconds), jobContext.CancellationToken);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogErrorHere(ex, "Ошибка при загрузке сотрудников из Modeus.");
|
||||||
|
|
||||||
|
if (attempt == MaxAttempts)
|
||||||
|
{
|
||||||
|
logger.LogError("Достигнуто максимальное число попыток из-за ошибок при запросе JWT");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(DelaySeconds), jobContext.CancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,7 @@ public class UpdateJwtJob(
|
|||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
ILogger<UpdateJwtJob> logger,
|
ILogger<UpdateJwtJob> logger,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
ModeusHttpClient modeusHttpClient,
|
ModeusHttpClient modeusHttpClient) : IJob
|
||||||
ModeusService modeusService) : IJob
|
|
||||||
{
|
{
|
||||||
private const int MaxAttempts = 5; // Максимальное число попыток
|
private const int MaxAttempts = 5; // Максимальное число попыток
|
||||||
private const int DelaySeconds = 20; // Задержка между попытками в секундах
|
private const int DelaySeconds = 20; // Задержка между попытками в секундах
|
||||||
@@ -19,8 +18,8 @@ public class UpdateJwtJob(
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Начало выполнения UpdateJwtJob");
|
logger.LogInformation("Начало выполнения UpdateJwtJob");
|
||||||
|
|
||||||
var authUrl = configuration["AUTH_URL"] ?? "http://msauth:8080/auth/ms";
|
var authUrl = configuration[AppConsts.AuthUrlEnv] ?? "http://msauth:8080/auth/ms";
|
||||||
var apiKey = configuration["AUTH_API_KEY"] ?? string.Empty;
|
var apiKey = configuration[AppConsts.AuthApiKeyEnv] ?? string.Empty;
|
||||||
|
|
||||||
var client = httpClientFactory.CreateClient("authClient");
|
var client = httpClientFactory.CreateClient("authClient");
|
||||||
client.Timeout = TimeSpan.FromSeconds(TimeoutSeconds + 10);
|
client.Timeout = TimeSpan.FromSeconds(TimeoutSeconds + 10);
|
||||||
@@ -72,7 +71,7 @@ public class UpdateJwtJob(
|
|||||||
|
|
||||||
configuration["TOKEN"] = body.Jwt;
|
configuration["TOKEN"] = body.Jwt;
|
||||||
modeusHttpClient.SetToken(body.Jwt);
|
modeusHttpClient.SetToken(body.Jwt);
|
||||||
await File.WriteAllTextAsync(GlobalConsts.JwtFilePath,
|
await File.WriteAllTextAsync(Path.Combine(Path.Combine(AppContext.BaseDirectory, AppConsts.DataFolderName), AppConsts.JwtFileName),
|
||||||
body.Jwt + "\n" + DateTime.Now.ToString("O"), cts.Token);
|
body.Jwt + "\n" + DateTime.Now.ToString("O"), cts.Token);
|
||||||
logger.LogInformation("JWT успешно обновлён");
|
logger.LogInformation("JWT успешно обновлён");
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ using Microsoft.OpenApi.Models;
|
|||||||
using ModeusSchedule.Abstractions;
|
using ModeusSchedule.Abstractions;
|
||||||
using Prometheus;
|
using Prometheus;
|
||||||
using Quartz;
|
using Quartz;
|
||||||
|
using Quartz.Listener;
|
||||||
|
using Quartz.Impl.Matchers;
|
||||||
using SfeduSchedule;
|
using SfeduSchedule;
|
||||||
using SfeduSchedule.Auth;
|
using SfeduSchedule.Auth;
|
||||||
using SfeduSchedule.Jobs;
|
using SfeduSchedule.Jobs;
|
||||||
@@ -24,29 +26,24 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
#region Работа с конфигурацией
|
#region Работа с конфигурацией
|
||||||
|
|
||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
var preinstalledJwtToken = configuration["TOKEN"];
|
var preinstalledJwtToken = configuration[AppConsts.PreinstalledJwtTokenEnv];
|
||||||
var tgChatId = configuration["TG_CHAT_ID"];
|
configuration[AppConsts.ModeusUrlEnv] ??= AppConsts.ModeusDefaultUrl;
|
||||||
var tgToken = configuration["TG_TOKEN"];
|
configuration[AppConsts.UpdateJwtCronEnv] ??= "0 0 4 ? * *";
|
||||||
var updateJwtCron = configuration["UPDATE_JWT_CRON"] ?? "0 0 4 ? * *";
|
configuration[AppConsts.UpdateEmployeeCronEnv] ??= "0 0 6 ? * *";
|
||||||
|
|
||||||
// Если не указана TZ, ставим Europe/Moscow
|
// Если не указана TZ, ставим Europe/Moscow
|
||||||
if (string.IsNullOrEmpty(configuration["TZ"]))
|
configuration["TZ"] ??= "Europe/Moscow";
|
||||||
configuration["TZ"] = "Europe/Moscow";
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(configuration["MODEUS_URL"]))
|
var permitLimit = int.TryParse(configuration[AppConsts.PermitLimitEnv], out var parsedPermitLimit) ? parsedPermitLimit : 40;
|
||||||
configuration["MODEUS_URL"] = "https://sfedu.modeus.org/";
|
var timeLimit = int.TryParse(configuration[AppConsts.TimeLimitEnv], out var parsedTimeLimit) ? parsedTimeLimit : 10;
|
||||||
|
|
||||||
var permitLimit = int.TryParse(configuration["PERMIT_LIMIT"], out var parsedPermitLimit) ? parsedPermitLimit : 40;
|
|
||||||
var timeLimit = int.TryParse(configuration["TIME_LIMIT"], out var parsedTimeLimit) ? parsedTimeLimit : 10;
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Работа с папкой данных
|
#region Работа с папкой данных
|
||||||
// Создать папку data если не существует
|
// Создать папку data если не существует
|
||||||
var dataDirectory = Path.Combine(AppContext.BaseDirectory, "data");
|
var dataDirectory = Path.Combine(AppContext.BaseDirectory, AppConsts.DataFolderName);
|
||||||
if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory);
|
if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory);
|
||||||
|
|
||||||
GlobalConsts.JwtFilePath = Path.Combine(dataDirectory, "jwt.txt");
|
var jwtFilePath = Path.Combine(dataDirectory, AppConsts.JwtFileName);
|
||||||
|
|
||||||
// Создать подкаталог для плагинов
|
// Создать подкаталог для плагинов
|
||||||
var pluginsPath = Path.Combine(dataDirectory, "Plugins");
|
var pluginsPath = Path.Combine(dataDirectory, "Plugins");
|
||||||
@@ -63,7 +60,7 @@ builder.Logging.AddConsole(options => options.FormatterName = "CustomConsoleForm
|
|||||||
.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
|
.AddConsoleFormatter<ConsoleFormatter, ConsoleFormatterOptions>();
|
||||||
|
|
||||||
builder.Logging.AddFilter("Quartz", LogLevel.Warning);
|
builder.Logging.AddFilter("Quartz", LogLevel.Warning);
|
||||||
if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
|
if (!string.IsNullOrEmpty(configuration[AppConsts.TgChatIdEnv]) && !string.IsNullOrEmpty(configuration[AppConsts.TgTokenEnv]))
|
||||||
builder.Logging.AddTelegram(options =>
|
builder.Logging.AddTelegram(options =>
|
||||||
{
|
{
|
||||||
options.FormatterConfiguration = new X.Extensions.Logging.Telegram.Base.Configuration.FormatterConfiguration
|
options.FormatterConfiguration = new X.Extensions.Logging.Telegram.Base.Configuration.FormatterConfiguration
|
||||||
@@ -71,8 +68,8 @@ if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
|
|||||||
IncludeException = true,
|
IncludeException = true,
|
||||||
IncludeProperties = true,
|
IncludeProperties = true,
|
||||||
};
|
};
|
||||||
options.ChatId = tgChatId;
|
options.ChatId = configuration[AppConsts.TgChatIdEnv]!;
|
||||||
options.AccessToken = tgToken;
|
options.AccessToken = configuration[AppConsts.TgTokenEnv]!;
|
||||||
options.FormatterConfiguration.UseEmoji = true;
|
options.FormatterConfiguration.UseEmoji = true;
|
||||||
options.FormatterConfiguration.ReadableApplicationName = "Modeus Schedule Proxy";
|
options.FormatterConfiguration.ReadableApplicationName = "Modeus Schedule Proxy";
|
||||||
options.LogLevel = new Dictionary<string, LogLevel>
|
options.LogLevel = new Dictionary<string, LogLevel>
|
||||||
@@ -149,23 +146,35 @@ foreach (var p in loadedPlugins)
|
|||||||
mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(p.Assembly));
|
mvcBuilder.PartManager.ApplicationParts.Add(new AssemblyPart(p.Assembly));
|
||||||
}
|
}
|
||||||
|
|
||||||
var jobKey = new JobKey("UpdateJWTJob");
|
var updateJwtJob = new JobKey("UpdateJWTJob");
|
||||||
|
|
||||||
|
builder.Services.AddQuartz(q =>
|
||||||
|
{
|
||||||
|
q.AddJob<UpdateJwtJob>(opts => opts.WithIdentity(updateJwtJob));
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(preinstalledJwtToken))
|
if (string.IsNullOrEmpty(preinstalledJwtToken))
|
||||||
{
|
{
|
||||||
builder.Services.AddQuartz(q =>
|
|
||||||
{
|
|
||||||
q.AddJob<UpdateJwtJob>(opts => opts.WithIdentity(jobKey));
|
|
||||||
|
|
||||||
q.AddTrigger(opts => opts
|
q.AddTrigger(opts => opts
|
||||||
.ForJob(jobKey)
|
.ForJob(updateJwtJob)
|
||||||
.WithIdentity("UpdateJWTJob-trigger")
|
.WithIdentity("UpdateJWTJob-trigger")
|
||||||
.WithCronSchedule(updateJwtCron)
|
.WithCronSchedule(configuration["UPDATE_JWT_CRON"]!)
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
q.AddJob<UpdateEmployeesJob>(opts => opts.WithIdentity(nameof(UpdateEmployeesJob)));
|
||||||
|
q.AddTrigger(opts => opts
|
||||||
|
.ForJob(nameof(UpdateEmployeesJob))
|
||||||
|
.WithIdentity("UpdateEmployeesJob-trigger")
|
||||||
|
.WithCronSchedule(configuration[AppConsts.UpdateEmployeeCronEnv]!)
|
||||||
|
);
|
||||||
|
|
||||||
|
// после успешного выполнения UpdateJwtJob сразу выполняем UpdateEmployeesJob
|
||||||
|
var chainListener = new JobChainingJobListener("chain");
|
||||||
|
chainListener.AddJobChainLink(updateJwtJob, new JobKey(nameof(UpdateEmployeesJob)));
|
||||||
|
q.AddJobListener(chainListener, GroupMatcher<JobKey>.GroupEquals(JobKey.DefaultGroup));
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = false);
|
||||||
}
|
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
builder.Services.AddSwaggerGen(options =>
|
builder.Services.AddSwaggerGen(options =>
|
||||||
@@ -258,16 +267,25 @@ app.UseForwardedHeaders();
|
|||||||
// Корреляция логов по запросам
|
// Корреляция логов по запросам
|
||||||
app.UseMiddleware<CorrelationIdMiddleware>();
|
app.UseMiddleware<CorrelationIdMiddleware>();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(preinstalledJwtToken))
|
|
||||||
{
|
|
||||||
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
|
||||||
var scheduler = await schedulerFactory.GetScheduler();
|
var scheduler = await schedulerFactory.GetScheduler();
|
||||||
|
|
||||||
|
var refreshJwt = true;
|
||||||
|
|
||||||
|
// Если есть предустановленный токен, используем его
|
||||||
|
if (!string.IsNullOrEmpty(preinstalledJwtToken))
|
||||||
|
{
|
||||||
|
logger.LogInformation("Используем предустановленный токен из конфигурации");
|
||||||
|
configuration["TOKEN"] = preinstalledJwtToken;
|
||||||
|
refreshJwt = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Проверить существование файла jwt.txt
|
// Проверить существование файла jwt.txt
|
||||||
if (File.Exists(GlobalConsts.JwtFilePath))
|
if (File.Exists(jwtFilePath) && refreshJwt)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Обнаружена прошлая сессия");
|
logger.LogInformation("Обнаружена прошлая сессия");
|
||||||
var lines = await File.ReadAllLinesAsync(GlobalConsts.JwtFilePath);
|
var lines = await File.ReadAllLinesAsync(jwtFilePath);
|
||||||
if (lines.Length > 1 && DateTime.TryParse(lines[1], out var expirationDate))
|
if (lines.Length > 1 && DateTime.TryParse(lines[1], out var expirationDate))
|
||||||
{
|
{
|
||||||
logger.LogInformation("Дата истечения токена: {ExpirationDate}", expirationDate);
|
logger.LogInformation("Дата истечения токена: {ExpirationDate}", expirationDate);
|
||||||
@@ -276,27 +294,21 @@ if (string.IsNullOrEmpty(preinstalledJwtToken))
|
|||||||
var token = lines[0];
|
var token = lines[0];
|
||||||
logger.LogInformation("Используем существующий токен");
|
logger.LogInformation("Используем существующий токен");
|
||||||
configuration["TOKEN"] = token;
|
configuration["TOKEN"] = token;
|
||||||
|
refreshJwt = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
logger.LogInformation("Токен истек или скоро истечет, выполняем обновление токена");
|
logger.LogInformation("Токен истек или скоро истечет, выполняем обновление токена");
|
||||||
await scheduler.TriggerJob(jobKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
logger.LogInformation(
|
logger.LogInformation(
|
||||||
"Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена");
|
"Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена");
|
||||||
await scheduler.TriggerJob(jobKey);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновить JWT если нужно, либо сразу запустить обновление сотрудников
|
||||||
|
if (refreshJwt)
|
||||||
|
await scheduler.TriggerJob(updateJwtJob);
|
||||||
else
|
else
|
||||||
{
|
await scheduler.TriggerJob(new JobKey(nameof(UpdateEmployeesJob)));
|
||||||
await scheduler.TriggerJob(jobKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
logger.LogInformation("Используем предустановленный токен из конфигурации");
|
|
||||||
|
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
using SfeduSchedule.Logging;
|
using Quartz;
|
||||||
|
using SfeduSchedule.Jobs;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace SfeduSchedule.Services;
|
namespace SfeduSchedule.Services;
|
||||||
|
|
||||||
public class ModeusEmployeeService(ILogger<ModeusEmployeeService> logger, ModeusService modeusService)
|
public class ModeusEmployeeService(ISchedulerFactory schedulerFactory)
|
||||||
: IHostedService
|
: IHostedService
|
||||||
{
|
{
|
||||||
private Dictionary<string, (string, List<string>)> _employees = [];
|
private Dictionary<string, (string, List<string>)> _employees = [];
|
||||||
private Task? _backgroundTask;
|
private Task? _backgroundTask;
|
||||||
private CancellationTokenSource? _cts;
|
private CancellationTokenSource? _cts;
|
||||||
|
private readonly string _employeesFilePath = Path.Combine(Path.Combine(AppContext.BaseDirectory, AppConsts.DataFolderName), AppConsts.EmployeesFileName);
|
||||||
|
|
||||||
public async Task<Dictionary<string, (string, List<string>)>> GetEmployees(string fullname, int size = 10)
|
public async Task<Dictionary<string, (string, List<string>)>> GetEmployees(string fullname, int size = 10)
|
||||||
{
|
{
|
||||||
@@ -17,34 +20,18 @@ public class ModeusEmployeeService(ILogger<ModeusEmployeeService> logger, Modeus
|
|||||||
.ToDictionary(e => e.Key, e => e.Value);
|
.ToDictionary(e => e.Key, e => e.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsInitialized()
|
||||||
|
{
|
||||||
|
return _employees.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
_backgroundTask = Task.Run(async () =>
|
_backgroundTask = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
// Загрузка с диска
|
||||||
{
|
await LoadEmployeesFromDisk();
|
||||||
await Task.Delay(TimeSpan.FromSeconds(15), _cts.Token);
|
|
||||||
|
|
||||||
var employees = await modeusService.GetEmployeesAsync();
|
|
||||||
if (employees.Count == 0)
|
|
||||||
{
|
|
||||||
logger.LogWarningHere("Не удалось получить список сотрудников из Modeus.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_employees = employees;
|
|
||||||
logger.LogInformationHere($"Получено {employees.Count} сотрудников из Modeus.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogErrorHere(ex, "Ошибка при загрузке сотрудников из Modeus.");
|
|
||||||
}
|
|
||||||
}, _cts.Token);
|
}, _cts.Token);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -68,4 +55,26 @@ public class ModeusEmployeeService(ILogger<ModeusEmployeeService> logger, Modeus
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task SetEmployees(Dictionary<string, (string, List<string>)> employees)
|
||||||
|
{
|
||||||
|
_employees = employees;
|
||||||
|
await SaveEmployeesToDisk();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadEmployeesFromDisk()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (File.Exists(_employeesFilePath))
|
||||||
|
{
|
||||||
|
var json = await File.ReadAllTextAsync(_employeesFilePath);
|
||||||
|
_employees = JsonSerializer.Deserialize<Dictionary<string, (string, List<string>)>>(json) ?? new Dictionary<string, (string, List<string>)>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveEmployeesToDisk()
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(_employees, new JsonSerializerOptions { WriteIndented = false });
|
||||||
|
await File.WriteAllTextAsync(_employeesFilePath, json);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -75,13 +75,13 @@ public class ModeusHttpClient
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ModeusSearchPersonResponse?> SearchPersonAsync(ModeusSearchPersonRequest modeusSearchPersonRequest)
|
public async Task<ModeusSearchPersonResponse?> SearchPersonAsync(ModeusSearchPersonRequest modeusSearchPersonRequest, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Post,
|
using var request = new HttpRequestMessage(HttpMethod.Post,
|
||||||
$"schedule-calendar-v2/api/people/persons/search");
|
$"schedule-calendar-v2/api/people/persons/search");
|
||||||
request.Content = JsonContent.Create(modeusSearchPersonRequest, options: GlobalConsts.JsonSerializerOptions);
|
request.Content = JsonContent.Create(modeusSearchPersonRequest, options: GlobalConsts.JsonSerializerOptions);
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||||
var requestMs = stopwatch.ElapsedMilliseconds;
|
var requestMs = stopwatch.ElapsedMilliseconds;
|
||||||
if (response.StatusCode != System.Net.HttpStatusCode.OK)
|
if (response.StatusCode != System.Net.HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
@@ -95,7 +95,7 @@ public class ModeusHttpClient
|
|||||||
await using var contentStream = await response.Content.ReadAsStreamAsync();
|
await using var contentStream = await response.Content.ReadAsStreamAsync();
|
||||||
var content = await JsonSerializer.DeserializeAsync<ModeusSearchPersonResponse>(
|
var content = await JsonSerializer.DeserializeAsync<ModeusSearchPersonResponse>(
|
||||||
contentStream,
|
contentStream,
|
||||||
GlobalConsts.JsonSerializerOptions);
|
GlobalConsts.JsonSerializerOptions, cancellationToken);
|
||||||
var groupMs = stopwatch.ElapsedMilliseconds - deserializeStartMs;
|
var groupMs = stopwatch.ElapsedMilliseconds - deserializeStartMs;
|
||||||
|
|
||||||
_logger.LogInformationHere($"SearchPersonAsync: Request time: {requestMs} ms, Deserialization time: {groupMs} ms, Total time: {stopwatch.ElapsedMilliseconds} ms.");
|
_logger.LogInformationHere($"SearchPersonAsync: Request time: {requestMs} ms, Deserialization time: {groupMs} ms, Total time: {stopwatch.ElapsedMilliseconds} ms.");
|
||||||
|
|||||||
@@ -186,10 +186,10 @@ public class ModeusService(
|
|||||||
return serializedCalendar;
|
return serializedCalendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Dictionary<string, (string, List<string>)>> GetEmployeesAsync()
|
public async Task<Dictionary<string, (string, List<string>)>> GetEmployeesAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var searchPersonResponse =
|
var searchPersonResponse =
|
||||||
await modeusHttpClient.SearchPersonAsync(new ModeusSearchPersonRequest() { Size = 38000 });
|
await modeusHttpClient.SearchPersonAsync(new ModeusSearchPersonRequest() { Size = 38000 }, cancellationToken);
|
||||||
if (searchPersonResponse == null)
|
if (searchPersonResponse == null)
|
||||||
{
|
{
|
||||||
logger.LogErrorHere("persons is null");
|
logger.LogErrorHere("persons is null");
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
@SfeduSchedule_HostAddress = http://localhost:5087
|
|
||||||
|
|
||||||
###
|
|
||||||
[Получить расписание по списку GUID]
|
|
||||||
GET {{SfeduSchedule_HostAddress}}/api/schedule?attendeePersonId={{guid1}}&attendeePersonId={{guid2}}
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
###
|
|
||||||
[Получить расписание через POST]
|
|
||||||
POST {{SfeduSchedule_HostAddress}}/api/schedule
|
|
||||||
Content-Type: application/json
|
|
||||||
Accept: application/json
|
|
||||||
|
|
||||||
{
|
|
||||||
"maxResults": 500,
|
|
||||||
"startDate": "2025-08-31T00:00:00Z",
|
|
||||||
"endDate": "2025-09-20T00:00:00Z",
|
|
||||||
"attendeePersonId": ["{{guid1}}", "{{guid2}}"]
|
|
||||||
}
|
|
||||||
|
|
||||||
###
|
|
||||||
Reference in New Issue
Block a user