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,14 +0,0 @@
namespace PARR.DAL.Cache.Models.Base
{
/// <summary>
/// Базовый интерфейс для КЭШ моделей
/// </summary>
public interface IBaseCache<T>
{
public T Data { get; set; }
public DateTimeOffset Timestamp { get; set; }
public string Source { get; set; }
}
}

View File

@@ -1,27 +0,0 @@
using PARR.DAL.Cache.Models.Base;
using PARR.Domain.Enums;
namespace PARR.DAL.Cache.Models
{
/// <summary>
/// КЭШ модель, состояние matching`a
/// </summary>
public class MatchingStatusItem : IBaseCache<MatchingStatusItemDto>
{
public required MatchingStatusItemDto Data { get; set; }
public DateTimeOffset Timestamp { get; set; }
public required string Source { get; set; }
}
public class MatchingStatusItemDto
{
public DateTimeOffset DateStart { get; set; }
public required TemplateMatcherActionEnum Action { get; set; }
public string? Comment { get; set; }
}
}

View File

@@ -1,18 +0,0 @@
using PARR.DAL.Cache.Models.Base;
namespace PARR.DAL.Cache.Models
{
public class UnitFilterIds : IBaseCache<UnitFilterIdsDto>
{
public required UnitFilterIdsDto Data { get; set; }
public DateTimeOffset Timestamp { get; set; }
public required string Source { get; set; }
}
public class UnitFilterIdsDto
{
public List<Guid> UnitIds { get; set; } = new();
}
}

View File

@@ -1,132 +0,0 @@
namespace PARR.DAL.Cache.Services.Base
{
public interface IRedisCacheService
{
/// <summary>
/// Получить кэшированные данные
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
T? GetCachedData<T>(string key);
/// <summary>
/// Получить кэшированные данные асинхронно
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
Task<T?> GetCachedDataAsync<T>(string key);
/// <summary>
/// Добавить в кэш данные
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="data"></param>
/// <param name="cacheDuration"></param>
void SetCachedData<T>(string key, T data, TimeSpan cacheDuration);
/// <summary>
/// Добавить в кэш данные асинхронно
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="data"></param>
/// <param name="cacheDuration"></param>
/// <returns></returns>
Task SetCachedDataAsync<T>(string key, T data, TimeSpan cacheDuration);
/// <summary>
/// Удалить кэшированные данные
/// </summary>
/// <param name="key"></param>
void DeleteCachedData(string key);
/// <summary>
/// Удалить кэшированные данные асинхронно
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
Task DeleteCachedDataAsync(string key);
/// <summary>
/// Сгенерировать уникальный ключ
/// </summary>
/// <param name="keyParts"></param>
/// <param name="keyPartsToHash"></param>
/// <returns></returns>
string GetKey(string[] keyParts, string[]? keyPartsToHash = null);
#region Работа с Hash
/// <summary>
/// Изменить одно поле в Hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="hashKey"></param>
/// <param name="field"></param>
/// <param name="value"></param>
/// <param name="ttl">Если указано, обновится у всего Hash. Если не указано и hash не существовал, создастся Hash с бесокнечным ttl</param>
/// <returns></returns>
Task SetHashFieldAsync<T>(string hashKey, string field, T value, TimeSpan? ttl = null);
/// <summary>
/// Получить значение поля из Hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="hashKey"></param>
/// <param name="field"></param>
/// <returns></returns>
Task<T?> GetHashFieldAsync<T>(string hashKey, string field);
/// <summary>
/// Получить все значения из Hash
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="hashKey"></param>
/// <returns></returns>
Task<Dictionary<string, T>> GetAllHashFieldsAsync<T>(string hashKey);
/// <summary>
/// Удалить одну запись из Hash
/// </summary>
/// <param name="hashKey"></param>
/// <param name="field"></param>
/// <returns></returns>
Task DeleteHashFieldAsync(string hashKey, string field);
/// <summary>
/// Есть ли запись в Hash
/// </summary>
/// <param name="hashKey"></param>
/// <param name="field"></param>
/// <returns></returns>
Task<bool> HashFieldExistsAsync(string hashKey, string field);
/// <summary>
/// Удалить весь Hash
/// </summary>
/// <param name="hashKey"></param>
/// <returns></returns>
Task DeleteHashAsync(string hashKey);
/// <summary>
/// Получить количество записей в Hash
/// </summary>
/// <param name="hashKey"></param>
/// <returns></returns>
Task<long> GetHashLengthAsync(string hashKey);
/// <summary>
/// Установить TTL для Hash
/// </summary>
/// <param name="hashKey"></param>
/// <param name="ttl"></param>
/// <returns></returns>
Task SetHashTtlAsync(string hashKey, TimeSpan ttl);
#endregion
}
}

View File

@@ -1,192 +0,0 @@
using Microsoft.Extensions.Caching.Distributed;
using StackExchange.Redis;
using System.Text.Json;
namespace PARR.DAL.Cache.Services.Base
{
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
}
}

View File

@@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using PARR.BLL.Domain;
using PARR.DAL.Contracts;
using PARR.DAL.Extensions;
using PARR.DAL.Models;
@@ -7,6 +6,7 @@ using PARR.DAL.Models.Job;
using PARR.DAL.Models.Schedule;
using PARR.DAL.Models.Unit;
using PARR.Domain.Common.Roles;
using PARR.Domain.Common.Template;
using PARR.Domain.Entities.TaskEntities;
using PARR.Domain.Enums;

View File

@@ -1,5 +1,5 @@
using PARR.BLL.Domain;
using PARR.DAL.Extensions;
using PARR.DAL.Extensions;
using PARR.Domain.Common.Template;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text.Json;

View File

@@ -1,4 +1,4 @@
using PARR.DAL.Cache.Models;
using PARR.Domain.Cache.Models;
namespace PARR.DAL.DomainModels
{

View File

@@ -1,9 +1,9 @@
using Microsoft.EntityFrameworkCore;
using PARR.DAL.Cache.Models;
using PARR.DAL.Cache.Services.Base;
using PARR.Core.Common.Interfaces;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.Domain.Cache.Models;
using PARR.Domain.Enums;
namespace PARR.DAL.DomainServices.Implementations

View File

@@ -1,5 +1,5 @@
using PARR.DAL.Cache.Models;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainModels;
using PARR.Domain.Cache.Models;
using PARR.Domain.Enums;
namespace PARR.DAL.DomainServices.Interfaces

View File

@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.DAL.Cache.Services.Base;
using PARR.Core.Common.Interfaces;
using PARR.DAL.Contracts;
using PARR.DAL.DomainModels;
using PARR.DAL.DomainServices.Shortcodes.Models;
@@ -10,6 +10,7 @@ using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using PARR.Domain.Common.Template;
using PARR.Domain.Enums;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
@@ -667,7 +668,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
return input.Replace(shortcode, replacement, StringComparison.OrdinalIgnoreCase);
}
private static string ReplaceConstants(List<BLL.Domain.TemplateNameConstantPart> nameConstants, string resultName)
private static string ReplaceConstants(List<TemplateNameConstantPart> nameConstants, string resultName)
{
foreach (var item in nameConstants)
resultName = resultName.Replace($"%{item.Name}%", item.Value);

View File

@@ -1,14 +1,13 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using PARR.DAL.Cache.Models;
using PARR.DAL.Cache.Services.Base;
using PARR.DAL.Contracts;
using PARR.Core.Common.Interfaces;
using PARR.DAL.DomainServices.UnitFilterService.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.Models.Unit;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using PARR.Domain.Cache.Models;
using PARR.Domain.Enums;
using System.Diagnostics;

View File

@@ -1,10 +0,0 @@
using InfluxDB.Client;
namespace PARR.DAL.InfluxDbServices
{
public interface IInfluxDbService
{
Task<T> QueryAsync<T>(Func<QueryApi, Task<T>> action, string token);
bool Write(Action<WriteApi> action, string token);
}
}

View File

@@ -1,56 +0,0 @@
using InfluxDB.Client;
using Microsoft.Extensions.Logging;
using PARR.DAL.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

@@ -7,12 +7,10 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="InfluxDB.Client" Version="4.17.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="7.0.12" />
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.4" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />

View File

@@ -3,7 +3,6 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces.TaskRepositories;
using PARR.DAL.Cache.Services.Base;
using PARR.DAL.Configurations.DbSettings;
using PARR.DAL.Context;
using PARR.DAL.Contracts;
@@ -12,7 +11,6 @@ using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.UnitFilterService;
using PARR.DAL.DomainServices.UnitFilterService.Models;
using PARR.DAL.InfluxDbServices;
using PARR.DAL.NextRunServices;
using PARR.DAL.NextRunServices.Subservices;
using PARR.DAL.Repositories.TaskRepositories;
@@ -27,7 +25,6 @@ using PARR.DAL.Services.Interfaces.Schedule;
using PARR.DAL.Services.Interfaces.Unit;
using PARR.DAL.Settings;
using PARR.DAL.TaskServices;
using StackExchange.Redis;
namespace PARR.DAL
{
@@ -54,36 +51,36 @@ namespace PARR.DAL
#region Redis + cache services
services.AddSingleton<IConnectionMultiplexer>(sp =>
{
// IConnectionMultiplexer - для нативных операций Redis
var connectionString = configuration.GetConnectionString("RedisConnection");
return ConnectionMultiplexer.Connect(connectionString);
});
//services.AddSingleton<IConnectionMultiplexer>(sp =>
//{
// // IConnectionMultiplexer - для нативных операций Redis
// var connectionString = configuration.GetConnectionString("RedisConnection");
// return ConnectionMultiplexer.Connect(connectionString);
//});
services.AddStackExchangeRedisCache(opt =>
{
opt.Configuration = configuration.GetConnectionString("RedisConnection");
});
//services.AddStackExchangeRedisCache(opt =>
//{
// opt.Configuration = configuration.GetConnectionString("RedisConnection");
//});
var groupedShortcodesCacheSettings = new GroupedShortcodesCacheSettings();
configuration.GetSection(nameof(GroupedShortcodesCacheSettings)).Bind(groupedShortcodesCacheSettings);
services.AddSingleton(groupedShortcodesCacheSettings);
services.AddSingleton<IRedisCacheService, RedisCacheService>();
//services.AddSingleton<IRedisCacheService, RedisCacheService>();
#endregion
#region InfluxDb
//#region InfluxDb
var influxDbSettings = new InfluxDbSettings();
configuration.GetSection(nameof(InfluxDbSettings)).Bind(influxDbSettings);
services.AddSingleton(influxDbSettings);
//var influxDbSettings = new InfluxDbSettings();
//configuration.GetSection(nameof(InfluxDbSettings)).Bind(influxDbSettings);
//services.AddSingleton(influxDbSettings);
services.AddTransient<IInfluxDbService, InfluxDbService>();
//services.AddTransient<IInfluxDbService, InfluxDbService>();
#endregion
//#endregion
// Entity services

View File

@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.DAL.Cache.Services.Base;
using PARR.Core.Common.Interfaces;
using PARR.DAL.Context;
using PARR.DAL.Contracts;
using PARR.DAL.Models;

View File

@@ -1,32 +0,0 @@
namespace PARR.DAL.Settings
{
public class InfluxDbSettings
{
public string Url { get; set; } = string.Empty;
public string Organization { get; set; } = string.Empty;
public InfluxDbBucketRobotsSettings? Robots { get; set; }
public InfluxDbBucketLogonsSettings? Logons { get; set; }
}
public class InfluxDbBucketRobotsSettings : IInfluxDbBucketSettings
{
public string Bucket { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
}
public class InfluxDbBucketLogonsSettings : IInfluxDbBucketSettings
{
public string Bucket { get; set; } = string.Empty;
public string Token { get; set; } = string.Empty;
}
public interface IInfluxDbBucketSettings
{
public string Bucket { get; set; }
public string Token { get; set; }
}
}