Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
28984db
Добавлена генерация контрактов с использованием Bogus, подключен Redi…
Tomashaytis Feb 21, 2026
4a4d2fb
Добавлен контроллер и кеширование с использованием Redis.
Tomashaytis Feb 21, 2026
6bd53d5
Добавлен WEB-интерфейс для redis, summaries и защита от недоступности…
Tomashaytis Feb 22, 2026
0f29d84
Добавлены сервис для сущности Курс и сервис для взаимодействия с кэшем
Tomashaytis Feb 22, 2026
f0bfce4
Скорректированы поля сущности Курс согласно варианту.
Tomashaytis Feb 22, 2026
9cd62ce
Update README.md
Tomashaytis Feb 22, 2026
451c4f9
Подправлено структурное логирование
Tomashaytis Feb 22, 2026
0fa0334
Исправил замечания
Tomashaytis Feb 24, 2026
55a48f7
Добавлен Api Gateway Ocelot, добавлены 3 реплики сервиса генерации да…
Tomashaytis Mar 6, 2026
6d642cf
Добавлен Query Based балансировщик нагрузки
Tomashaytis Mar 7, 2026
ffaa9c9
Добавлено создание реплик в цикле, summary к балансировщику нагрузки
Tomashaytis Mar 7, 2026
2393615
Исправлена ошибка, из-за которой ApiGateway не использовался; добавле…
Tomashaytis Mar 7, 2026
634ca0c
Update README.md
Tomashaytis Mar 7, 2026
7b673a5
Изменена структура проекта
Tomashaytis Mar 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Client.Wasm/Client.Wasm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
<None Remove="Components\StudentCard.razor~RF1bb17a4.TMP" />
</ItemGroup>

<ItemGroup>
<None Include="Client.Wasm.csproj.user" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Blazorise" Version="1.8.8" />
<PackageReference Include="Blazorise.Bootstrap" Version="1.8.8" />
Expand Down
8 changes: 4 additions & 4 deletions Client.Wasm/Components/StudentCard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
</CardHeader>
<CardBody>
<UnorderedList Unstyled>
<UnorderedListItem>Номер <Strong>№X "Название лабораторной"</Strong></UnorderedListItem>
<UnorderedListItem>Вариант <Strong>№Х "Название варианта"</Strong></UnorderedListItem>
<UnorderedListItem>Выполнена <Strong>Фамилией Именем 65ХХ</Strong> </UnorderedListItem>
<UnorderedListItem><Link To="https://puginarug.com/">Ссылка на форк</Link></UnorderedListItem>
<UnorderedListItem>Номер <Strong>№ 2</Strong></UnorderedListItem>
<UnorderedListItem>Вариант <Strong>№ 52</Strong></UnorderedListItem>
<UnorderedListItem>Выполнена <Strong>Томашайтисом Павлом 6513</Strong> </UnorderedListItem>
<UnorderedListItem><Link To="https://github.com/Tomashaytis/cloud-development">Ссылка на форк</Link></UnorderedListItem>
</UnorderedList>
</CardBody>
</Card>
2 changes: 1 addition & 1 deletion Client.Wasm/wwwroot/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
}
},
"AllowedHosts": "*",
"BaseAddress": "https://localhost:7170/land-plot"
"BaseAddress": "https://localhost:5000/course-management"
}
25 changes: 0 additions & 25 deletions CloudDevelopment.sln

This file was deleted.

18 changes: 18 additions & 0 deletions CourseManagement.ApiGateway/CourseManagement.ApiGateway.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.2" />
<PackageReference Include="Ocelot" Version="24.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CourseManagement\CourseManagement.ServiceDefaults\CourseManagement.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
44 changes: 44 additions & 0 deletions CourseManagement.ApiGateway/LoadBalancers/QueryBased.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Ocelot.LoadBalancer.Interfaces;
using Ocelot.Responses;
using Ocelot.Values;

namespace CourseManagement.ApiGateway.LoadBalancers;

/// <summary>
/// Балансировщик нагрузки на основе запроса
/// </summary>
/// <param name="logger">Логгер</param>
/// <param name="services">Список сервисов</param>
public class QueryBased(ILogger<QueryBased> logger, List<Service> services) : ILoadBalancer
{
/// <summary>
/// Тип балансировщика нагрузки
/// </summary>
public string Type => "QueryBased";

/// <summary>
/// Метод выбора сервиса на основе запроса
/// </summary>
/// <param name="httpContext">HTTP запрос</param>
/// <returns>Выбранный сервис</returns>
public Task<Response<ServiceHostAndPort>> LeaseAsync(HttpContext httpContext)
{
var idString = httpContext.Request.Query["id"].FirstOrDefault();

if (!int.TryParse(idString, out var id))
id = 0;

var service = services[id % services.Count];

logger.LogInformation("Request {ResourceId} sent to service on port {servicePort}", id, service.HostAndPort.DownstreamPort);

return Task.FromResult<Response<ServiceHostAndPort>>(
new OkResponse<ServiceHostAndPort>(service.HostAndPort));
}

/// <summary>
/// Метод очистки ресурсов для сервиса
/// </summary>
/// <param name="hostAndPort">Параметры сервиса</param>
public void Release(ServiceHostAndPort hostAndPort) { }
}
40 changes: 40 additions & 0 deletions CourseManagement.ApiGateway/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using CourseManagement.ApiGateway.LoadBalancers;
using Ocelot.DependencyInjection;
using Ocelot.Middleware;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// Подключение конфига Ocelot
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);

// Добавление Ocelot с Query Based балансировщиком нагрузки
builder.Services.AddOcelot()
.AddCustomLoadBalancer<QueryBased>((serviceProvider, downstreamRoute, serviceDiscoveryProvider) =>
{
var logger = serviceProvider.GetRequiredService<ILogger<QueryBased>>();

var services = serviceDiscoveryProvider.GetAsync().GetAwaiter().GetResult().ToList();

return new QueryBased(logger, services);
});

// CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowClient", policy =>
{
policy.AllowAnyOrigin()
.WithMethods("GET")
.WithHeaders("Content-Type");
});
});

var app = builder.Build();

app.UseCors("AllowClient");

await app.UseOcelot();

app.Run();
23 changes: 23 additions & 0 deletions CourseManagement.ApiGateway/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5256",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7253;http://localhost:5256",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
8 changes: 8 additions & 0 deletions CourseManagement.ApiGateway/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions CourseManagement.ApiGateway/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
56 changes: 56 additions & 0 deletions CourseManagement.ApiGateway/ocelot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
{
"Routes": [
{
"DownstreamPathTemplate": "/course-management",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8081
},
{
"Host": "localhost",
"Port": 8082
},
{
"Host": "localhost",
"Port": 8083
}
],
"UpstreamPathTemplate": "/course-management",
"UpstreamHttpMethod": [ "GET" ],
"LoadBalancerOptions": {
"Type": "QueryBased",
"Key": "course-api"
}
},
{
"DownstreamPathTemplate": "/health",
"DownstreamScheme": "https",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 8081
},
{
"Host": "localhost",
"Port": 8082
},
{
"Host": "localhost",
"Port": 8083
}
],
"UpstreamPathTemplate": "/health",
"UpstreamHttpMethod": [ "GET" ],
"Priority": 1,
"LoadBalancerOptions": {
"Type": "QueryBased",
"Key": "course-api-health"
}
}
],
"GlobalConfiguration": {
"BaseUrl": "https://localhost:5000"
}
}
30 changes: 30 additions & 0 deletions CourseManagement.ApiService/Controllers/CourseController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;
using CourseManagement.ApiService.Dto;
using CourseManagement.ApiService.Services;

namespace CourseManagement.ApiService.Controllers;

/// <summary>
/// Контроллер для сущности типа Курс
/// </summary>
/// <param name="logger">Логгер</param>
/// <param name="courseService">Сервис для сущности типа Курс</param>
[ApiController]
[Route("course-management")]
public class CourseController(ILogger<CourseController> logger, CourseService courseService) : ControllerBase
{
/// <summary>
/// Обработчик GET-запроса на генерацию курса
/// </summary>
/// <param name="id">Идентификатор курса</param>
/// <returns>Сгенерированный курс</returns>
[HttpGet]
public async Task<ActionResult<CourseDto>> GetCourse(int? id)
{
logger.LogInformation("Processing request for course {ResourceId}", id);

var course = await courseService.GetCourse(id ?? 0);

return course != null ? Ok(course) : Problem("Internal server error", statusCode: 500);
}
}
21 changes: 21 additions & 0 deletions CourseManagement.ApiService/CourseManagement.ApiService.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.StackExchange.Redis" Version="13.1.2" />
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="13.1.2" />
<PackageReference Include="Bogus" Version="35.6.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="10.0.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\CourseManagement.ServiceDefaults\CourseManagement.ServiceDefaults.csproj" />
</ItemGroup>

</Project>
70 changes: 70 additions & 0 deletions CourseManagement.ApiService/Dto/CourseDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Text.Json.Serialization;

namespace CourseManagement.ApiService.Dto;


/// <summary>
/// DTO для сущности типа курс
/// </summary>
public class CourseDto
{
/// <summary>
/// Идентификатор курса
/// </summary>
[JsonPropertyName("id")]
public int Id { get; set; }

/// <summary>
/// Название курса
/// </summary>
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;

/// <summary>
/// Лектор
/// </summary>
[JsonPropertyName("lector")]
public string Lector { get; set; } = string.Empty;

/// <summary>
/// Дата начала курса
/// </summary>
[JsonPropertyName("startDate")]
public DateOnly StartDate { get; set; }

/// <summary>
/// Дата окончания курса
/// </summary>
[JsonPropertyName("endDate")]
public DateOnly EndDate { get; set; }

/// <summary>
/// Максимальное число студентов для курса
/// </summary>
[JsonPropertyName("maxStudents")]
public int MaxStudents { get; set; }

/// <summary>
/// Текущее число студентов курса
/// </summary>
[JsonPropertyName("enrolledStudents")]
public int EnrolledStudents { get; set; }

/// <summary>
/// Выдача сертификата
/// </summary>
[JsonPropertyName("hasSertificate")]
public bool HasSertificate { get; set; }

/// <summary>
/// Стоимость курса
/// </summary>
[JsonPropertyName("price")]
public decimal Price { get; set; }

/// <summary>
/// Рейтинг
/// </summary>
[JsonPropertyName("rating")]
public int Rating { get; set; }
}
Loading