Compare commits

..

2 Commits

Author SHA1 Message Date
ea254c1c02 Добавил RateLimiter
All checks were successful
Create and publish a Docker image / Publish image (push) Successful in 11m22s
2025-09-06 22:07:37 +03:00
5f52e16dbd Добавил логирование в телеграм 2025-09-06 21:04:59 +03:00
5 changed files with 72 additions and 14 deletions

View File

@@ -1,10 +1,12 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using SfeduSchedule.Services;
namespace SfeduSchedule.Controllers
{
[ApiController]
[Route("api/[controller]")]
[EnableRateLimiting("throttle")]
public class ScheduleController(ModeusService modeusService) : ControllerBase
{
/// <summary>
@@ -13,6 +15,7 @@ namespace SfeduSchedule.Controllers
/// <param name="attendeePersonId">Список GUID пользователей, для которых запрашивается расписание.</param>
/// <returns>Список событий расписания.</returns>
/// <response code="200">Возвращает расписание</response>
/// <response code="429">Слишком много запросов</response>
[HttpGet]
[Route("test")]
public async Task<IActionResult> Get([FromQuery] List<Guid> attendeePersonId, [FromQuery] DateTime? startDate, [FromQuery] DateTime? endDate)
@@ -31,6 +34,7 @@ namespace SfeduSchedule.Controllers
/// <param name="request">Объект запроса, содержащий параметры фильтрации расписания.</param>
/// <returns>Список событий расписания.</returns>
/// <response code="200">Возвращает расписание</response>
/// <response code="429">Слишком много запросов</response>
[HttpPost]
public async Task<IActionResult> Post([FromBody] ModeusScheduleRequest request)
{

View File

@@ -1,13 +1,17 @@
using System.Threading.RateLimiting;
using Microsoft.Identity.Web;
using Quartz;
using SfeduSchedule;
using SfeduSchedule.Jobs;
using SfeduSchedule.Services;
using X.Extensions.Logging.Telegram.Extensions;
var builder = WebApplication.CreateBuilder(args);
var configuration = builder.Configuration;
string? preinstsalledJwtToken = configuration["TOKEN"];
string? preinstalledJwtToken = configuration["TOKEN"];
string? tgChatId = configuration["TG_CHAT_ID"];
string? tgToken = configuration["TG_TOKEN"];
string updateJwtCron = configuration["UPDATE_JWT_CRON"] ?? "0 4 * ? * *";
// создать папку data если не существует
@@ -16,9 +20,27 @@ if (!Directory.Exists(dataDirectory))
{
Directory.CreateDirectory(dataDirectory);
}
GlobalVariables.JwtFilePath = Path.Combine(dataDirectory, "jwt.txt");
builder.Services.AddOpenApi();
builder.Logging.ClearProviders();
builder.Logging.AddConsole();
if (!string.IsNullOrEmpty(tgChatId) && !string.IsNullOrEmpty(tgToken))
builder.Logging.AddTelegram(options =>
{
options.ChatId = tgChatId;
options.AccessToken = tgToken;
options.FormatterConfiguration.UseEmoji = true;
options.FormatterConfiguration.ReadableApplicationName = "Sfedu Schedule";
options.LogLevel = new Dictionary<string, LogLevel>
{
{ "Default", LogLevel.Error },
{ "SfeduSchedule.Jobs.UpdateJwtJob", LogLevel.Information },
{ "Program", LogLevel.Information }
};
});
builder.Services.AddControllers();
builder.Services.AddHttpClient<ModeusService>();
@@ -26,7 +48,7 @@ builder.Services.AddMicrosoftIdentityWebAppAuthentication(builder.Configuration)
var jobKey = new JobKey("UpdateJWTJob");
if (string.IsNullOrEmpty(preinstsalledJwtToken))
if (string.IsNullOrEmpty(preinstalledJwtToken))
{
builder.Services.AddQuartz(q =>
{
@@ -38,7 +60,7 @@ if (string.IsNullOrEmpty(preinstsalledJwtToken))
.WithCronSchedule(updateJwtCron)
);
});
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
}
@@ -50,13 +72,38 @@ builder.Services.AddSwaggerGen(options =>
options.IncludeXmlComments(xmlPath);
});
var app = builder.Build();
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("throttle", httpContext =>
RateLimitPartition.GetFixedWindowLimiter(
partitionKey: httpContext.Connection.RemoteIpAddress?.ToString() ?? "unknown",
factory: _ => new FixedWindowRateLimiterOptions
{
PermitLimit = 20,
Window = TimeSpan.FromSeconds(10)
}));
app.UseForwardedHeaders();
options.OnRejected = async (context, cancellationToken) =>
{
context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
context.HttpContext.Response.Headers["Retry-After"] = "60";
await context.HttpContext.Response.WriteAsync("Rate limit exceeded. Please try again later.",
cancellationToken);
var reqLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<Program>>();
reqLogger.LogWarning("Rate limit exceeded for IP: {IpAddress}",
context.HttpContext.Connection.RemoteIpAddress);
};
});
var app = builder.Build();
var logger = app.Services.GetRequiredService<ILogger<Program>>();
if (string.IsNullOrEmpty(preinstsalledJwtToken))
app.UseForwardedHeaders();
if (string.IsNullOrEmpty(preinstalledJwtToken))
{
var schedulerFactory = app.Services.GetRequiredService<ISchedulerFactory>();
var scheduler = await schedulerFactory.GetScheduler();
@@ -83,7 +130,8 @@ if (string.IsNullOrEmpty(preinstsalledJwtToken))
}
else
{
logger.LogInformation("Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена");
logger.LogInformation(
"Файл jwt.txt не содержит дату истечения или она некорректна, выполняем обновление токена");
await scheduler.TriggerJob(jobKey);
}
}
@@ -106,10 +154,11 @@ app.UseStaticFiles();
app.MapGet("/", async context =>
{
context.Response.ContentType = "text/html; charset=utf-8";
await context.Response.SendFileAsync(Path.Combine(app.Environment.WebRootPath ?? "wwwroot", "index.html"));
await context.Response.SendFileAsync(Path.Combine(app.Environment.WebRootPath, "index.html"));
});
app.MapControllers();
app.Run();
app.UseRateLimiter();
app.Run();

View File

@@ -15,6 +15,7 @@
<PackageReference Include="Microsoft.Playwright" Version="1.55.0" />
<PackageReference Include="Quartz.AspNetCore" Version="3.15.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.4" />
<PackageReference Include="X.Extensions.Logging.Telegram" Version="2.0.2" />
</ItemGroup>
</Project>

View File

@@ -12,7 +12,9 @@ services:
- AzureAd:CallbackPath=/signin-oidc
- MS_USERNAME=${MS_USERNAME}
- MS_PASSWORD=${MS_PASSWORD}
# - TOKEN=
- TG_CHAT_ID=${TG_CHAT_ID}
- TG_TOKEN=${TG_TOKEN}
# - TOKEN=${TOKEN}
volumes:
- data:/app/data
restart: always

View File

@@ -10,9 +10,11 @@ services:
- AzureAd:ClientSecret=
- AzureAd:Domain=sfedu.onmicrosoft.com
- AzureAd:CallbackPath=/signin-oidc
- MS_USERNAME=
- MS_PASSWORD=
# - TOKEN=
- MS_USERNAME=${MS_USERNAME}
- MS_PASSWORD=${MS_PASSWORD}
- TG_CHAT_ID=${TG_CHAT_ID}
- TG_TOKEN=${TG_TOKEN}
# - TOKEN=${TOKEN}
volumes:
- ./data:/app/data
restart: unless-stopped