Skip to content
Closed
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>№3 Интеграционное тестирование </Strong></UnorderedListItem>
<UnorderedListItem>Вариант <Strong>№45 Товар на складе</Strong></UnorderedListItem>
<UnorderedListItem>Выполнена <Strong> Ле Хань Хоанг 6513</Strong> </UnorderedListItem>
<UnorderedListItem><Link To="https://github.com/vieKH/cloud-development">Ссылка на форк</Link></UnorderedListItem>
</UnorderedList>
</CardBody>
</Card>
2 changes: 1 addition & 1 deletion Client.Wasm/wwwroot/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
}
},
"AllowedHosts": "*",
"BaseAddress": ""
"BaseAddress": "https://localhost:7000/api/inventory"
}
100 changes: 98 additions & 2 deletions CloudDevelopment.sln
Original file line number Diff line number Diff line change
@@ -1,20 +1,116 @@

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.11520.95
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}") = "Inventory.ApiService", "InventoryManager\Inventory.ApiService\Inventory.ApiService.csproj", "{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inventory.AppHost", "InventoryManager\Inventory.AppHost\Inventory.AppHost.csproj", "{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inventory.ServiceDefaults", "InventoryManager\Inventory.ServiceDefaults\Inventory.ServiceDefaults.csproj", "{E302BFA1-84FC-63A7-EA3A-7872A83042B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inventory.Gateway", "InventoryManager\Inventory.Gateway\Inventory.Gateway.csproj", "{103A57B1-1180-645E-9B47-C67CDA2CD513}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inventory.Tests", "InventoryManager\Inventory.Tests\Inventory.Tests.csproj", "{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Inventory.FileService", "InventoryManager\Inventory.FileService\Inventory.FileService.csproj", "{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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}.Debug|x64.ActiveCfg = Debug|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Debug|x64.Build.0 = Debug|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Debug|x86.ActiveCfg = Debug|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Debug|x86.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
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|x64.ActiveCfg = Release|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|x64.Build.0 = Release|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|x86.ActiveCfg = Release|Any CPU
{AE7EEA74-2FE0-136F-D797-854FD87E022A}.Release|x86.Build.0 = Release|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Debug|x64.ActiveCfg = Debug|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Debug|x64.Build.0 = Debug|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Debug|x86.ActiveCfg = Debug|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Debug|x86.Build.0 = Debug|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Release|Any CPU.Build.0 = Release|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Release|x64.ActiveCfg = Release|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Release|x64.Build.0 = Release|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Release|x86.ActiveCfg = Release|Any CPU
{E32F4311-CD79-A2FA-0DBA-55DAD48F6377}.Release|x86.Build.0 = Release|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Debug|x64.Build.0 = Debug|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Debug|x86.Build.0 = Debug|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Release|Any CPU.Build.0 = Release|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Release|x64.ActiveCfg = Release|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Release|x64.Build.0 = Release|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Release|x86.ActiveCfg = Release|Any CPU
{AEBB0C4B-1B1C-46E8-ED62-4289C5A76CEE}.Release|x86.Build.0 = Release|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Debug|x64.ActiveCfg = Debug|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Debug|x64.Build.0 = Debug|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Debug|x86.ActiveCfg = Debug|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Debug|x86.Build.0 = Debug|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Release|Any CPU.Build.0 = Release|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Release|x64.ActiveCfg = Release|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Release|x64.Build.0 = Release|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Release|x86.ActiveCfg = Release|Any CPU
{E302BFA1-84FC-63A7-EA3A-7872A83042B9}.Release|x86.Build.0 = Release|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Debug|Any CPU.Build.0 = Debug|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Debug|x64.ActiveCfg = Debug|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Debug|x64.Build.0 = Debug|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Debug|x86.ActiveCfg = Debug|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Debug|x86.Build.0 = Debug|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Release|Any CPU.ActiveCfg = Release|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Release|Any CPU.Build.0 = Release|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Release|x64.ActiveCfg = Release|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Release|x64.Build.0 = Release|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Release|x86.ActiveCfg = Release|Any CPU
{103A57B1-1180-645E-9B47-C67CDA2CD513}.Release|x86.Build.0 = Release|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Debug|x64.ActiveCfg = Debug|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Debug|x64.Build.0 = Debug|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Debug|x86.ActiveCfg = Debug|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Debug|x86.Build.0 = Debug|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Release|Any CPU.Build.0 = Release|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Release|x64.ActiveCfg = Release|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Release|x64.Build.0 = Release|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Release|x86.ActiveCfg = Release|Any CPU
{0F0A6F8E-863E-D920-2530-95CD2F7CD63D}.Release|x86.Build.0 = Release|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Debug|x64.ActiveCfg = Debug|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Debug|x64.Build.0 = Debug|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Debug|x86.ActiveCfg = Debug|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Debug|x86.Build.0 = Debug|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Release|Any CPU.Build.0 = Release|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Release|x64.ActiveCfg = Release|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Release|x64.Build.0 = Release|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Release|x86.ActiveCfg = Release|Any CPU
{B30C51CA-2866-57DA-E0D4-D52385B5F3BE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
17 changes: 17 additions & 0 deletions InventoryManager/Inventory.ApiService/Cache/IInventoryCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Inventory.ApiService.Entity;

namespace Inventory.ApiService.Cache;

/// <summary>
/// Интерфейс сервиса для получения продукта с использованием кэширования.
/// </summary>
public interface IInventoryCache
{
/// <summary>
/// Возвращает продукт по идентификатору из кэша или генерирует его при отсутствии в кэше.
/// </summary>
/// <param name="id"> Идентификатор продукта</param>
/// <param name="ct"> Токен отмены операции</param>
/// <returns> Экземпляр продукта</returns>
public Task<Product> GetAsync(int id, CancellationToken ct);
}
84 changes: 84 additions & 0 deletions InventoryManager/Inventory.ApiService/Cache/InventoryCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System.Text.Json;
using Inventory.ApiService.Entity;
using Inventory.ApiService.Generation;
using Microsoft.Extensions.Caching.Distributed;

namespace Inventory.ApiService.Cache;
/// <summary>
/// Реализация сервиса кэширования для получения продукта.
/// Сначала пытается получить данные из кэша, при отсутствии — генерирует продукт и сохраняет его в кэш.
/// </summary>
/// <param name="cache"> Сервис распределённого кэширования</param>
/// <param name="configuration"> Конфигурация приложения</param>
/// <param name="logger"> Логгер для записи событий</param>
/// <param name="generator"> Генератор </param>
public class InventoryCache(IDistributedCache cache, IConfiguration configuration, ILogger<InventoryCache> logger,Generator generator) : IInventoryCache
{
/// <summary>
/// Возвращает продукт по идентификатору.
/// При наличии в кэше возвращает сохранённые данные, иначе генерирует новый объект и сохраняет его в кэш
/// </summary>
/// <param name="id"> Идентификатор продукта</param>
/// <param name="ct"> Токен отмены операции</param>
/// <returns></returns>
public async Task<Product> GetAsync(int id, CancellationToken ct)
{
var cacheKey = $"inventory-{id}";
logger.LogInformation("Try get product {Id} from cache", id);

string? cachedData = null;

try
{
cachedData = await cache.GetStringAsync(cacheKey, ct);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Cache READ failed for {Id}. Continue without cache.", id);
}

if (!string.IsNullOrEmpty(cachedData))
{
try
{
var cachedProduct = JsonSerializer.Deserialize<Product>(cachedData);
if (cachedProduct is not null)
{
logger.LogInformation("Cache HIT for product {Id}", id);
return cachedProduct;
}

logger.LogWarning("Cache HIT but deserialize returned null for product {Id}", id);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Deserialize failed for product {Id}. Continue without cache.", id);
}
}

logger.LogInformation("Cache MISS for product {Id}. Generating.", id);
var product = generator.Generate(id);

try
{
var expirationMinutes = configuration.GetValue("CacheSettings:ExpirationMinutes", 5);
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(expirationMinutes)
};

await cache.SetStringAsync(cacheKey, JsonSerializer.Serialize(product), options, ct);
logger.LogInformation("Product {Id} saved to cache", id);
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
logger.LogWarning(ex, "Cache WRITE failed for {Id}. Continue without cache.", id);
}

return product;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Inventory.ApiService.Entity;
using Inventory.ApiService.Services;
using Microsoft.AspNetCore.Mvc;

namespace Inventory.ApiService.Controllers;

/// <summary>
/// Контроллер для работы с инвентарём (товарами).
/// Предоставляет методы получения информации о продуктах по идентификатору.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class InventoryController(ILogger<InventoryController> logger, IInventoryService inventoryService) : ControllerBase
{
/// <summary>
/// Получает информацию о продукте из инвентаря по указанному ID.
/// </summary>
/// <param name="id"> Идентификатор продукта (целое неотрицательное число).</param>
/// <param name="ct"> Токен отмены операции.</param>
/// <returns> Объект продукта с кодом 200 OK или ошибку 400 Bad Request, если ID не указан или неверен.</returns>
[HttpGet]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> Get([FromQuery] int? id, CancellationToken ct)
{
if (id is null || id < 0)
return BadRequest("id is required and must be >= 0");

logger.LogInformation("Processing request for inventory {ResourceId}", id);

var product = await inventoryService.GetInventory(id.Value, ct);

return Ok(product);
}
}
57 changes: 57 additions & 0 deletions InventoryManager/Inventory.ApiService/Entity/Product.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
namespace Inventory.ApiService.Entity;

/// <summary>
/// Класс, представляющий товар на складе
/// </summary>
public class Product
{
/// <summary>
/// Идентификатор в системе
/// </summary>
public int Id { get; set; }

/// <summary>
/// Наименование товара
/// </summary>
public string NameProduct { get; set; } = string.Empty;

/// <summary>
/// Категория товара
/// </summary>
public string Category { get; set; } = string.Empty;

/// <summary>
/// Количество на складе
/// </summary>
public int Quantity { get; set; }

/// <summary>
/// Цена за единицу товара
/// </summary>
public decimal Price { get; set; }

/// <summary>
/// Вес единицы товара
/// </summary>
public double Weight { get; set; }

/// <summary>
/// Габариты единицы товара
/// </summary>
public string Dimension { get; set; } = string.Empty;

/// <summary>
/// Товар хрупкий
/// </summary>
public bool IsFragile { get; set; }

/// <summary>
/// Дата последней поставки
/// </summary>
public DateOnly LastDeliveryDate { get; set; }

/// <summary>
/// Дата следующей поставки
/// </summary>
public DateOnly NextDeliveryDate { get; set; }
}
43 changes: 43 additions & 0 deletions InventoryManager/Inventory.ApiService/Generation/Generator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Bogus;
using Inventory.ApiService.Entity;

namespace Inventory.ApiService.Generation;
/// <summary>
/// Сервис генерации тестовых данных продукта.Использует библиотеку Bogus для создания случайных значений.
/// </summary>
public class Generator
{
private static readonly Faker<Product> _faker = new Faker<Product>()
.RuleFor(x => x.NameProduct, f => f.Commerce.ProductName())
.RuleFor(x => x.Category, f => f.Commerce.Categories(1)[0])
.RuleFor(x => x.Quantity, f => f.Random.Int(0, 1000))
.RuleFor(x => x.Price, f => Math.Round(f.Random.Decimal(1, 10000), 2))
.RuleFor(x => x.Weight, f => Math.Round(f.Random.Double(0.1, 100), 2))
.RuleFor(x => x.Dimension, f =>
{
var a = f.Random.Int(1, 200);
var b = f.Random.Int(1, 200);
var c = f.Random.Int(1, 200);
return $"{a}×{b}×{c} cm";
})
.RuleFor(x => x.IsFragile, f => f.Random.Bool())
.RuleFor(x => x.LastDeliveryDate, f => DateOnly.FromDateTime(f.Date.Past(2)))
.RuleFor(x => x.NextDeliveryDate, (f, item) =>
{
var lastDate = item.LastDeliveryDate.ToDateTime(TimeOnly.MinValue);
var nextDate = f.Date.Between(lastDate, lastDate.AddMonths(6));
return DateOnly.FromDateTime(nextDate);
});

/// <summary>
/// Генерирует продукт по заданному идентификатору.
/// </summary>
/// <param name="id"> Идентификатор продукта</param>
/// <returns> Сгенерированный объект продукта</returns>
public Product Generate(int id)
{
var product = _faker.Generate();
product.Id = id;
return product;
}
}
Loading