Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
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>№23 "Товар на складе"</Strong></UnorderedListItem>
<UnorderedListItem>Выполнена <Strong>Карповым Владимиром 6512</Strong> </UnorderedListItem>
<UnorderedListItem><Link To="https://github.com/Vouchiko/cloud-development">Ссылка на форк</Link></UnorderedListItem>
</UnorderedList>
</CardBody>
</Card>
2 changes: 1 addition & 1 deletion Client.Wasm/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
@using Microsoft.JSInterop
@using Client.Wasm.Layout
@using Client.Wasm.Components
@using Blazorise
@using Blazorise
@using Blazorise.Components
@using System.Text.Json
@using System.Text.Json.Nodes
18 changes: 9 additions & 9 deletions Client.Wasm/wwwroot/appsettings.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"BaseAddress": ""
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"BaseAddress": "http://localhost:5300/warehouse-item"
Comment on lines +2 to +9
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Раньше отступы лучше выглядели

}
26 changes: 26 additions & 0 deletions CloudDevelopment.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,40 @@ VisualStudioVersion = 17.14.36811.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Wasm", "Client.Wasm\Client.Wasm.csproj", "{AE7EEA74-2FE0-136F-D797-854FD87E022A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarehouseItem.ServiceDefaults", "WarehouseItem.ServiceDefaults\WarehouseItem.ServiceDefaults.csproj", "{97B30C3C-3125-4E99-BA67-240DD8126A25}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarehouseItem.Generator", "WarehouseItem.Generator\WarehouseItem.Generator.csproj", "{A9B4BB9B-9F03-4C1F-AB67-9DAD2E4D66BA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarehouseItem.AppHost", "WarehouseItem.AppHost\WarehouseItem.AppHost.csproj", "{07AFB6CB-7359-432D-BF0B-14BA7C582AA5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WarehouseItem.Gateway", "WarehouseItem.Gateway\WarehouseItem.Gateway.csproj", "{D0EFD087-0103-4ACE-AD32-F8A68EDE1988}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D0EFD087-0103-4ACE-AD32-F8A68EDE1988}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D0EFD087-0103-4ACE-AD32-F8A68EDE1988}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D0EFD087-0103-4ACE-AD32-F8A68EDE1988}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D0EFD087-0103-4ACE-AD32-F8A68EDE1988}.Release|Any CPU.Build.0 = Release|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|Any CPU.Build.0 = Release|Any CPU
{97B30C3C-3125-4E99-BA67-240DD8126A25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{97B30C3C-3125-4E99-BA67-240DD8126A25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{97B30C3C-3125-4E99-BA67-240DD8126A25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{97B30C3C-3125-4E99-BA67-240DD8126A25}.Release|Any CPU.Build.0 = Release|Any CPU
{A9B4BB9B-9F03-4C1F-AB67-9DAD2E4D66BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A9B4BB9B-9F03-4C1F-AB67-9DAD2E4D66BA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A9B4BB9B-9F03-4C1F-AB67-9DAD2E4D66BA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A9B4BB9B-9F03-4C1F-AB67-9DAD2E4D66BA}.Release|Any CPU.Build.0 = Release|Any CPU
{07AFB6CB-7359-432D-BF0B-14BA7C582AA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{07AFB6CB-7359-432D-BF0B-14BA7C582AA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{07AFB6CB-7359-432D-BF0B-14BA7C582AA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{07AFB6CB-7359-432D-BF0B-14BA7C582AA5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -23,3 +47,5 @@ Global
SolutionGuid = {90FE6B04-8381-437E-893A-FEBA1DA10AEE}
EndGlobalSection
EndGlobal


28 changes: 28 additions & 0 deletions WarehouseItem.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("warehouse-item-cache")
.WithRedisInsight(containerName: "warehouse-item-insight");

var gateway = builder.AddProject<Projects.WarehouseItem_Gateway>("gateway")
.WithHttpEndpoint(name: "http", port: 5300)
.WithExternalHttpEndpoints();

const int generatorPortBase = 5200;
for (var i = 1; i <= 5; ++i)
{
var generator = builder.AddProject<Projects.WarehouseItem_Generator>($"generator-{i}")
.WithReference(cache, "warehouse-item-cache")
.WithHttpEndpoint(name: "http", port: generatorPortBase + i)
.WaitFor(cache);

gateway.WaitFor(generator);
}

builder.AddProject<Projects.Client_Wasm>("client")
.WithReference(gateway)
.WaitFor(gateway);

builder.Build().Run();



Comment on lines +26 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Лишние строки

29 changes: 29 additions & 0 deletions WarehouseItem.AppHost/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17129;http://localhost:15221",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21101",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22255"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15221",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19083",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20274"
}
}
}
}
19 changes: 19 additions & 0 deletions WarehouseItem.AppHost/WarehouseItem.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Sdk Name="Aspire.AppHost.Sdk" Version="9.5.2" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>ed7e1e47-dc98-4419-8424-85412466aa9b</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.5.2" />
<PackageReference Include="Aspire.Hosting.Redis" Version="9.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\WarehouseItem.Generator\WarehouseItem.Generator.csproj" />
<ProjectReference Include="..\Client.Wasm\Client.Wasm.csproj" />
<ProjectReference Include="..\WarehouseItem.Gateway\WarehouseItem.Gateway.csproj" />
</ItemGroup>
</Project>
8 changes: 8 additions & 0 deletions WarehouseItem.AppHost/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 WarehouseItem.AppHost/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
65 changes: 65 additions & 0 deletions WarehouseItem.Gateway/LoadBalancer/QueryBasedLoadBalancer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using Ocelot.LoadBalancer.Interfaces;
using Ocelot.Responses;
using Ocelot.Values;

namespace WarehouseItem.Gateway.LoadBalancer;

/// <summary>
/// Query-based балансировщик нагрузки: выбирает downstream по query-параметру <c>id</c>.
/// Если параметр отсутствует/невалиден — выбирает случайный downstream.
/// </summary>
public sealed class QueryBasedLoadBalancer(
ILogger<QueryBasedLoadBalancer> logger,
Func<Task<List<Service>>> servicesProvider) : ILoadBalancer
{
private const string IdParam = "id";

public string Type => nameof(QueryBasedLoadBalancer);

public void Release(ServiceHostAndPort hostAndPort) { }

public async Task<Response<ServiceHostAndPort>> LeaseAsync(HttpContext httpContext)
{
var services = await servicesProvider();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Возможно стоит добавить проверку, что services.Count не равно нулю


var selectedIndex = ChooseIndex(httpContext, services.Count, out var parsedId);

if (parsedId is null)
{
logger.LogWarning("Query param {param} missing/invalid; index={index} selected by random.", IdParam,
selectedIndex);
}
else
{
logger.LogInformation("Query-based selection: id={id} -> index={index}.", parsedId, selectedIndex);
}

return new OkResponse<ServiceHostAndPort>(services[selectedIndex].HostAndPort);
}

private static int ChooseIndex(HttpContext httpContext, int servicesCount, out int? id)
{
id = null;

if (TryParseId(httpContext, out var parsed))
{
id = parsed;
return parsed % servicesCount;
}

return Random.Shared.Next(servicesCount);
}

private static bool TryParseId(HttpContext httpContext, out int id)
{
id = 0;

if (!httpContext.Request.Query.TryGetValue(IdParam, out var values) || values.Count == 0)
{
return false;
}

var raw = values[0];
return !string.IsNullOrWhiteSpace(raw) && int.TryParse(raw, out id) && id >= 0;
}
}
36 changes: 36 additions & 0 deletions WarehouseItem.Gateway/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using WarehouseItem.Gateway.LoadBalancer;
using WarehouseItem.ServiceDefaults;

var builder = WebApplication.CreateBuilder(args);

const string CorsPolicyName = "LocalDev";

builder.AddServiceDefaults();

builder.Services.AddCors(static options =>
{
options.AddPolicy(CorsPolicyName, static policy =>
policy.AllowAnyOrigin()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Можно еще добавить разрешенные origins
Также удалить cors из сервиса генерации, поскольку клиент теперь обращается к апи гейтвею

.WithHeaders("Content-Type")
.WithMethods("GET"));
});

builder.Configuration
.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true)
.AddOcelot();

builder.Services
.AddOcelot(builder.Configuration)
.AddCustomLoadBalancer((sp, _, discoveryProvider) =>
new QueryBasedLoadBalancer(
sp.GetRequiredService<ILogger<QueryBasedLoadBalancer>>(),
discoveryProvider.GetAsync));

var app = builder.Build();

app.UseCors(CorsPolicyName);

await app.UseOcelot();
await app.RunAsync();
17 changes: 17 additions & 0 deletions WarehouseItem.Gateway/WarehouseItem.Gateway.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

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

<ItemGroup>
<PackageReference Include="Ocelot" Version="24.1.0" />
</ItemGroup>

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

</Project>
20 changes: 20 additions & 0 deletions WarehouseItem.Gateway/ocelot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"Routes": [
{
"DownstreamPathTemplate": "/api/warehouse-item",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{ "Host": "localhost", "Port": 5201 },
{ "Host": "localhost", "Port": 5202 },
{ "Host": "localhost", "Port": 5203 },
{ "Host": "localhost", "Port": 5204 },
{ "Host": "localhost", "Port": 5205 }
],
"UpstreamPathTemplate": "/warehouse-item",
"UpstreamHttpMethod": [ "GET" ],
"LoadBalancerOptions": {
"Type": "QueryBasedLoadBalancer"
}
}
]
}
36 changes: 36 additions & 0 deletions WarehouseItem.Generator/Controller/WarehouseItemController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Microsoft.AspNetCore.Mvc;
using WarehouseItem.Generator.DTO;
using WarehouseItem.Generator.Service;

namespace WarehouseItem.Generator.Controller;

/// <summary>
/// API контроллер для работы с товарами склада.
/// </summary>
[ApiController]
[Route("api/warehouse-item")]
public sealed class WarehouseItemController(ILogger<WarehouseItemController> logger, IWarehouseItemService service) : ControllerBase
{
/// <summary>
/// Получить товар по id.
/// </summary>
/// <param name="id">Идентификатор товара</param>
/// <param name="cancellationToken">Токен отмены</param>
/// <returns>Данные товара</returns>
[HttpGet]
[ProducesResponseType(typeof(WarehouseItemDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<WarehouseItemDto>> Get([FromQuery] int id, CancellationToken cancellationToken)
{
if (id < 0)
{
return BadRequest(new { message = "id cannot be negative" });
}

logger.LogInformation("Request warehouse item id={id}.", id);
var dto = await service.GetAsync(id, cancellationToken);
logger.LogInformation("Response warehouse item id={id}.", id);

return Ok(dto);
}
}
Loading
Loading