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>№1 "Кэширование"</Strong></UnorderedListItem>
<UnorderedListItem>Вариант <Strong>№46 "Программный проект"</Strong></UnorderedListItem>
<UnorderedListItem>Выполнена <Strong>Митеревым Дмитрием 6513</Strong> </UnorderedListItem>
<UnorderedListItem><Link To=" https://github.com/Dmiterev/cloud-development">Ссылка на форк</Link></UnorderedListItem>
</UnorderedList>
</CardBody>
</Card>
6 changes: 3 additions & 3 deletions Client.Wasm/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5127",
"environmentVariables": {
Expand All @@ -22,7 +22,7 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7282;http://localhost:5127",
"environmentVariables": {
Expand All @@ -31,7 +31,7 @@
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchBrowser": false,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
Expand Down
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:7046/generate"
}
24 changes: 24 additions & 0 deletions CloudDevelopment.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ VisualStudioVersion = 17.14.36811.4
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}") = "ProjectGenerator.AppHost", "ProjectGenerator\ProjectGenerator.AppHost\ProjectGenerator.AppHost.csproj", "{2BD86B8F-8507-43F0-B17F-9607B7352733}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectGenerator.ServiceDefaults", "ProjectGenerator\ProjectGenerator.ServiceDefaults\ProjectGenerator.ServiceDefaults.csproj", "{8F9985A2-9D06-28F9-3428-8039644EF545}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectGenerator.Api", "ProjectGenerator.Api\ProjectGenerator.Api.csproj", "{AAEE18D8-2A8E-7273-1128-2FA0B1FA40F9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProjectGenerator.Domain", "ProjectGenerator.Domain\ProjectGenerator.Domain.csproj", "{67E00678-695C-407F-B401-27C9EC3527B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +23,22 @@ 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
{2BD86B8F-8507-43F0-B17F-9607B7352733}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2BD86B8F-8507-43F0-B17F-9607B7352733}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2BD86B8F-8507-43F0-B17F-9607B7352733}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2BD86B8F-8507-43F0-B17F-9607B7352733}.Release|Any CPU.Build.0 = Release|Any CPU
{8F9985A2-9D06-28F9-3428-8039644EF545}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F9985A2-9D06-28F9-3428-8039644EF545}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F9985A2-9D06-28F9-3428-8039644EF545}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8F9985A2-9D06-28F9-3428-8039644EF545}.Release|Any CPU.Build.0 = Release|Any CPU
{AAEE18D8-2A8E-7273-1128-2FA0B1FA40F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AAEE18D8-2A8E-7273-1128-2FA0B1FA40F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AAEE18D8-2A8E-7273-1128-2FA0B1FA40F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AAEE18D8-2A8E-7273-1128-2FA0B1FA40F9}.Release|Any CPU.Build.0 = Release|Any CPU
{67E00678-695C-407F-B401-27C9EC3527B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{67E00678-695C-407F-B401-27C9EC3527B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{67E00678-695C-407F-B401-27C9EC3527B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{67E00678-695C-407F-B401-27C9EC3527B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
30 changes: 30 additions & 0 deletions ProjectGenerator.Api/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using ProjectGenerator.Api.Services;

var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();
builder.AddRedisDistributedCache("cache");

builder.Services.AddSingleton<ISoftwareProjectGenerator, SoftwareProjectGenerator>();
builder.Services.AddScoped<ISoftwareProjectService, SoftwareProjectService>();

var trustedUrls = builder.Configuration.GetSection("CorsOrigins").Get<string[]>() ?? [];
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins(trustedUrls)
.WithMethods("GET")
.WithHeaders("Content-Type");
});
});

var app = builder.Build();

app.MapDefaultEndpoints();
app.UseCors();

app.MapGet("/generate", async (int id, ISoftwareProjectService service) =>
await service.GetOrGenerate(id));

app.Run();
23 changes: 23 additions & 0 deletions ProjectGenerator.Api/ProjectGenerator.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>
</PropertyGroup>

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

<ItemGroup>
<Folder Include="Services\" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Aspire.StackExchange.Redis.DistributedCaching" Version="9.5.2" />
<PackageReference Include="Bogus" Version="35.6.5" />
</ItemGroup>

</Project>
38 changes: 38 additions & 0 deletions ProjectGenerator.Api/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:10098",
"sslPort": 44392
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7046;http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
14 changes: 14 additions & 0 deletions ProjectGenerator.Api/Services/ISoftwareProjectGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ProjectGenerator.Domain.Models;

namespace ProjectGenerator.Api.Services;

/// <summary>
/// Интерфейс генератора программных проектов
/// </summary>
public interface ISoftwareProjectGenerator
{
/// <summary>
/// Генерирует программный проект с указанным идентификатором
/// </summary>
public SoftwareProject Generate(int id);
}
14 changes: 14 additions & 0 deletions ProjectGenerator.Api/Services/ISoftwareProjectService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using ProjectGenerator.Domain.Models;

namespace ProjectGenerator.Api.Services;

/// <summary>
/// Интерфейс сервиса программных проектов
/// </summary>
public interface ISoftwareProjectService
{
/// <summary>
/// Получает программный проект по идентификатору из кэша или генерирует новый
/// </summary>
public Task<SoftwareProject> GetOrGenerate(int id);
}
62 changes: 62 additions & 0 deletions ProjectGenerator.Api/Services/SoftwareProjectGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Bogus;
using Bogus.DataSets;
using ProjectGenerator.Domain.Models;

namespace ProjectGenerator.Api.Services;

/// <summary>
/// Генератор программных проектов на основе Bogus
/// </summary>
public class SoftwareProjectGenerator : ISoftwareProjectGenerator
{
private readonly Faker<SoftwareProject> _faker = new Faker<SoftwareProject>("ru")
.RuleFor(p => p.Id, _ => 0)
.RuleFor(p => p.ProjectName, f =>
$"{f.Commerce.ProductName()} {f.Hacker.Adjective()} {f.Finance.AccountName()} {f.Lorem.Word()}")
.RuleFor(p => p.Customer, f => f.Company.CompanyName())
.RuleFor(p => p.ProjectManager, f =>
{
var gender = f.PickRandom<Name.Gender>();
return
$"{f.Name.LastName(gender)} {f.Name.FirstName(gender)} " +
$"{GeneratePatronymic(f.Name.FirstName(Name.Gender.Male), gender == Name.Gender.Female)}";
})
.RuleFor(p => p.StartDate, f =>
DateOnly.FromDateTime(f.Date.Past(2)))
.RuleFor(p => p.PlannedEndDate, (f, p) =>
p.StartDate.AddDays(f.Random.Int(30, 365)))
.RuleFor(p => p.ActualEndDate, (f, p) =>
f.Random.Bool(0.3f)
? p.StartDate.AddDays(f.Random.Int(10, 400))
: null)
.RuleFor(p => p.Budget, f =>
Math.Round(f.Finance.Amount(100_000, 100_000_000), 2))
.RuleFor(p => p.ActualCosts, (f, p) =>
Math.Round(p.Budget * f.Random.Decimal(0.01m, 1.2m), 2))
.RuleFor(p => p.CompletionPercentage, (f, p) =>
p.ActualEndDate.HasValue ? 100 : f.Random.Int(0, 99));

/// <summary>
/// Генерация отчества на основе мужского имени
/// </summary>
/// <param name="maleName">Мужское имя</param>
/// <param name="isFemale">Признак женского пола</param>
/// <returns>Отчество</returns>
private static string GeneratePatronymic(string maleName, bool isFemale) => maleName switch
{
_ when maleName.EndsWith("ий") => maleName[..^2] + (isFemale ? "ьевна" : "ьевич"),
_ when maleName.EndsWith('ь') => maleName[..^1] + (isFemale ? "евна" : "евич"),
_ when maleName.EndsWith('й') => maleName[..^1] + (isFemale ? "евна" : "евич"),
_ when maleName.EndsWith('а') || maleName.EndsWith('я') => maleName[..^1] + (isFemale ? "ична" : "ич"),
_ => maleName + (isFemale ? "овна" : "ович")
};

/// <inheritdoc />
public SoftwareProject Generate(int id)
{
var project = _faker.Generate();
project.Id = id;
return project;
}

}
90 changes: 90 additions & 0 deletions ProjectGenerator.Api/Services/SoftwareProjectService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
using ProjectGenerator.Domain.Models;

namespace ProjectGenerator.Api.Services;

/// <summary>
/// Сервис программных проектов с кэшированием
/// </summary>
/// <param name="generator">Генератор программных проектов</param>
/// <param name="cache">Распределённый кэш</param>
/// <param name="configuration">Конфигурация приложения</param>
/// <param name="logger">Логгер</param>
public class SoftwareProjectService(
ISoftwareProjectGenerator generator,
IDistributedCache cache,
IConfiguration configuration,
ILogger<SoftwareProjectService> logger) : ISoftwareProjectService
{
private const string CacheKeyPrefix = "software-project";
private readonly TimeSpan _cacheTtl = TimeSpan.FromMinutes(configuration.GetValue("CacheTtlMinutes", 15));

/// <inheritdoc />
public async Task<SoftwareProject> GetOrGenerate(int id)
{
var cacheKey = $"{CacheKeyPrefix}:{id}";

var cached = await GetFromCache(cacheKey);
if (cached is not null)
{
return cached;
}

logger.LogInformation("Cache miss for id {Id}, generating new data", id);

var project = generator.Generate(id);

await SetToCache(cacheKey, project);

return project;
}

/// <summary>
/// Получение программного проекта из кэша
/// </summary>
/// <param name="cacheKey">Ключ кэша</param>
/// <returns>Программный проект или null</returns>
private async Task<SoftwareProject?> GetFromCache(string cacheKey)
{
try
{
var cached = await cache.GetStringAsync(cacheKey);
if (cached is null)
{
return null;
}

logger.LogInformation("Cache hit for key {CacheKey}", cacheKey);
return JsonSerializer.Deserialize<SoftwareProject>(cached);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to read from cache for key {CacheKey}", cacheKey);
return null;
}
}

/// <summary>
/// Сохранение программного проекта в кэш
/// </summary>
/// <param name="cacheKey">Ключ кэша</param>
/// <param name="project">Программный проект</param>
private async Task SetToCache(string cacheKey, SoftwareProject project)
{
try
{
var json = JsonSerializer.Serialize(project);
await cache.SetStringAsync(cacheKey, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = _cacheTtl
});

logger.LogInformation("Cached data for key {CacheKey}", cacheKey);
}
catch (Exception ex)
{
logger.LogWarning(ex, "Failed to write to cache for key {CacheKey}", cacheKey);
}
}
}
8 changes: 8 additions & 0 deletions ProjectGenerator.Api/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
14 changes: 14 additions & 0 deletions ProjectGenerator.Api/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"CacheTtlMinutes": 15,
"CorsOrigins": [
"http://localhost:5127",
"https://localhost:7282"
]
}
Loading
Loading