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
ed206e6
revert: откат лаб.1 — был выбран неверный вариант задания. Переделыва…
Pancake2021 Mar 17, 2026
22144d2
feat(domain): добавлена модель кредитной заявки и логика данных
Pancake2021 Mar 17, 2026
e63c60a
infra: настройка .NET Aspire: добавлен Redis и LocalStack (SQS) по за…
Pancake2021 Mar 17, 2026
e69bfa2
chore: обновление Solution файла и документации под 30 вариант
Pancake2021 Mar 17, 2026
957eb65
Initial commit: Variant 30 Credit Application with SQS and Localstack
Pancake2021 Mar 19, 2026
cd7d6dc
Merge remote with local changes (Variant 30 Credit Application)
Pancake2021 Mar 19, 2026
efd1035
Доработка техдолга по логике сервиса по заданию 1 лабораторной
Pancake2021 Apr 9, 2026
9b5b424
ЛР2: добавлен API Gateway на Ocelot и Query Based балансировка
Pancake2021 Apr 9, 2026
ef49e0e
Рефактор генерации заявок: опции, валидация инвариантов и тесты
Pancake2021 Apr 9, 2026
bbb6195
Починил конфликт портов в AppHost для реплик
Pancake2021 Apr 9, 2026
2b8b0a5
Убрал localstack и вернул нормальный Redis-кэш
Pancake2021 Apr 21, 2026
571d71b
Убрал дублирующий контур CreditSystem, оставил только ProjectApp
Pancake2021 Apr 21, 2026
949ec8b
Добавил summary и тест на стабильный кэш по id
Pancake2021 Apr 21, 2026
9229ae8
Подровнял версию пакета Redis-кэша
Pancake2021 Apr 21, 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
6 changes: 3 additions & 3 deletions Client.Wasm/Components/DataCard.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<CardDeck>
<Card>
<CardHeader>
<Heading Size="HeadingSize.Is5"><Icon Name="IconName.Table" /> Характеристики текущего объекта</Heading>
<Heading Size="HeadingSize.Is5"><Blazorise.Icons.FontAwesome.Icon Name="IconName.Table" /> Характеристики текущего объекта</Heading>
</CardHeader>
<CardBody>
<Table Bordered >
Expand Down Expand Up @@ -43,7 +43,7 @@

<Card Margin="Margin.Is3.OnY">
<CardHeader>
<Heading Size="HeadingSize.Is5"><Icon Name="IconName.Send" /> Запросить новый объект</Heading>
<Heading Size="HeadingSize.Is5"><Blazorise.Icons.FontAwesome.Icon Name="IconName.Send" /> Запросить новый объект</Heading>
</CardHeader>
<CardBody>
<Row>
Expand All @@ -54,7 +54,7 @@
<NumericEdit TValue="int" @bind-Value="@Id"/>
</Column>
<Column ColumnSize="ColumnSize.Is4">
<Button Clicked=RequestNewData Color="Color.Primary">Запросить данные <Icon Name="IconName.ArrowRight" /></Button>
<Button Clicked=RequestNewData Color="Color.Primary">Запросить данные <Blazorise.Icons.FontAwesome.Icon Name="IconName.ArrowRight" /></Button>
</Column>
</Row>
</CardBody>
Expand Down
10 changes: 5 additions & 5 deletions Client.Wasm/Components/StudentCard.razor
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<Card>
<CardHeader>
<Heading Size="HeadingSize.Is5"><Icon Name="IconName.User" /> Лабораторная работа</Heading>
<Heading Size="HeadingSize.Is5"><Blazorise.Icons.FontAwesome.Icon Name="IconName.User" /> Лабораторная работа</Heading>
</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>№1 "Кэширование"</Strong></UnorderedListItem>
<UnorderedListItem>Вариант <Strong>№30 "Кредитная заявка"</Strong></UnorderedListItem>
<UnorderedListItem>Выполнена <Strong>Панкеевым Глебом 6512</Strong> </UnorderedListItem>
<UnorderedListItem><Link To="https://github.com/Pancake2021/cloud-development/tree/lab1/pankeev-gleb">Ссылка на мой форк</Link></UnorderedListItem>
</UnorderedList>
</CardBody>
</Card>
5 changes: 3 additions & 2 deletions Client.Wasm/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
@using Microsoft.JSInterop
@using Client.Wasm.Layout
@using Client.Wasm.Components
@using Blazorise
@using Blazorise.Components
@using Blazorise
@using Blazorise.Bootstrap
@using Blazorise.Icons.FontAwesome
@using System.Text.Json
@using System.Text.Json.Nodes
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": ""
"BaseAddress": "http://localhost:7000/api/creditapplication"
}
41 changes: 38 additions & 3 deletions CloudDevelopment.sln
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36811.4
VisualStudioVersion = 17.13.35931.197
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}") = "ProjectApp.Api", "ProjectApp.Api\ProjectApp.Api.csproj", "{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectApp.Domain", "ProjectApp.Domain\ProjectApp.Domain.csproj", "{CC5A9873-4CC3-4B71-83AF-E4FD09F7B1AD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectApp.ServiceDefaults", "ProjectApp.ServiceDefaults\ProjectApp.ServiceDefaults.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectApp.AppHost", "ProjectApp.AppHost\ProjectApp.AppHost.csproj", "{2A5FB573-9376-4FEB-9289-A8387F435C13}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectApp.Gateway", "ProjectApp.Gateway\ProjectApp.Gateway.csproj", "{0EB8062F-A339-472C-97E3-E1E39DCE9673}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectApp.Tests", "ProjectApp.Tests\ProjectApp.Tests.csproj", "{6236DF33-B1C6-48D9-82A8-4FC6A4DF010A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +26,30 @@ Global
{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
{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}.Release|Any CPU.Build.0 = Release|Any CPU
{CC5A9873-4CC3-4B71-83AF-E4FD09F7B1AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC5A9873-4CC3-4B71-83AF-E4FD09F7B1AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC5A9873-4CC3-4B71-83AF-E4FD09F7B1AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC5A9873-4CC3-4B71-83AF-E4FD09F7B1AD}.Release|Any CPU.Build.0 = Release|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
{2A5FB573-9376-4FEB-9289-A8387F435C13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2A5FB573-9376-4FEB-9289-A8387F435C13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2A5FB573-9376-4FEB-9289-A8387F435C13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2A5FB573-9376-4FEB-9289-A8387F435C13}.Release|Any CPU.Build.0 = Release|Any CPU
{0EB8062F-A339-472C-97E3-E1E39DCE9673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0EB8062F-A339-472C-97E3-E1E39DCE9673}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0EB8062F-A339-472C-97E3-E1E39DCE9673}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0EB8062F-A339-472C-97E3-E1E39DCE9673}.Release|Any CPU.Build.0 = Release|Any CPU
{6236DF33-B1C6-48D9-82A8-4FC6A4DF010A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6236DF33-B1C6-48D9-82A8-4FC6A4DF010A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6236DF33-B1C6-48D9-82A8-4FC6A4DF010A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6236DF33-B1C6-48D9-82A8-4FC6A4DF010A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
26 changes: 26 additions & 0 deletions ProjectApp.Api/Controllers/CreditApplicationController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using ProjectApp.Api.Services.CreditApplicationService;
using ProjectApp.Domain.Entities;
using Microsoft.AspNetCore.Mvc;

namespace ProjectApp.Api.Controllers;

[Route("api/[controller]")]
[ApiController]
public class CreditApplicationController(ICreditApplicationService creditService, ILogger<CreditApplicationController> logger) : ControllerBase
{
/// <summary>
/// Получить кредитную заявку по ID, если не найдена в кэше — сгенерировать новую
/// </summary>
/// <param name="id">ID кредитной заявки</param>
/// <param name="cancellationToken">Токен отмены операции</param>
/// <returns>Кредитная заявка</returns>
[HttpGet]
public async Task<ActionResult<CreditApplication>> GetById([FromQuery] int id, CancellationToken cancellationToken)
{
logger.LogInformation("Received request to retrieve/generate credit application {Id}", id);

var application = await creditService.GetByIdAsync(id, cancellationToken);

return Ok(application);
}
}
32 changes: 32 additions & 0 deletions ProjectApp.Api/Options/CreditApplicationGenerationOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace ProjectApp.Api.Options;

/// <summary>
/// Настройки генерации кредитных заявок.
/// </summary>
public class CreditApplicationGenerationOptions
{
/// <summary>Имя секции в appsettings.</summary>
public const string SectionName = "CreditApplicationSettings";

/// <summary>Минимальная допустимая ставка (ключевая ставка ЦБ).</summary>
public double CentralBankKeyRate { get; set; } = 21.0;
/// <summary>Минимальная запрашиваемая сумма.</summary>
public decimal MinRequestedAmount { get; set; } = 50_000m;
/// <summary>Максимальная запрашиваемая сумма.</summary>
public decimal MaxRequestedAmount { get; set; } = 5_000_000m;
/// <summary>Минимальный срок кредита.</summary>
public int MinTermMonths { get; set; } = 6;
/// <summary>Максимальный срок кредита.</summary>
public int MaxTermMonths { get; set; } = 360;
/// <summary>Максимальный возраст даты подачи заявки в годах.</summary>
public int MaxApplicationAgeYears { get; set; } = 2;
/// <summary>Справочник доступных типов кредита.</summary>
public string[] CreditTypes { get; set; } =
[
"Потребительский",
"Ипотека",
"Автокредит",
"Рефинансирование",
"Кредитная карта"
];
}
65 changes: 65 additions & 0 deletions ProjectApp.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using ProjectApp.Api.Services.CreditApplicationService;
using ProjectApp.Api.Options;
using ProjectApp.ServiceDefaults;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = builder.Configuration.GetConnectionString("cache");
});

builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.WithMethods("GET")
.WithHeaders("Content-Type");
});
});

builder.Services.Configure<CreditApplicationGenerationOptions>(
builder.Configuration.GetSection(CreditApplicationGenerationOptions.SectionName));
builder.Services.AddSingleton<CreditApplicationGenerator>();
builder.Services.AddSingleton<CreditApplicationValidator>();
builder.Services.AddScoped<ICreditApplicationService, CreditApplicationService>();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new Microsoft.OpenApi.OpenApiInfo
{
Title = "Credit Application Generator API"
});

var xmlFilename = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFilename);
if (File.Exists(xmlPath))
{
options.IncludeXmlComments(xmlPath);
}

var domainXmlPath = Path.Combine(AppContext.BaseDirectory, "ProjectApp.Domain.xml");
if (File.Exists(domainXmlPath))
{
options.IncludeXmlComments(domainXmlPath);
}
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseCors();
app.UseHttpsRedirection();
app.MapControllers();
app.MapDefaultEndpoints();

app.Run();
23 changes: 23 additions & 0 deletions ProjectApp.Api/ProjectApp.Api.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Bogus" Version="35.6.5" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.24" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.4" />
</ItemGroup>

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

</Project>
41 changes: 41 additions & 0 deletions ProjectApp.Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:46825",
"sslPort": 44333
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5179",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7170;http://localhost:5179",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using Bogus;
using Microsoft.Extensions.Options;
using ProjectApp.Api.Options;
using ProjectApp.Domain.Entities;

namespace ProjectApp.Api.Services.CreditApplicationService;

/// <summary>
/// Генератор кредитных заявок на основе Bogus.
/// </summary>
public class CreditApplicationGenerator
{
private readonly Faker<CreditApplication> _faker;
private readonly CreditApplicationGenerationOptions _options;

public CreditApplicationGenerator(IOptions<CreditApplicationGenerationOptions> options)
{
_options = options.Value;

var nonTerminalStatuses = new[] { "Новая", "В обработке" };
var terminalStatuses = new[] { "Одобрена", "Отклонена" };
var minApplicationDate = DateTime.Today.AddYears(-_options.MaxApplicationAgeYears);
var maxApplicationDate = DateTime.Today.AddDays(-1);

_faker = new Faker<CreditApplication>("ru")
.RuleFor(c => c.Id, f => f.IndexFaker + 1)
.RuleFor(c => c.CreditType, f => f.PickRandom(_options.CreditTypes))
.RuleFor(c => c.RequestedAmount, f => Math.Round(
f.Finance.Amount(_options.MinRequestedAmount, _options.MaxRequestedAmount), 2))
.RuleFor(c => c.TermMonths, f => f.Random.Int(_options.MinTermMonths, _options.MaxTermMonths))
.RuleFor(c => c.InterestRate, f =>
{
var generatedRate = f.Random.Double(_options.CentralBankKeyRate, _options.CentralBankKeyRate + 12.0);
return Math.Round(generatedRate, 2);
})
.RuleFor(c => c.ApplicationDate, f =>
{
var date = f.Date.Between(minApplicationDate, maxApplicationDate);
return DateOnly.FromDateTime(date);
})
.RuleFor(c => c.RequiresInsurance, f => f.Random.Bool())
.RuleFor(c => c.Status, f =>
{
var isTerminal = f.Random.Bool(0.7f);
return isTerminal ? f.PickRandom(terminalStatuses) : f.PickRandom(nonTerminalStatuses);
})
.RuleFor(c => c.DecisionDate, (f, c) =>
{
if (c.Status is not ("Одобрена" or "Отклонена"))
{
return null;
}

var minDate = c.ApplicationDate.ToDateTime(TimeOnly.MinValue).AddDays(1);
var maxDate = DateTime.Today;

if (minDate > maxDate)
{
return DateOnly.FromDateTime(minDate);
}

var endDate = f.Date.Between(minDate, maxDate);
return DateOnly.FromDateTime(endDate);
})
.RuleFor(c => c.ApprovedAmount, (f, c) =>
{
if (c.Status != "Одобрена")
{
return null;
}

var approved = f.Finance.Amount(_options.MinRequestedAmount, c.RequestedAmount);
return Math.Round(approved, 2);
});
}

/// <summary>
/// Генерирует одну заявку.
/// </summary>
/// <returns>Сгенерированная кредитная заявка.</returns>
public CreditApplication Generate() => _faker.Generate();
}
Loading