1 Commits

Author SHA1 Message Date
6160732a66 Сделал 4 лабу 2025-10-21 23:21:44 +04:00
14 changed files with 412 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**

View File

@@ -0,0 +1,70 @@
using Microsoft.AspNetCore.Mvc;
using ProductMicroservice.Models;
using System.Linq;
[ApiController]
[Route("api/[controller]")]
public class CategoryController : ControllerBase
{
private readonly InMemoryStore _db;
public CategoryController(InMemoryStore db) => _db = db;
[HttpGet]
public ActionResult<IEnumerable<Category>> GetAll([FromQuery] string? q = null)
{
var data = _db.Categories.AsEnumerable();
if (!string.IsNullOrWhiteSpace(q))
data = data.Where(c => c.Name.Contains(q, StringComparison.OrdinalIgnoreCase));
return Ok(data);
}
[HttpGet("{id:int}")]
public ActionResult<Category> GetById(int id)
{
var item = _db.Categories.FirstOrDefault(c => c.Id == id);
return item is null ? NotFound(new { message = "Категория не найдена" }) : Ok(item);
}
[HttpPost]
public ActionResult<Category> Create([FromBody] Category model)
{
if (!ModelState.IsValid) return ValidationProblem(ModelState);
if (_db.Categories.Any(c => string.Equals(c.Name, model.Name, StringComparison.OrdinalIgnoreCase)))
return Conflict(new { message = $"Категория '{model.Name}' уже существует" });
var created = _db.AddCategory(new Category { Name = model.Name.Trim() });
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
}
[HttpPut("{id:int}")]
public ActionResult<Category> Update(int id, [FromBody] Category model)
{
if (!ModelState.IsValid) return ValidationProblem(ModelState);
var item = _db.Categories.FirstOrDefault(c => c.Id == id);
if (item is null) return NotFound(new { message = "Категория не найдена" });
if (_db.Categories.Any(c => c.Id != id && string.Equals(c.Name, model.Name, StringComparison.OrdinalIgnoreCase)))
return Conflict(new { message = $"Категория '{model.Name}' уже существует" });
item.Name = model.Name.Trim();
return Ok(item);
}
[HttpDelete("{id:int}")]
public IActionResult Delete(int id)
{
var item = _db.Categories.FirstOrDefault(c => c.Id == id);
if (item is null) return NotFound(new { message = "Категория не найдена" });
if (_db.Products.Any(p => p.CategoryId == id))
return Conflict(new { message = "Нельзя удалить категорию: есть связанные продукты" });
_db.Categories.Remove(item);
return NoContent();
}
}

View File

@@ -0,0 +1,85 @@
using Microsoft.AspNetCore.Mvc;
using ProductMicroservice.Models;
using System.Linq;
[ApiController]
[Route("api/[controller]")]
public class ProductController : ControllerBase
{
private readonly InMemoryStore _db;
public ProductController(InMemoryStore db) => _db = db;
[HttpGet]
public ActionResult<IEnumerable<Product>> GetAll(
[FromQuery] int? categoryId = null,
[FromQuery] string? name = null,
[FromQuery] decimal? minPrice = null,
[FromQuery] decimal? maxPrice = null)
{
var data = _db.Products.AsEnumerable();
if (categoryId is not null) data = data.Where(p => p.CategoryId == categoryId);
if (!string.IsNullOrWhiteSpace(name))
data = data.Where(p => p.Name.Contains(name, StringComparison.OrdinalIgnoreCase));
if (minPrice is not null) data = data.Where(p => p.Price >= minPrice);
if (maxPrice is not null) data = data.Where(p => p.Price <= maxPrice);
return Ok(data);
}
[HttpGet("{id:int}")]
public ActionResult<Product> GetById(int id)
{
var item = _db.Products.FirstOrDefault(p => p.Id == id);
return item is null ? NotFound(new { message = "Продукт не найден" }) : Ok(item);
}
[HttpPost]
public ActionResult<Product> Create([FromBody] Product model)
{
if (!ModelState.IsValid) return ValidationProblem(ModelState);
if (!_db.Categories.Any(c => c.Id == model.CategoryId))
return BadRequest(new { message = "Указанная категория не существует" });
var created = _db.AddProduct(new Product
{
Name = model.Name.Trim(),
Price = model.Price,
CategoryId = model.CategoryId
});
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
}
[HttpPut("{id:int}")]
public ActionResult<Product> Update(int id, [FromBody] Product model)
{
if (!ModelState.IsValid) return ValidationProblem(ModelState);
var item = _db.Products.FirstOrDefault(p => p.Id == id);
if (item is null) return NotFound(new { message = "Продукт не найден" });
if (!_db.Categories.Any(c => c.Id == model.CategoryId))
return BadRequest(new { message = "Указанная категория не существует" });
item.Name = model.Name.Trim();
item.Price = model.Price;
item.CategoryId = model.CategoryId;
return Ok(item);
}
[HttpDelete("{id:int}")]
public IActionResult Delete(int id)
{
var item = _db.Products.FirstOrDefault(p => p.Id == id);
if (item is null) return NotFound(new { message = "Продукт не найден" });
_db.Products.Remove(item);
return NoContent();
}
}

View File

@@ -0,0 +1,30 @@
# См. статью по ссылке https://aka.ms/customizecontainer, чтобы узнать как настроить контейнер отладки и как Visual Studio использует этот Dockerfile для создания образов для ускорения отладки.
# Этот этап используется при запуске из VS в быстром режиме (по умолчанию для конфигурации отладки)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
# Этот этап используется для сборки проекта службы
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ProductMicroservice.csproj", "."]
RUN dotnet restore "./ProductMicroservice.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "./ProductMicroservice.csproj" -c $BUILD_CONFIGURATION -o /app/build
# Этот этап используется для публикации проекта службы, который будет скопирован на последний этап
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./ProductMicroservice.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Этот этап используется в рабочей среде или при запуске из VS в обычном режиме (по умолчанию, когда конфигурация отладки не используется)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ProductMicroservice.dll"]

View File

@@ -0,0 +1,7 @@
namespace ProductMicroservice.Models;
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

View File

@@ -0,0 +1,9 @@
namespace ProductMicroservice.Models;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Price { get; set; }
public int CategoryId { get; set; }
}

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>4a831111-1a8f-4dd4-b364-44bd98f36e3b</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>.</DockerfileContext>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.22.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@ProductMicroservice_HostAddress = http://localhost:5186
GET {{ProductMicroservice_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36401.2 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProductMicroservice", "ProductMicroservice.csproj", "{CA014D50-626B-4303-80DC-362AA95C15F6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CA014D50-626B-4303-80DC-362AA95C15F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CA014D50-626B-4303-80DC-362AA95C15F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CA014D50-626B-4303-80DC-362AA95C15F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CA014D50-626B-4303-80DC-362AA95C15F6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2EC5F8EE-6748-4CEB-9C6F-A266F67E1176}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,26 @@
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddCors(o =>
{
o.AddDefaultPolicy(p => p
.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod());
});
builder.Services.AddSingleton<InMemoryStore>();
var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.UseCors();
app.MapControllers();
app.Run();

View File

@@ -0,0 +1,52 @@
{
"profiles": {
"http": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5186"
},
"https": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:7112;http://localhost:5186"
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Container (Dockerfile)": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
},
"publishAllPorts": true,
"useSSL": true
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:46293",
"sslPort": 44338
}
}
}

View File

@@ -0,0 +1,38 @@
using ProductMicroservice.Models;
public class InMemoryStore
{
public List<Category> Categories { get; } = new();
public List<Product> Products { get; } = new();
private int _catId = 0;
private int _prodId = 0;
private readonly object _lock = new();
public InMemoryStore()
{
AddCategory(new Category { Name = "Молочные изделия" });
AddCategory(new Category { Name = "Овощи" });
AddProduct(new Product { Name = "Молоко 2.5%", Price = 69.9m, CategoryId = 1 });
}
public Category AddCategory(Category c)
{
lock (_lock)
{
c.Id = ++_catId;
Categories.Add(c);
return c;
}
}
public Product AddProduct(Product p)
{
lock (_lock)
{
p.Id = ++_prodId;
Products.Add(p);
return p;
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}