From 9e2d2aee3bcadc0eff2c7805e77af621e16421eb Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 22:29:04 +0400 Subject: [PATCH 01/14] =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=BE=D0=B1=D1=85=D0=BE=D0=B4=D0=B8=D0=BC=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D1=8B=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D1=83=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CloudDevelopment.sln | 22 ++- .../Controllers/WeatherForecastController.cs | 32 +++++ CreditApp.Api/CreditApp.Api.csproj | 17 +++ CreditApp.Api/CreditApp.Api.http | 6 + CreditApp.Api/Program.cs | 29 ++++ CreditApp.Api/Properties/launchSettings.json | 41 ++++++ CreditApp.Api/WeatherForecast.cs | 12 ++ CreditApp.Api/appsettings.Development.json | 8 ++ CreditApp.Api/appsettings.json | 9 ++ CreditApp.AppHost/AppHost.cs | 5 + CreditApp.AppHost/CreditApp.AppHost.csproj | 15 +++ .../Properties/launchSettings.json | 31 +++++ .../appsettings.Development.json | 8 ++ CreditApp.AppHost/appsettings.json | 9 ++ CreditApp.Domain/CreditApp.Domain.csproj | 10 ++ CreditApp.Domain/Program.cs | 2 + .../CreditApp.ServiceDefaults.csproj | 22 +++ CreditApp.ServiceDefaults/Extensions.cs | 127 ++++++++++++++++++ 18 files changed, 403 insertions(+), 2 deletions(-) create mode 100644 CreditApp.Api/Controllers/WeatherForecastController.cs create mode 100644 CreditApp.Api/CreditApp.Api.csproj create mode 100644 CreditApp.Api/CreditApp.Api.http create mode 100644 CreditApp.Api/Program.cs create mode 100644 CreditApp.Api/Properties/launchSettings.json create mode 100644 CreditApp.Api/WeatherForecast.cs create mode 100644 CreditApp.Api/appsettings.Development.json create mode 100644 CreditApp.Api/appsettings.json create mode 100644 CreditApp.AppHost/AppHost.cs create mode 100644 CreditApp.AppHost/CreditApp.AppHost.csproj create mode 100644 CreditApp.AppHost/Properties/launchSettings.json create mode 100644 CreditApp.AppHost/appsettings.Development.json create mode 100644 CreditApp.AppHost/appsettings.json create mode 100644 CreditApp.Domain/CreditApp.Domain.csproj create mode 100644 CreditApp.Domain/Program.cs create mode 100644 CreditApp.ServiceDefaults/CreditApp.ServiceDefaults.csproj create mode 100644 CreditApp.ServiceDefaults/Extensions.cs diff --git a/CloudDevelopment.sln b/CloudDevelopment.sln index cb48241..e1a4ac7 100644 --- a/CloudDevelopment.sln +++ b/CloudDevelopment.sln @@ -1,10 +1,16 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.14.36811.4 +# Visual Studio Version 18 +VisualStudioVersion = 18.3.11512.155 d18.3 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}") = "CreditApp.AppHost", "CreditApp.AppHost\CreditApp.AppHost.csproj", "{5432516B-65B7-417A-9D7C-D87F95B880D5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreditApp.ServiceDefaults", "CreditApp.ServiceDefaults\CreditApp.ServiceDefaults.csproj", "{2A1134C7-1080-475D-2A48-9F65479D8C91}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreditApp.Api", "CreditApp.Api\CreditApp.Api.csproj", "{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +21,18 @@ 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 + {5432516B-65B7-417A-9D7C-D87F95B880D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5432516B-65B7-417A-9D7C-D87F95B880D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5432516B-65B7-417A-9D7C-D87F95B880D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5432516B-65B7-417A-9D7C-D87F95B880D5}.Release|Any CPU.Build.0 = Release|Any CPU + {2A1134C7-1080-475D-2A48-9F65479D8C91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2A1134C7-1080-475D-2A48-9F65479D8C91}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2A1134C7-1080-475D-2A48-9F65479D8C91}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2A1134C7-1080-475D-2A48-9F65479D8C91}.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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CreditApp.Api/Controllers/WeatherForecastController.cs b/CreditApp.Api/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..b5fe0cd --- /dev/null +++ b/CreditApp.Api/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace CreditApp.Api.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/CreditApp.Api/CreditApp.Api.csproj b/CreditApp.Api/CreditApp.Api.csproj new file mode 100644 index 0000000..5b3b0e4 --- /dev/null +++ b/CreditApp.Api/CreditApp.Api.csproj @@ -0,0 +1,17 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + diff --git a/CreditApp.Api/CreditApp.Api.http b/CreditApp.Api/CreditApp.Api.http new file mode 100644 index 0000000..175983e --- /dev/null +++ b/CreditApp.Api/CreditApp.Api.http @@ -0,0 +1,6 @@ +@CreditApp.Api_HostAddress = http://localhost:5144 + +GET {{CreditApp.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/CreditApp.Api/Program.cs b/CreditApp.Api/Program.cs new file mode 100644 index 0000000..edda8a7 --- /dev/null +++ b/CreditApp.Api/Program.cs @@ -0,0 +1,29 @@ +var builder = WebApplication.CreateBuilder(args); + +builder.AddServiceDefaults(); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +app.MapDefaultEndpoints(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/CreditApp.Api/Properties/launchSettings.json b/CreditApp.Api/Properties/launchSettings.json new file mode 100644 index 0000000..e9e6313 --- /dev/null +++ b/CreditApp.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50546", + "sslPort": 44330 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5144", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7184;http://localhost:5144", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/CreditApp.Api/WeatherForecast.cs b/CreditApp.Api/WeatherForecast.cs new file mode 100644 index 0000000..3d8d5c3 --- /dev/null +++ b/CreditApp.Api/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace CreditApp.Api; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/CreditApp.Api/appsettings.Development.json b/CreditApp.Api/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/CreditApp.Api/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/CreditApp.Api/appsettings.json b/CreditApp.Api/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/CreditApp.Api/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/CreditApp.AppHost/AppHost.cs b/CreditApp.AppHost/AppHost.cs new file mode 100644 index 0000000..b1dab74 --- /dev/null +++ b/CreditApp.AppHost/AppHost.cs @@ -0,0 +1,5 @@ +var builder = DistributedApplication.CreateBuilder(args); + +builder.AddProject("creditapp-api"); + +builder.Build().Run(); diff --git a/CreditApp.AppHost/CreditApp.AppHost.csproj b/CreditApp.AppHost/CreditApp.AppHost.csproj new file mode 100644 index 0000000..88571e4 --- /dev/null +++ b/CreditApp.AppHost/CreditApp.AppHost.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + b2dc854c-a0d1-4a18-8fc3-d0168e660b49 + + + + + + + diff --git a/CreditApp.AppHost/Properties/launchSettings.json b/CreditApp.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000..4af7734 --- /dev/null +++ b/CreditApp.AppHost/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17244;http://localhost:15135", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21273", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "https://localhost:23257", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22263" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15135", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19038", + "ASPIRE_DASHBOARD_MCP_ENDPOINT_URL": "http://localhost:18140", + "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20023" + } + } + } +} diff --git a/CreditApp.AppHost/appsettings.Development.json b/CreditApp.AppHost/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/CreditApp.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/CreditApp.AppHost/appsettings.json b/CreditApp.AppHost/appsettings.json new file mode 100644 index 0000000..31c092a --- /dev/null +++ b/CreditApp.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/CreditApp.Domain/CreditApp.Domain.csproj b/CreditApp.Domain/CreditApp.Domain.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/CreditApp.Domain/CreditApp.Domain.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/CreditApp.Domain/Program.cs b/CreditApp.Domain/Program.cs new file mode 100644 index 0000000..3751555 --- /dev/null +++ b/CreditApp.Domain/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/CreditApp.ServiceDefaults/CreditApp.ServiceDefaults.csproj b/CreditApp.ServiceDefaults/CreditApp.ServiceDefaults.csproj new file mode 100644 index 0000000..8ad6726 --- /dev/null +++ b/CreditApp.ServiceDefaults/CreditApp.ServiceDefaults.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + true + + + + + + + + + + + + + + + diff --git a/CreditApp.ServiceDefaults/Extensions.cs b/CreditApp.ServiceDefaults/Extensions.cs new file mode 100644 index 0000000..b72c875 --- /dev/null +++ b/CreditApp.ServiceDefaults/Extensions.cs @@ -0,0 +1,127 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ServiceDiscovery; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + private const string HealthEndpointPath = "/health"; + private const string AlivenessEndpointPath = "/alive"; + + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation(tracing => + // Exclude health check requests from tracing + tracing.Filter = context => + !context.Request.Path.StartsWithSegments(HealthEndpointPath) + && !context.Request.Path.StartsWithSegments(AlivenessEndpointPath) + ) + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks(HealthEndpointPath); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks(AlivenessEndpointPath, new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} From 0b417ebf591840221bb65b4e57b032e8c119f2cf Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 22:33:16 +0400 Subject: [PATCH 02/14] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20Application?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CreditApp.Domain/Data/CreditApplication.cs | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 CreditApp.Domain/Data/CreditApplication.cs diff --git a/CreditApp.Domain/Data/CreditApplication.cs b/CreditApp.Domain/Data/CreditApplication.cs new file mode 100644 index 0000000..9b17d4b --- /dev/null +++ b/CreditApp.Domain/Data/CreditApplication.cs @@ -0,0 +1,45 @@ +namespace CreditApp.Domain.Data; + +public class CreditApplication +{ + /// + /// Идентификатор в системе + /// + public int Id { get; set; } + /// + /// Тип кредита + /// + public string CreditType { get; set; } = default!; + /// + /// Запрашиваемая сумма + /// + public decimal RequestedAmount { get; set; } + /// + /// Срок в месяцах + /// + public int TermMonths { get; set; } + /// + /// Процентная ставка + /// + public double InterestRate { get; set; } + /// + /// Дата подачи + /// + public DateOnly ApplicationDate { get; set; } + /// + /// Необходимость страховки + /// + public bool HasInsurance { get; set; } + /// + /// Статус заявки + /// + public string Status { get; set; } = default!; + /// + /// Дата решения + /// + public DateOnly? DecisionDate { get; set; } + /// + /// Одобренная сумма + /// + public decimal? ApprovedAmount { get; set; } +} \ No newline at end of file From 5fdc84a6c7646fa89e2d8eab4dd915194690cafb Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 22:36:36 +0400 Subject: [PATCH 03/14] =?UTF-8?q?=D0=A1=D0=BE=D0=B7=D0=B4=D0=B0=D0=BB=20Ge?= =?UTF-8?q?nerator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CreditApp.Api/Services/CreditGenerator.cs | 38 +++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 CreditApp.Api/Services/CreditGenerator.cs diff --git a/CreditApp.Api/Services/CreditGenerator.cs b/CreditApp.Api/Services/CreditGenerator.cs new file mode 100644 index 0000000..e4703d0 --- /dev/null +++ b/CreditApp.Api/Services/CreditGenerator.cs @@ -0,0 +1,38 @@ +using Bogus; +using CreditApp.Domain.Data; + +namespace CreditApp.Api.Services; + +public class CreditGenerator +{ + private const double CbRate = 16.0; + + public CreditApplication Generate(int id) + { + var statuses = new[] { "Новая", "В обработке", "Одобрена", "Отклонена" }; + var types = new[] { "Потребительский", "Ипотека", "Автокредит" }; + + var faker = new Faker() + .RuleFor(x => x.Id, id) + .RuleFor(x => x.CreditType, f => f.PickRandom(types)) + .RuleFor(x => x.RequestedAmount, f => Math.Round(f.Random.Decimal(10000, 5_000_000), 2)) + .RuleFor(x => x.TermMonths, f => f.Random.Int(6, 360)) + .RuleFor(x => x.InterestRate, f => Math.Round(f.Random.Double(CbRate, CbRate + 5), 2)) + .RuleFor(x => x.ApplicationDate, f => DateOnly.FromDateTime(f.Date.Past(2))) + .RuleFor(x => x.HasInsurance, f => f.Random.Bool()) + .RuleFor(x => x.Status, f => f.PickRandom(statuses)) + .RuleFor(x => x.DecisionDate, (f, x) => + x.Status is "Одобрена" or "Отклонена" + ? DateOnly.FromDateTime( + f.Date.Between( + x.ApplicationDate.ToDateTime(TimeOnly.MinValue), + DateTime.Now)) + : null) + .RuleFor(x => x.ApprovedAmount, (f, x) => + x.Status == "Одобрена" + ? Math.Round(f.Random.Decimal(10000, x.RequestedAmount), 2) + : null); + + return faker.Generate(); + } +} \ No newline at end of file From 50fa1f5ec2644f0800cd6915f76646f2f2e5be40 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 22:38:53 +0400 Subject: [PATCH 04/14] =?UTF-8?q?=D0=9A=D0=B5=D1=88=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CreditApp.Api/Services/CreditService.cs | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 CreditApp.Api/Services/CreditService.cs diff --git a/CreditApp.Api/Services/CreditService.cs b/CreditApp.Api/Services/CreditService.cs new file mode 100644 index 0000000..04668b7 --- /dev/null +++ b/CreditApp.Api/Services/CreditService.cs @@ -0,0 +1,48 @@ +using CreditApp.Domain.Data; +using Microsoft.Extensions.Caching.Distributed; +using System.Text.Json; + +namespace CreditApp.Api.Services; + +public class CreditService +{ + private readonly IDistributedCache _cache; + private readonly CreditGenerator _generator; + private readonly ILogger _logger; + + public CreditService( + IDistributedCache cache, + CreditGenerator generator, + ILogger logger) + { + _cache = cache; + _generator = generator; + _logger = logger; + } + + public async Task GetAsync(int id) + { + var key = $"credit:{id}"; + var cached = await _cache.GetStringAsync(key); + + if (cached != null) + { + _logger.LogInformation("Cache HIT {Id}", id); + return JsonSerializer.Deserialize(cached)!; + } + + _logger.LogInformation("Cache MISS {Id}", id); + + var result = _generator.Generate(id); + + await _cache.SetStringAsync( + key, + JsonSerializer.Serialize(result), + new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) + }); + + return result; + } +} \ No newline at end of file From 494e372072967fac23e286a12daa275303dbbd6b Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 23:19:46 +0400 Subject: [PATCH 05/14] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=D1=8B=D0=B6=D0=B8=D1=82=D1=8C=20=D0=B2=20=D1=8D?= =?UTF-8?q?=D1=82=D0=BE=D0=BC=20=D0=B6=D0=B5=D1=81=D1=82=D0=BE=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BC=D0=B8=D1=80=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Запутался в коде и пытаюсь исправить проблему с контейнерами --- CloudDevelopment.sln | 6 ++++ CreditApp.Api/Controllers/CreditController.cs | 20 ++++++++++++ .../Controllers/WeatherForecastController.cs | 32 ------------------- CreditApp.Api/CreditApp.Api.csproj | 4 +++ CreditApp.Api/Program.cs | 30 +++++++---------- CreditApp.Api/WeatherForecast.cs | 12 ------- CreditApp.AppHost/AppHost.cs | 11 +++++-- CreditApp.AppHost/CreditApp.AppHost.csproj | 10 +++++- 8 files changed, 60 insertions(+), 65 deletions(-) create mode 100644 CreditApp.Api/Controllers/CreditController.cs delete mode 100644 CreditApp.Api/Controllers/WeatherForecastController.cs delete mode 100644 CreditApp.Api/WeatherForecast.cs diff --git a/CloudDevelopment.sln b/CloudDevelopment.sln index e1a4ac7..02142d4 100644 --- a/CloudDevelopment.sln +++ b/CloudDevelopment.sln @@ -11,6 +11,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreditApp.ServiceDefaults", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreditApp.Api", "CreditApp.Api\CreditApp.Api.csproj", "{E7D4CA8B-53EA-9676-D96D-BE2F0CB11054}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CreditApp.Domain", "CreditApp.Domain\CreditApp.Domain.csproj", "{B3DC2A03-88A2-468A-BE14-95A1F4DEA883}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -33,6 +35,10 @@ Global {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 + {B3DC2A03-88A2-468A-BE14-95A1F4DEA883}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B3DC2A03-88A2-468A-BE14-95A1F4DEA883}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B3DC2A03-88A2-468A-BE14-95A1F4DEA883}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B3DC2A03-88A2-468A-BE14-95A1F4DEA883}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CreditApp.Api/Controllers/CreditController.cs b/CreditApp.Api/Controllers/CreditController.cs new file mode 100644 index 0000000..f14adf8 --- /dev/null +++ b/CreditApp.Api/Controllers/CreditController.cs @@ -0,0 +1,20 @@ +using CreditApp.Api.Services; +using Microsoft.AspNetCore.Mvc; + +namespace CreditApp.Api.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class CreditController : ControllerBase +{ + private readonly CreditService _service; + + public CreditController(CreditService service) + { + _service = service; + } + + [HttpGet("{id:int}")] + public async Task Get(int id) + => Ok(await _service.GetAsync(id)); +} \ No newline at end of file diff --git a/CreditApp.Api/Controllers/WeatherForecastController.cs b/CreditApp.Api/Controllers/WeatherForecastController.cs deleted file mode 100644 index b5fe0cd..0000000 --- a/CreditApp.Api/Controllers/WeatherForecastController.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Mvc; - -namespace CreditApp.Api.Controllers; - -[ApiController] -[Route("[controller]")] -public class WeatherForecastController : ControllerBase -{ - private static readonly string[] Summaries = new[] - { - "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" - }; - - private readonly ILogger _logger; - - public WeatherForecastController(ILogger logger) - { - _logger = logger; - } - - [HttpGet(Name = "GetWeatherForecast")] - public IEnumerable Get() - { - return Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = Summaries[Random.Shared.Next(Summaries.Length)] - }) - .ToArray(); - } -} diff --git a/CreditApp.Api/CreditApp.Api.csproj b/CreditApp.Api/CreditApp.Api.csproj index 5b3b0e4..8f29694 100644 --- a/CreditApp.Api/CreditApp.Api.csproj +++ b/CreditApp.Api/CreditApp.Api.csproj @@ -7,10 +7,14 @@ + + + + diff --git a/CreditApp.Api/Program.cs b/CreditApp.Api/Program.cs index edda8a7..d451b37 100644 --- a/CreditApp.Api/Program.cs +++ b/CreditApp.Api/Program.cs @@ -1,29 +1,23 @@ +using CreditApp.Api.Services; var builder = WebApplication.CreateBuilder(args); -builder.AddServiceDefaults(); - -// Add services to the container. - builder.Services.AddControllers(); -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); -var app = builder.Build(); +builder.Services.AddStackExchangeRedisCache(options => +{ + options.Configuration = builder.Configuration.GetConnectionString("redis"); +}); -app.MapDefaultEndpoints(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(); -} +builder.Services.AddSwaggerGen(); -app.UseHttpsRedirection(); +var app = builder.Build(); -app.UseAuthorization(); +app.UseSwagger(); +app.UseSwaggerUI(); app.MapControllers(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/CreditApp.Api/WeatherForecast.cs b/CreditApp.Api/WeatherForecast.cs deleted file mode 100644 index 3d8d5c3..0000000 --- a/CreditApp.Api/WeatherForecast.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CreditApp.Api; - -public class WeatherForecast -{ - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } -} diff --git a/CreditApp.AppHost/AppHost.cs b/CreditApp.AppHost/AppHost.cs index b1dab74..d7bc21c 100644 --- a/CreditApp.AppHost/AppHost.cs +++ b/CreditApp.AppHost/AppHost.cs @@ -1,5 +1,12 @@ +using Microsoft.Extensions.Hosting; + var builder = DistributedApplication.CreateBuilder(args); -builder.AddProject("creditapp-api"); +var redis = builder.AddRedis("redis") + .WithImage("redis:alpine") + .WithLifetime(ContainerLifetime.Session); + +var api = builder.AddProject("api") + .WithReference(redis); -builder.Build().Run(); +builder.Build().Run(); \ No newline at end of file diff --git a/CreditApp.AppHost/CreditApp.AppHost.csproj b/CreditApp.AppHost/CreditApp.AppHost.csproj index 88571e4..9ddda98 100644 --- a/CreditApp.AppHost/CreditApp.AppHost.csproj +++ b/CreditApp.AppHost/CreditApp.AppHost.csproj @@ -1,4 +1,4 @@ - + Exe @@ -9,7 +9,15 @@ + + + + + + + + From 871d7982460838b96190eb74c0e6e11539c1a488 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 23:20:19 +0400 Subject: [PATCH 06/14] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CreditApp.AppHost/AppHost.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/CreditApp.AppHost/AppHost.cs b/CreditApp.AppHost/AppHost.cs index d7bc21c..ac5b4a6 100644 --- a/CreditApp.AppHost/AppHost.cs +++ b/CreditApp.AppHost/AppHost.cs @@ -1,12 +1,8 @@ -using Microsoft.Extensions.Hosting; - var builder = DistributedApplication.CreateBuilder(args); -var redis = builder.AddRedis("redis") - .WithImage("redis:alpine") - .WithLifetime(ContainerLifetime.Session); +var redis = builder.AddRedis("redis"); -var api = builder.AddProject("api") - .WithReference(redis); +builder.AddProject("api") + .WithReference(redis); builder.Build().Run(); \ No newline at end of file From c8e701bf1668315b220550af31553aa47c418bf7 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Tue, 24 Feb 2026 23:33:37 +0400 Subject: [PATCH 07/14] :) --- CreditApp.Api/CreditApp.Api.csproj | 1 + CreditApp.AppHost/AppHost.cs | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/CreditApp.Api/CreditApp.Api.csproj b/CreditApp.Api/CreditApp.Api.csproj index 8f29694..300aeb5 100644 --- a/CreditApp.Api/CreditApp.Api.csproj +++ b/CreditApp.Api/CreditApp.Api.csproj @@ -14,6 +14,7 @@ + diff --git a/CreditApp.AppHost/AppHost.cs b/CreditApp.AppHost/AppHost.cs index ac5b4a6..f0fe0f6 100644 --- a/CreditApp.AppHost/AppHost.cs +++ b/CreditApp.AppHost/AppHost.cs @@ -1,8 +1,22 @@ +using Google.Protobuf.WellKnownTypes; + var builder = DistributedApplication.CreateBuilder(args); var redis = builder.AddRedis("redis"); -builder.AddProject("api") - .WithReference(redis); +var redisCommander = builder.AddContainer("redis-commander", "rediscommander/redis-commander") + .WithEnvironment("REDIS_HOSTS", "local:redis:6379") + .WithReference(redis) + .WaitFor(redis) + .WithEndpoint(port: 8081, targetPort: 8081); + +var api = builder.AddProject("api") + .WithReference(redis) + .WaitFor(redis); + +builder.AddProject("client") + .WithReference(api) + .WaitFor(api); + +builder.Build().Run(); -builder.Build().Run(); \ No newline at end of file From 5c5cb8e56b8144698ccedf321941a01853fa7ddb Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:11:51 +0400 Subject: [PATCH 08/14] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BB=D0=BE?= =?UTF-8?q?=D0=BF=D0=B0=D1=82=D0=B8=D0=B2=20=D0=B2=D0=B5=D1=81=D1=8C=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=B4=20=D0=BE=D0=B1=D0=BD=D0=B0=D1=80=D1=83=D0=B6?= =?UTF-8?q?=D0=B8=D0=BB,=20=D1=87=D1=82=D0=BE=20=D0=BD=D0=B0=D0=B4=D0=BE?= =?UTF-8?q?=20=D0=B1=D1=8B=D0=BB=D0=BE=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D1=82=D1=8C=20=D0=B2=D1=81=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=BD=D1=83=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D1=83=D1=8E...?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Это была война не на жизнь, а на смерть с 2 часами сна, кровью из носа и криками боли. --- Client.Wasm/Components/StudentCard.razor | 8 +- Client.Wasm/wwwroot/appsettings.json | 2 +- CreditApp.Api/Controllers/CreditController.cs | 98 ++++++++++++++++- CreditApp.Api/CreditApp.Api.csproj | 1 + CreditApp.Api/Program.cs | 36 ++++-- CreditApp.Api/Services/CreditGenerator.cs | 25 ++++- CreditApp.Api/Services/CreditService.cs | 104 +++++++++++++++--- CreditApp.Api/Services/ICreditGenerator.cs | 21 ++++ CreditApp.Api/Services/ICreditService.cs | 21 ++++ 9 files changed, 277 insertions(+), 39 deletions(-) create mode 100644 CreditApp.Api/Services/ICreditGenerator.cs create mode 100644 CreditApp.Api/Services/ICreditService.cs diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index 661f118..8a6e747 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -4,10 +4,10 @@ - Номер №X "Название лабораторной" - Вариант №Х "Название варианта" - Выполнена Фамилией Именем 65ХХ - Ссылка на форк + 1 №X "Кэширование" + Вариант: 53 №Х "Кредитная заявка" + Выполнена Уваровым Никитой 6513 + https://github.com/Rifinn-crypto/cloud-development diff --git a/Client.Wasm/wwwroot/appsettings.json b/Client.Wasm/wwwroot/appsettings.json index d1fe7ab..fa63cb7 100644 --- a/Client.Wasm/wwwroot/appsettings.json +++ b/Client.Wasm/wwwroot/appsettings.json @@ -6,5 +6,5 @@ } }, "AllowedHosts": "*", - "BaseAddress": "" + "BaseAddress": "https://localhost:7184/api/Credit" } diff --git a/CreditApp.Api/Controllers/CreditController.cs b/CreditApp.Api/Controllers/CreditController.cs index f14adf8..0407128 100644 --- a/CreditApp.Api/Controllers/CreditController.cs +++ b/CreditApp.Api/Controllers/CreditController.cs @@ -1,20 +1,108 @@ using CreditApp.Api.Services; +using CreditApp.Domain.Data; using Microsoft.AspNetCore.Mvc; namespace CreditApp.Api.Controllers; +/// +/// Контроллер для работы с кредитными заявками +/// [ApiController] [Route("api/[controller]")] public class CreditController : ControllerBase { - private readonly CreditService _service; + private readonly ICreditService _creditService; + private readonly ILogger _logger; - public CreditController(CreditService service) + public CreditController( + ICreditService creditService, + ILogger logger) { - _service = service; + _creditService = creditService; + _logger = logger; } + /// + /// Получить кредитную заявку по идентификатору + /// [HttpGet("{id:int}")] - public async Task Get(int id) - => Ok(await _service.GetAsync(id)); + [ProducesResponseType(typeof(CreditApplication), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetCreditApplication( + int id, + CancellationToken cancellationToken) + { + _logger.LogInformation("Getting credit application with Id: {CreditId}", id); + + if (id <= 0) + { + _logger.LogWarning("Invalid credit application Id: {CreditId}", id); + return BadRequest("Id must be positive number"); + } + + try + { + var creditApplication = await _creditService.GetAsync(id, cancellationToken); + + _logger.LogInformation( + "Successfully retrieved credit application {CreditId}", + id); + + return Ok(creditApplication); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error getting credit application {CreditId}", id); + return StatusCode(500, "Internal server error"); + } + } + + /// + /// Сгенерировать кредитную заявку с указанным seed + /// + [HttpGet] + [ProducesResponseType(typeof(CreditApplication), StatusCodes.Status200OK)] + public async Task> GenerateCredit( + [FromQuery] int? seed, + CancellationToken cancellationToken) + { + _logger.LogInformation("Generating credit application with seed: {Seed}", seed); + + var id = new Random().Next(1, 10000); + + CreditApplication creditApplication; + + if (seed.HasValue) + { + creditApplication = await _creditService.GetAsync(id, seed.Value, cancellationToken); + _logger.LogInformation("Generated credit application with seed {Seed}: {CreditId}", seed, id); + } + else + { + creditApplication = await _creditService.GetAsync(id, cancellationToken); + _logger.LogInformation("Generated random credit application: {CreditId}", id); + } + + return Ok(creditApplication); + } + + /// + /// Удалить заявку из кэша + /// + [HttpDelete("{id:int}")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public async Task RemoveCreditApplication( + int id, + CancellationToken cancellationToken) + { + if (id <= 0) + { + return BadRequest("Id must be positive number"); + } + + await _creditService.RemoveAsync(id, cancellationToken); + return NoContent(); + } } \ No newline at end of file diff --git a/CreditApp.Api/CreditApp.Api.csproj b/CreditApp.Api/CreditApp.Api.csproj index 300aeb5..9dd2bd8 100644 --- a/CreditApp.Api/CreditApp.Api.csproj +++ b/CreditApp.Api/CreditApp.Api.csproj @@ -7,6 +7,7 @@ + diff --git a/CreditApp.Api/Program.cs b/CreditApp.Api/Program.cs index d451b37..27577bc 100644 --- a/CreditApp.Api/Program.cs +++ b/CreditApp.Api/Program.cs @@ -1,23 +1,39 @@ using CreditApp.Api.Services; + var builder = WebApplication.CreateBuilder(args); +builder.AddServiceDefaults(); +builder.AddRedisDistributedCache("redis"); + builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); -builder.Services.AddStackExchangeRedisCache(options => +builder.Services.AddCors(options => { - options.Configuration = builder.Configuration.GetConnectionString("redis"); + options.AddPolicy("wasm", policy => + { + policy.AllowAnyOrigin() + .WithMethods("GET") + .WithHeaders("Content-Type"); + }); }); -builder.Services.AddSingleton(); -builder.Services.AddScoped(); - -builder.Services.AddSwaggerGen(); +builder.Services.AddSingleton(); +builder.Services.AddScoped(); var app = builder.Build(); -app.UseSwagger(); -app.UseSwaggerUI(); - +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.MapDefaultEndpoints(); +app.UseHttpsRedirection(); +app.UseCors("wasm"); +app.UseAuthorization(); app.MapControllers(); -app.Run(); \ No newline at end of file +app.Run(); diff --git a/CreditApp.Api/Services/CreditGenerator.cs b/CreditApp.Api/Services/CreditGenerator.cs index e4703d0..7f2ab44 100644 --- a/CreditApp.Api/Services/CreditGenerator.cs +++ b/CreditApp.Api/Services/CreditGenerator.cs @@ -3,24 +3,37 @@ namespace CreditApp.Api.Services; -public class CreditGenerator +/// +/// Генератор тестовых данных для кредитных заявок с использованием библиотеки Bogus. +/// +public class CreditGenerator : ICreditGenerator { private const double CbRate = 16.0; + private readonly string[] _statuses = { "Новая", "В обработке", "Одобрена", "Отклонена" }; + private readonly string[] _types = { "Потребительский", "Ипотека", "Автокредит" }; + /// + /// Генерирует кредитную заявку со случайным seed. + /// public CreditApplication Generate(int id) { - var statuses = new[] { "Новая", "В обработке", "Одобрена", "Отклонена" }; - var types = new[] { "Потребительский", "Ипотека", "Автокредит" }; + return Generate(id, new Random().Next(1, 10000)); + } + /// + /// Генерирует кредитную заявку с указанным seed для воспроизводимости результатов. + /// + public CreditApplication Generate(int id, int seed) + { var faker = new Faker() .RuleFor(x => x.Id, id) - .RuleFor(x => x.CreditType, f => f.PickRandom(types)) + .RuleFor(x => x.CreditType, f => f.PickRandom(_types)) .RuleFor(x => x.RequestedAmount, f => Math.Round(f.Random.Decimal(10000, 5_000_000), 2)) .RuleFor(x => x.TermMonths, f => f.Random.Int(6, 360)) .RuleFor(x => x.InterestRate, f => Math.Round(f.Random.Double(CbRate, CbRate + 5), 2)) .RuleFor(x => x.ApplicationDate, f => DateOnly.FromDateTime(f.Date.Past(2))) .RuleFor(x => x.HasInsurance, f => f.Random.Bool()) - .RuleFor(x => x.Status, f => f.PickRandom(statuses)) + .RuleFor(x => x.Status, f => f.PickRandom(_statuses)) .RuleFor(x => x.DecisionDate, (f, x) => x.Status is "Одобрена" or "Отклонена" ? DateOnly.FromDateTime( @@ -33,6 +46,8 @@ x.Status is "Одобрена" or "Отклонена" ? Math.Round(f.Random.Decimal(10000, x.RequestedAmount), 2) : null); + faker.UseSeed(seed); + return faker.Generate(); } } \ No newline at end of file diff --git a/CreditApp.Api/Services/CreditService.cs b/CreditApp.Api/Services/CreditService.cs index 04668b7..e6643e9 100644 --- a/CreditApp.Api/Services/CreditService.cs +++ b/CreditApp.Api/Services/CreditService.cs @@ -4,15 +4,18 @@ namespace CreditApp.Api.Services; -public class CreditService +/// +/// Сервис для работы с кредитными заявками, реализующий кэширование через IDistributedCache. +/// +public class CreditService : ICreditService { private readonly IDistributedCache _cache; - private readonly CreditGenerator _generator; + private readonly ICreditGenerator _generator; private readonly ILogger _logger; public CreditService( IDistributedCache cache, - CreditGenerator generator, + ICreditGenerator generator, ILogger logger) { _cache = cache; @@ -20,29 +23,102 @@ public CreditService( _logger = logger; } - public async Task GetAsync(int id) + /// + /// Получает кредитную заявку по идентификатору. При отсутствии в кэше генерирует новую. + /// + public async Task GetAsync(int id, CancellationToken cancellationToken = default) { var key = $"credit:{id}"; - var cached = await _cache.GetStringAsync(key); + + _logger.LogDebug("Attempting to get credit application {CreditId} from cache", id); + + var cached = await _cache.GetStringAsync(key, cancellationToken); if (cached != null) { - _logger.LogInformation("Cache HIT {Id}", id); + _logger.LogInformation( + "Cache HIT for credit application {CreditId}. Cache size: {SizeBytes} bytes", + id, + System.Text.Encoding.UTF8.GetByteCount(cached)); + return JsonSerializer.Deserialize(cached)!; } - _logger.LogInformation("Cache MISS {Id}", id); + _logger.LogInformation( + "Cache MISS for credit application {CreditId}. Generating new instance", + id); var result = _generator.Generate(id); + await CacheResultAsync(key, result, cancellationToken); + + return result; + } + + /// + /// Получает кредитную заявку по идентификатору с использованием seed для детерминированной генерации. + /// + public async Task GetAsync(int id, int seed, CancellationToken cancellationToken = default) + { + var key = $"credit:{id}:seed:{seed}"; + + _logger.LogDebug("Attempting to get credit application {CreditId} with seed {Seed} from cache", id, seed); + + var cached = await _cache.GetStringAsync(key, cancellationToken); + + if (cached != null) + { + _logger.LogInformation( + "Cache HIT for credit application {CreditId} with seed {Seed}", + id, + seed); + + return JsonSerializer.Deserialize(cached)!; + } + + _logger.LogInformation( + "Cache MISS for credit application {CreditId} with seed {Seed}. Generating new instance", + id, + seed); + + var result = _generator.Generate(id, seed); + - await _cache.SetStringAsync( - key, - JsonSerializer.Serialize(result), - new DistributedCacheEntryOptions - { - AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) - }); + await CacheResultAsync(key, result, cancellationToken); return result; } + + /// + /// Удаляет кредитную заявку из кэша. + /// + public async Task RemoveAsync(int id, CancellationToken cancellationToken = default) + { + var key = $"credit:{id}"; + await _cache.RemoveAsync(key, cancellationToken); + + _logger.LogWarning( + "Credit application {CreditId} removed from cache", + id); + } + + /// + /// Сохраняет сгенерированную кредитную заявку в кэш. + /// + private async Task CacheResultAsync(string key, CreditApplication result, CancellationToken cancellationToken) + { + var serialized = JsonSerializer.Serialize(result); + var options = new DistributedCacheEntryOptions + { + AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10) + }; + + await _cache.SetStringAsync(key, serialized, options, cancellationToken); + + _logger.LogInformation( + "Generated new credit application {CreditId}. Type: {CreditType}, Amount: {Amount:C}, Status: {Status}", + result.Id, + result.CreditType, + result.RequestedAmount, + result.Status); + } } \ No newline at end of file diff --git a/CreditApp.Api/Services/ICreditGenerator.cs b/CreditApp.Api/Services/ICreditGenerator.cs new file mode 100644 index 0000000..75e7092 --- /dev/null +++ b/CreditApp.Api/Services/ICreditGenerator.cs @@ -0,0 +1,21 @@ +using CreditApp.Domain.Data; + +namespace CreditApp.Api.Services; + +/// +/// Интерфейс генератора кредитных заявок +/// +public interface ICreditGenerator +{ + /// + /// Сгенерировать кредитную заявку + /// + public CreditApplication Generate(int id); + + + /// + /// Сгенерировать кредитную заявку с указанным seed + /// + public CreditApplication Generate(int id, int seed); + +} \ No newline at end of file diff --git a/CreditApp.Api/Services/ICreditService.cs b/CreditApp.Api/Services/ICreditService.cs new file mode 100644 index 0000000..cdc479e --- /dev/null +++ b/CreditApp.Api/Services/ICreditService.cs @@ -0,0 +1,21 @@ +using CreditApp.Domain.Data; + +namespace CreditApp.Api.Services; + +public interface ICreditService +{ + /// + /// Получить кредитную заявку по идентификатору + /// + public Task GetAsync(int id, CancellationToken cancellationToken = default); + + /// + /// Получить кредитную заявку с указанным seed для генерации + /// + public Task GetAsync(int id, int seed, CancellationToken cancellationToken = default); + + /// + /// Удалить заявку из кэша + /// + public Task RemoveAsync(int id, CancellationToken cancellationToken = default); +} \ No newline at end of file From b0a7b6cf6c4c149201d1a281f36b58bed4c560e3 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:22:30 +0400 Subject: [PATCH 09/14] . --- Client.Wasm/Components/StudentCard.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index 8a6e747..f5a9546 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -10,4 +10,4 @@ https://github.com/Rifinn-crypto/cloud-development - + \ No newline at end of file From 9f3a8f01ae28019f1abdd587366b40aa4c76392b Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:26:07 +0400 Subject: [PATCH 10/14] . --- CreditApp.Domain/Data/CreditApplication.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CreditApp.Domain/Data/CreditApplication.cs b/CreditApp.Domain/Data/CreditApplication.cs index 9b17d4b..bcffdea 100644 --- a/CreditApp.Domain/Data/CreditApplication.cs +++ b/CreditApp.Domain/Data/CreditApplication.cs @@ -42,4 +42,4 @@ public class CreditApplication /// Одобренная сумма /// public decimal? ApprovedAmount { get; set; } -} \ No newline at end of file +} From 9d3bc730cc443b253d019864d62f5a350ee79c75 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:34:36 +0400 Subject: [PATCH 11/14] ..... --- Client.Wasm/Components/StudentCard.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index f5a9546..0aca68e 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -4,7 +4,7 @@ - 1 №X "Кэширование" + Номер №1 "Кэширование" Вариант: 53 №Х "Кредитная заявка" Выполнена Уваровым Никитой 6513 https://github.com/Rifinn-crypto/cloud-development From 826be7dc436cc038d303a9e2b0821d9b41fc20c8 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:36:52 +0400 Subject: [PATCH 12/14] ............. --- Client.Wasm/Components/StudentCard.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index 0aca68e..c1b65b1 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -5,7 +5,7 @@ Номер №1 "Кэширование" - Вариант: 53 №Х "Кредитная заявка" + Вариант №53 "Кредитная заявка" Выполнена Уваровым Никитой 6513 https://github.com/Rifinn-crypto/cloud-development From a79906c530cbf83e8bc48482e5212dfd5877c5b6 Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:39:36 +0400 Subject: [PATCH 13/14] ..... --- Client.Wasm/Components/StudentCard.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index c1b65b1..0a89bbd 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -7,7 +7,7 @@ Номер №1 "Кэширование" Вариант №53 "Кредитная заявка" Выполнена Уваровым Никитой 6513 - https://github.com/Rifinn-crypto/cloud-development + Ссылка на форк \ No newline at end of file From ef0152dab30085f604814c3dd5cba57d225680bf Mon Sep 17 00:00:00 2001 From: Rifinn-crypto Date: Wed, 25 Feb 2026 01:41:36 +0400 Subject: [PATCH 14/14] Update StudentCard.razor --- Client.Wasm/Components/StudentCard.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client.Wasm/Components/StudentCard.razor b/Client.Wasm/Components/StudentCard.razor index 0a89bbd..39eae8e 100644 --- a/Client.Wasm/Components/StudentCard.razor +++ b/Client.Wasm/Components/StudentCard.razor @@ -10,4 +10,4 @@ Ссылка на форк - \ No newline at end of file +