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
Binary file added AspireApp.ApiService/.DS_Store
Binary file not shown.
19 changes: 19 additions & 0 deletions AspireApp.ApiService/AspireApp.ApiService.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

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

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

<ItemGroup>
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="8.2.2" />
<PackageReference Include="Bogus" Version="35.6.5" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.15" />
</ItemGroup>

</Project>
69 changes: 69 additions & 0 deletions AspireApp.ApiService/Entities/Warehouse.cs
Copy link

Choose a reason for hiding this comment

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

Папки Entities и Generator вынести из Properties

Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System.Text.Json.Serialization;

namespace AspireApp.ApiService.Entities;

/// <summary>
/// Товар на складе
/// </summary>
public class Warehouse
{
/// <summary>
/// Идентификатор
/// </summary>
[JsonPropertyName("id")]
public int Id { get; set; }

/// <summary>
/// Наименование товара
/// </summary>
[JsonPropertyName("name")]
public string? Name { get; set; }

/// <summary>
/// Категория товара
/// </summary>
[JsonPropertyName("category")]
public string? Category { get; set; }

/// <summary>
/// Количество на складе
/// </summary>
[JsonPropertyName("stockQuantity")]
public int StockQuantity { get; set; }

/// <summary>
/// Цена за единицу товара
/// </summary>
[JsonPropertyName("price")]
public decimal Price { get; set; }

/// <summary>
/// Вес единицы товара
/// </summary>
[JsonPropertyName("weight")]
public double Weight { get; set; }

/// <summary>
/// Габариты единицы товара
/// </summary>
[JsonPropertyName("dimensions")]
public string? Dimensions { get; set; }

/// <summary>
/// Хрупкий ли товар
/// </summary>
[JsonPropertyName("isFragile")]
public bool IsFragile { get; set; }

/// <summary>
/// Дата последней поставки
/// </summary>
[JsonPropertyName("lastDeliveryDate")]
public DateOnly LastDeliveryDate { get; set; }

/// <summary>
/// Дата следующей планируемой поставки
/// </summary>
[JsonPropertyName("nextDeliveryDate")]
public DateOnly NextDeliveryDate { get; set; }
}
19 changes: 19 additions & 0 deletions AspireApp.ApiService/Generator/IWarehouseCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using AspireApp.ApiService.Entities;

namespace AspireApp.ApiService.Generator;

/// <summary>
/// Интерфейс для работы с кэшем товаров
/// </summary>
public interface IWarehouseCache
{
/// <summary>
/// Получить товар из кэша по идентификатору
/// </summary>
Task<Warehouse?> GetAsync(int id);

/// <summary>
/// Сохранить товар в кэш
/// </summary>
Task SetAsync(Warehouse warehouse);
}
11 changes: 11 additions & 0 deletions AspireApp.ApiService/Generator/IWarehouseGeneratorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using AspireApp.ApiService.Entities;

namespace AspireApp.ApiService.Generator;

/// <summary>
/// Сервис обработки товаров на складе
/// </summary>
public interface IWarehouseGeneratorService
{
Task<Warehouse> ProcessWarehouse(int id);
}
49 changes: 49 additions & 0 deletions AspireApp.ApiService/Generator/WarehouseCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using AspireApp.ApiService.Entities;

namespace AspireApp.ApiService.Generator;

/// <summary>
/// Кэширование товаров
/// </summary>
public class WarehouseCache(
IDistributedCache cache,
ILogger<WarehouseCache> logger,
IConfiguration configuration) : IWarehouseCache
{
private readonly TimeSpan _defaultExpiration = int.TryParse(configuration["CacheExpiration"], out var seconds)
? TimeSpan.FromSeconds(seconds)
: TimeSpan.FromSeconds(3600);

public async Task<Warehouse?> GetAsync(int id)
{
var key = $"warehouse_{id}";
var cached = await cache.GetStringAsync(key);
if (cached == null)
return null;

try
{
return JsonSerializer.Deserialize<Warehouse>(cached);
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка десериализации товара {Id} из кэша", id);
return null;
}
}

public async Task SetAsync(Warehouse warehouse)
{
var key = $"warehouse_{warehouse.Id}";
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = _defaultExpiration
};
var serialized = JsonSerializer.Serialize(warehouse);
await cache.SetStringAsync(key, serialized, options);
}
}
35 changes: 35 additions & 0 deletions AspireApp.ApiService/Generator/WarehouseGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Bogus;
using AspireApp.ApiService.Entities;

namespace AspireApp.ApiService.Generator;

/// <summary>
/// Генератор случайных товаров с использованием Bogus
/// </summary>
public class WarehouseGenerator
{
private readonly Faker<Warehouse> _faker;

public WarehouseGenerator()
{
_faker = new Faker<Warehouse>()
.RuleFor(w => w.Id, f => f.IndexFaker + 1) // будет перезаписан позже
.RuleFor(w => w.Name, f => f.Commerce.ProductName())
.RuleFor(w => w.Category, f => f.Commerce.Categories(1)[0])
.RuleFor(w => w.StockQuantity, f => f.Random.Int(0, 1000))
.RuleFor(w => w.Price, f => decimal.Parse(f.Commerce.Price()))
.RuleFor(w => w.Weight, f => f.Random.Double(0.1, 50.0))
.RuleFor(w => w.Dimensions, f => $"{f.Random.Int(10, 100)}x{f.Random.Int(10, 100)}x{f.Random.Int(10, 100)}")
.RuleFor(w => w.IsFragile, f => f.Random.Bool(0.3f))
.RuleFor(w => w.LastDeliveryDate, f => DateOnly.FromDateTime(f.Date.Past(30)))
.RuleFor(w => w.NextDeliveryDate, f => DateOnly.FromDateTime(f.Date.Future(30)));
}

/// <summary>
/// Генерирует один случайный товар (Id будет перезаписан вызывающим кодом)
/// </summary>
public Warehouse Generate()
{
return _faker.Generate();
}
}
53 changes: 53 additions & 0 deletions AspireApp.ApiService/Generator/WarehouseGeneratorService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using AspireApp.ApiService.Entities;

namespace AspireApp.ApiService.Generator;

/// <summary>
/// Служба для запуска юзкейса по обработке товаров на складе
/// </summary>
public class WarehouseGeneratorService(
IWarehouseCache warehouseCache,
ILogger<WarehouseGeneratorService> logger,
WarehouseGenerator generator) : IWarehouseGeneratorService
{
public async Task<Warehouse> ProcessWarehouse(int id)
{
logger.LogInformation("Обработка товара с Id = {Id} начата", id);

// Получаем товар из кэша
Warehouse? warehouse = null;
try
{
warehouse = await warehouseCache.GetAsync(id);
if (warehouse != null)
{
logger.LogInformation("Товар {Id} получен из кэша", id);
return warehouse;
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "Не удалось получить товар {Id} из кэша (ошибка игнорируется)", id);
}

// Если в кэше нет или ошибка — генерируем новый товар
logger.LogInformation("Товар {Id} в кэше не найден или кэш недоступен, запуск генерации", id);
warehouse = generator.Generate();
warehouse.Id = id;

// Попытка сохранить в кэш
try
{
logger.LogInformation("Сохранение товара {Id} в кэш", id);
await warehouseCache.SetAsync(warehouse);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Не удалось сохранить товар {Id} в кэш (ошибка игнорируется)", id);
}

return warehouse;
}
}
38 changes: 38 additions & 0 deletions AspireApp.ApiService/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using AspireApp.ApiService.Generator;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

builder.AddRedisDistributedCache("RedisCache");

builder.Services.AddScoped<IWarehouseCache, WarehouseCache>();
builder.Services.AddSingleton<WarehouseGenerator>();
builder.Services.AddScoped<IWarehouseGeneratorService, WarehouseGeneratorService>();

builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
Comment on lines +20 to +22
Copy link

Choose a reason for hiding this comment

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

Настроить Cors

});
});

var app = builder.Build();

app.MapDefaultEndpoints();

app.MapGet("/warehouse", async (IWarehouseGeneratorService service, int id) =>
{
var warehouse = await service.ProcessWarehouse(id);
return Results.Ok(warehouse);
});

app.UseCors();

app.Run();
13 changes: 13 additions & 0 deletions AspireApp.ApiService/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
9 changes: 9 additions & 0 deletions AspireApp.ApiService/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{"BaseAddress": "http://localhost:53677",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

9 changes: 9 additions & 0 deletions AspireApp.ApiService/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
13 changes: 13 additions & 0 deletions AspireApp.AppHost/AppHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Aspire.Hosting;

var builder = DistributedApplication.CreateBuilder(args);

var cache = builder.AddRedis("RedisCache");

var service = builder.AddProject("service-api", "../AspireApp.ApiService/AspireApp.ApiService.csproj")
.WithReference(cache);

builder.AddProject("client-wasm", "../Client.Wasm/Client.Wasm.csproj")
.WithReference(service);

builder.Build().Run();
21 changes: 21 additions & 0 deletions AspireApp.AppHost/AspireApp.AppHost.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
<UserSecretsId>59f7c075-68cd-4f31-89d0-9f0df5a69013</UserSecretsId>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\AspireApp.ApiService\AspireApp.ApiService.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.AppHost" Version="8.2.2" />
<PackageReference Include="Aspire.Hosting.Redis" Version="8.2.2" />
</ItemGroup>

</Project>
Loading