feat: Перенесены внешние сервисы в Infrastructure. Удалены лишние проекты.

This commit is contained in:
Mikhail Trubnikov
2026-04-14 16:27:27 +10:00
parent 13dd2815d4
commit 188ebc20a0
159 changed files with 354 additions and 1389 deletions

View File

@@ -1,9 +1,13 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using PARR.Core.Common;
using PARR.Core.Common.RabbitServices;
using PARR.Core.Common.Interfaces;
using PARR.Core.Common.Interfaces.RabbitServices;
using PARR.DAL.InfluxDbServices;
using PARR.Domain.Settings;
using PARR.Infrastructure.Interval;
using PARR.Infrastructure.Rabbit;
using PARR.Infrastructure.Redis;
using StackExchange.Redis;
namespace PARR.Infrastructure
{
@@ -19,11 +23,49 @@ namespace PARR.Infrastructure
{
// тут регистрируем все внешние сервисы, Rabbit, Redis, Email...
#region Rabbit
services.AddHttpClient<IRabbitAdminService, RabbitAdminService>();
services.AddTransient<IRabbitService, RabbitService>();
#endregion
#region Interval
services.AddTransient<IIntervalService, IntervalService>();
#endregion
#region Redis
services.AddSingleton<IConnectionMultiplexer>(sp =>
{
// IConnectionMultiplexer - для нативных операций Redis
var connectionString = configuration.GetConnectionString("RedisConnection");
return ConnectionMultiplexer.Connect(connectionString);
});
services.AddStackExchangeRedisCache(opt =>
{
opt.Configuration = configuration.GetConnectionString("RedisConnection");
});
services.AddSingleton<IRedisCacheService, RedisCacheService>();
#endregion
#region InfluxDb
var influxDbSettings = new InfluxDbSettings();
configuration.GetSection(nameof(InfluxDbSettings)).Bind(influxDbSettings);
services.AddSingleton(influxDbSettings);
services.AddTransient<IInfluxDbService, InfluxDbService>();
#endregion
//services.AddTransient<IEmailService, EmailService>();
//services.AddTransient<IRabbitService, RabbitService>();
////Redis (singleton)

View File

@@ -0,0 +1,57 @@
using InfluxDB.Client;
using Microsoft.Extensions.Logging;
using PARR.Core.Common.Interfaces;
using PARR.Domain.Settings;
namespace PARR.DAL.InfluxDbServices
{
internal class InfluxDbService : IInfluxDbService
{
private readonly InfluxDbSettings settings;
private readonly ILogger<InfluxDbService> logger;
public InfluxDbService(InfluxDbSettings settings, ILogger<InfluxDbService> logger)
{
this.settings = settings;
this.logger = logger;
}
public bool Write(Action<WriteApi> action, string token)
{
try
{
using var client = new InfluxDBClient(settings.Url, token);
using (var write = client.GetWriteApi())
{
action(write);
}
return true;
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при записи в БД, InfluxDb");
return false;
}
}
public async Task<T> QueryAsync<T>(Func<QueryApi, Task<T>> action, string token)
{
try
{
using var client = new InfluxDBClient(settings.Url, token);
var query = client.GetQueryApi();
return await action(query);
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при запросе данных из БД, InfuxDb");
// return Task.FromResult();
// return Task.CompletedTask;
//return default(T);
return default(T);
}
}
}
}

View File

@@ -1,5 +1,5 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Common;
using PARR.Core.Common.Interfaces;
namespace PARR.Infrastructure.Interval
{

View File

@@ -7,7 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="InfluxDB.Client" Version="4.17.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.3.9" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="7.0.20" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
</ItemGroup>

View File

@@ -1,5 +1,5 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Common.RabbitServices;
using PARR.Core.Common.Interfaces.RabbitServices;
using PARR.Domain.DTOs.RabbitApi;
using PARR.Domain.Settings;
using System.Net;

View File

@@ -1,5 +1,5 @@
using Microsoft.Extensions.Logging;
using PARR.Core.Common.RabbitServices;
using PARR.Core.Common.Interfaces.RabbitServices;
using PARR.Domain.Common.Rabbit;
using PARR.Domain.Settings;
using RabbitMQ.Client;

View File

@@ -0,0 +1,193 @@
using Microsoft.Extensions.Caching.Distributed;
using PARR.Core.Common.Interfaces;
using StackExchange.Redis;
using System.Text.Json;
namespace PARR.Infrastructure.Redis
{
internal class RedisCacheService : IRedisCacheService
{
private readonly IDistributedCache cache;
private readonly IDatabase redis;
public RedisCacheService(
IDistributedCache cache,
IConnectionMultiplexer connectionMultiplexer
)
{
this.cache = cache;
this.redis = connectionMultiplexer.GetDatabase();
}
#region Распределенный кэш IDistributedCache
public async Task<T?> GetCachedDataAsync<T>(string key)
{
var jsonData = await cache.GetStringAsync(key);
if (jsonData == null)
return default(T);
return JsonSerializer.Deserialize<T>(jsonData);
}
public T? GetCachedData<T>(string key)
{
var jsonData = cache.GetString(key);
if (jsonData == null)
return default(T);
return JsonSerializer.Deserialize<T>(jsonData);
}
public void SetCachedData<T>(string key, T data, TimeSpan cacheDuration)
{
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = cacheDuration
};
var jsonData = JsonSerializer.Serialize(data);
cache.SetString(key, jsonData, options);
}
public async Task SetCachedDataAsync<T>(string key, T data, TimeSpan cacheDuration)
{
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = cacheDuration
};
var jsonData = JsonSerializer.Serialize(data);
await cache.SetStringAsync(key, jsonData, options);
}
public async Task DeleteCachedDataAsync(string key)
{
await cache.RemoveAsync(key);
}
public void DeleteCachedData(string key)
{
cache.Remove(key);
}
#endregion
#region Нативные операции Redis, Redis Hash
public async Task SetHashFieldAsync<T>(string hashKey, string field, T value, TimeSpan? ttl = null)
{
// меняет одно поле в Hash
var jsonData = JsonSerializer.Serialize(value);
// true - поля не было, создалось новое. false - поле было, обновили значение
var result = await redis.HashSetAsync(hashKey, field, jsonData);
// если указан ttl, обновим для всего Hash
// если не указан и ранее был создан hashKey, оставит его ttl; а если hashKey не было, то создаст его БЕССРОЧНЫМ!!!
if (ttl.HasValue)
await SetHashTtlAsync(hashKey, ttl.Value);
}
public async Task<T?> GetHashFieldAsync<T>(string hashKey, string field)
{
// получить значение поля из Hash
var value = await redis.HashGetAsync(hashKey, field);
if (value.IsNullOrEmpty)
return default;
return JsonSerializer.Deserialize<T>(value);
}
public async Task<Dictionary<string, T>> GetAllHashFieldsAsync<T>(string hashKey)
{
// получить все записи из Hash
var objs = await redis.HashGetAllAsync(hashKey);
if (objs.Length == 0)
return new Dictionary<string, T>();
var result = objs.ToDictionary(
t => t.Name.ToString(),
t => JsonSerializer.Deserialize<T>(t.Value)
);
return result!;
}
public async Task DeleteHashFieldAsync(string hashKey, string field)
{
// удалить запись из Hash
await redis.HashDeleteAsync(hashKey, field);
}
public async Task<bool> HashFieldExistsAsync(string hashKey, string field)
{
// есть ли запись в Hash
return await redis.HashExistsAsync(hashKey, field);
}
public async Task DeleteHashAsync(string hashKey)
{
// удалить весь Hash
await redis.KeyDeleteAsync(hashKey);
}
public async Task<long> GetHashLengthAsync(string hashKey)
{
// кол-во записей в hash
return await redis.HashLengthAsync(hashKey);
}
public async Task SetHashTtlAsync(string hashKey, TimeSpan ttl)
{
// Установить ttl для Hash
await redis.KeyExpireAsync(hashKey, ttl);
}
#endregion
#region Helpers
public string GetKey(string[] keyParts, string[]? keyPartsToHash = null)
{
// разделитель между ИД
var mainSeparator = "_";
// разделитель между словами
var wordSeparator = "-";
if (keyParts.Length == 0)
{
throw new ArgumentNullException("keyParts не может быть пустым");
}
var keyStr = string.Join(mainSeparator, keyParts).Replace(" ", wordSeparator);
if (keyPartsToHash != null && keyPartsToHash.Length > 0)
{
var partsToHashStr = string.Join(mainSeparator, keyPartsToHash).Replace(" ", wordSeparator);
using var sha256 = System.Security.Cryptography.SHA256.Create();
var hashedBytes = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(partsToHashStr));
var base64str = Convert.ToBase64String(hashedBytes).Substring(0, 16);
return (keyStr + mainSeparator + base64str).ToLower();
}
return keyStr.ToLower();
}
#endregion
}
}