using MP.Data;
using Newtonsoft.Json;
using NLog;
using StackExchange.Redis;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace MP.IOC.Services
{
///
/// Classe base per i servizi che fornisce funzionalità comuni come
/// - connessione a Redis
/// - configurazione
/// - gestione dei messaggi
/// - strategie di caching.
///
/// Questa classe agisce come modello per altri servizi derivati.
///
public class BaseServ
{
#region Public Constructors
///
/// Inizializza una nuova istanza della classe BaseServ.
/// Configura la connessione Redis, carica le impostazioni di configurazione e inizializza il serializzatore JSON.
///
/// Oggetto di configurazione per recuperare le impostazioni dell'applicazione.
/// Multiplexer di connessione Redis per operazioni sul database.
public BaseServ(IConfiguration Configuration, IConnectionMultiplexer RedisConn)
{
_config = Configuration;
_redisConn = RedisConn;
_redisDb = _redisConn.GetDatabase();
// configuro la base key x la cache Redis, con verifica contenga Cache finale
_redisBaseKey = _config.GetValue("ServerConf:RedisBaseKey") ?? "Lux:Cache";
// aggiungo cache se non finisse per ":cache"
if (!_redisBaseKey.EndsWith(":Cache"))
{
_redisBaseKey += ":Cache";
}
// Configurazione serializzatore JSON per risolvere errore di loop circolare
JSSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
}
#endregion Public Constructors
#region Public Properties
///
/// Pipe dei messaggi per la comunicazione riguardo update calcolo BOM
///
public MessagePipe PipeBom { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno HwList da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelHwList.
///
public MessagePipe PipeHwList { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno HwOptions calcolate da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelHwOpt.
///
public MessagePipe PipeHwOpt { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno PNG calcolati da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelPng.
///
public MessagePipe PipePng { get; set; } = null!;
///
/// Canale informazioni relativo ad attività relative alla gesitone PROD:
/// - carico macchine
/// - scheduling
///
public MessagePipe PipeProd { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno info elementi del profile
///
public MessagePipe PipeProfElement { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno ProfileListAsync calcolate da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelProfList.
///
public MessagePipe PipeProfList { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno Shape calcolate da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelShape.
///
public MessagePipe PipeShape { get; set; } = null!;
///
/// Pipe dei messaggi per ritorno SVG calcolati da Engine di calcolo verso interfaccia utente.
/// I messaggi vengono inviati sul canale Redis definito da ChannelSvg.
///
public MessagePipe PipeSvg { get; set; } = null!;
///
/// Pipe dei messaggi per la comunicazione riguardo update generico UI
///
public MessagePipe PipeUpdate { get; set; } = null!;
#endregion Public Properties
#region Protected Fields
///
/// Oggetto per collezione dati Activity (span in Uptrace)
///
protected static readonly ActivitySource ActivitySource = new ActivitySource("Lux.DATA");
///
/// Oggetto logger utilizzato per registrare eventi e errori a livello di classe.
/// Utile per il monitoraggio del comportamento dell'applicazione e la risoluzione di problemi.
///
protected static Logger Log = LogManager.GetCurrentClassLogger();
///
/// Oggetto di configurazione statico per accedere alle impostazioni dell'applicazione (es. stringhe di connessione).
/// Condiviso tra tutte le istanze di BaseServ.
///
protected readonly IConfiguration _config = null!;
///
/// Path base chiavi REDIS
///
protected readonly string _redisBaseKey = "Lux:Cache";
///
/// Oggetto per la connessione a Redis utilizzato per operazioni di lettura/scrittura.
///
protected readonly IConnectionMultiplexer _redisConn = null!;
///
/// Database Redis utilizzato per le operazioni di lettura/scrittura
/// nb: ottenuto tramite _redisConn.GetDatabase()
///
protected readonly IDatabase _redisDb = null!;
///
/// Abilitazione operazioni tracing generiche
///
protected readonly bool _traceEnabled = false;
///
/// Impostazioni del serializzatore JSON utilizzato per gestire oggetti con riferimenti circolari
/// (es. oggetti che si fanno riferimento reciprocamente).
///
protected JsonSerializerSettings? JSSettings;
#endregion Protected Fields
#region Protected Properties
///
/// Durata della cache breve (circa 1 minuto + variazione del +/-10%)
///
protected TimeSpan FastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort * rnd.Next(900, 1100) / 1000);
}
///
/// Durata della cache lunga (+ variazione del +/-10%)
///
protected TimeSpan LongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * rnd.Next(900, 1100) / 1000);
}
///
/// Durata della cache molto breve (circa 10 secondi + variazione del +/-10%)
///
protected TimeSpan UltraFastCache
{
get => TimeSpan.FromSeconds(cacheTtlShort / 6 * rnd.Next(900, 1100) / 1000);
}
///
/// Durata della cache molto lunga (+ variazione del +/-10%)
///
protected TimeSpan UltraLongCache
{
get => TimeSpan.FromSeconds(cacheTtlLong * 10 * rnd.Next(900, 1100) / 1000);
}
#endregion Protected Properties
#region Protected Methods
///
/// Helper avvio attività per la funzione tracciata
///
///
///
protected static Activity? StartActivity([CallerMemberName] string? methodName = null)
{
var activity = ActivitySource.StartActivity(methodName ?? "UNDEF");
activity?.SetTag("host.name", Environment.MachineName);
return activity;
}
///
/// Invalida una o più chiavi/pattern in Redis
///
protected async Task ClearCacheAsync(params string[] patterns)
{
foreach (var pattern in patterns)
{
// Chiamata al tuo metodo esistente
await ExecFlushRedisPatternAsync((RedisValue)pattern);
}
}
///
/// Metodo di flush dati cache Redis
///
///
///
protected async Task ExecFlushRedisPatternAsync(RedisValue pattern)
{
// Qui inserisci la tua logica attuale (es. via Lua script o Keys/Scan)
// Esempio rapido via server scan:
var endpoints = _redisConn.GetEndPoints();
foreach (var endpoint in endpoints)
{
var server = _redisConn.GetServer(endpoint);
await foreach (var key in server.KeysAsync(_redisDb.Database, pattern))
{
await _redisDb.KeyDeleteAsync(key);
}
}
}
///
/// Helper generale di lettura da cache o da funzione (DB) con caching successivo
///
///
///
///
///
///
protected async Task GetOrSetCacheAsync(string key, Func> factory, TimeSpan? expiration = null, [CallerMemberName] string? caller = null)
{
using var activity = StartActivity();
string source = "DB";
// 1. Provo Redis
var cached = await _redisDb.StringGetAsync(key);
if (cached.HasValue)
{
source = "REDIS";
var cachedResult = JsonConvert.DeserializeObject(cached!)!;
activity?.SetTag("data.source", source);
LogTrace($"{source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", NLog.LogLevel.Trace, caller);
return cachedResult;
}
// 2. Chiamo il factory (DB)
T result = await factory();
if (result != null)
{
// 3. Salva in Redis per la prossima volta
var serialized = JsonConvert.SerializeObject(result, JSSettings);
await _redisDb.StringSetAsync(key, serialized, expiration ?? LongCache);
}
// sistemo activity tracking data
activity?.SetTag("data.source", source);
activity?.Stop();
// log in console
LogTrace($"GetOrSetCacheAsync | {source} | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds:N3}ms", NLog.LogLevel.Trace, caller);
return result!;
}
///
/// Helper trace messaggio log (SE abilitato)
///
///
///
///
protected void LogTrace(string traceMsg, NLog.LogLevel? reqLevel = null, [CallerMemberName] string? methodName = null)
{
if (!_traceEnabled)
return;
reqLevel ??= NLog.LogLevel.Debug;
// Loggo!
Log.Log(reqLevel, $"{methodName} | {traceMsg}");
}
///
/// Helper generale per la telemetria e gestione eccezioni
///
///
///
///
///
///
protected async Task TraceAsync(string name, Func> body, object? parameters = null)
{
using var activity = ActivitySource.StartActivity(name);
try
{
if (parameters != null)
{
activity?.SetTag("params", JsonConvert.SerializeObject(parameters));
}
var result = await body(activity);
activity?.SetStatus(ActivityStatusCode.Ok);
activity?.Stop();
LogTrace($"TraceAsync | trace: {activity?.TraceId} | {activity?.Duration.TotalMilliseconds}ms", methodName: name);
return result;
}
catch (Exception ex)
{
activity?.SetStatus(ActivityStatusCode.Error, ex.Message);
//Log.Error(ex, "Errore in {MethodName}", name);
LogTrace($"Errore in {name}", NLog.LogLevel.Error, name);
throw; // Riesponi l'eccezione per il tracking globale
}
}
#endregion Protected Methods
#region Private Fields
private static readonly ConcurrentDictionary _locks = new();
///
/// Durata della cache lunga in secondi (predefinito: 5 minuti)
/// Utilizzato nella proprietà LongCache per definire quanto a lungo i dati devono essere memorizzati in cache.
///
private int cacheTtlLong = 60 * 5;
///
/// Durata della cache breve in secondi (predefinito: 1 minuto)
/// Utilizzato nelle proprietà FastCache e UltraFastCache per definire la durata della cache breve.
///
private int cacheTtlShort = 60 * 1;
///
/// Generatore di numeri casuali utilizzato per introdurre variabilità dinamica nelle durate della cache
/// (simula variazioni reali nella freschezza dei dati e nei tempi di scadenza).
///
private Random rnd = new Random();
#endregion Private Fields
#region Private Methods
#endregion Private Methods
}
}