using Core; using Core.DTO; using LiMan.DB; using LiMan.DB.DBModels; using LiMan.DB.DTO; using LiMan.DB.Services; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Win32; using Newtonsoft.Json; using NLog; using StackExchange.Redis; using StackExchange.Redis.Extensions.Core.Abstractions; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using static Core.Enum; namespace LiMan.APi.Data { /// /// Classe astrazione accesso dati /// public class ApiDataService : CommonDataServices { #region Public Constructors /// /// Init classe /// /// /// /// /// /// public ApiDataService(IConfiguration configuration, ILogger logger, IEmailSender emailSender, IRedisCacheClient redisCacheClient, IConnectionMultiplexer redisConnMult) : base(configuration, redisConnMult, emailSender) { _configuration = configuration; #if false _logger = logger; _emailSender = emailSender; _redisCacheClient = redisCacheClient; //this.distributedCache = distributedCache; #endif #if false // Conf cache redisConn = redisConnMult; redisDb = this.redisConn.GetDatabase(); // json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/ JSSettings = new JsonSerializerSettings() { ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; // conf messagepipe: setup canali pub/sub EnrollMessPipe = new MessagePipe(redisConn, Const.ENRL_MSG_PIPE); TaskMessPipe = new MessagePipe(redisConn, Const.TASK_MSG_PIPE); UpdActMessPipe = new MessagePipe(redisConn, Const.UPDT_MSG_PIPE); // conf DB string connStrDB = _configuration.GetConnectionString("LiMan.DB"); if (string.IsNullOrEmpty(connStrDB)) { Log.Error("ConnString empty!"); } else { dbController = new LiMan.DB.Controllers.DbController(configuration); Log.Info("DbController OK"); } #endif } #endregion Public Constructors #region Public Methods /// /// Recupero da Redis info x elenco info dettaglio di un SINGOLO EgwACC /// /// Cod univoco impegno public Dictionary EgwAccDictDetailGet(string CodImp) { RedisKey reqKey = (RedisKey)$"{rKeyEACCDetail}:{CodImp}"; Dictionary answ = redisHashDictGet(reqKey); return answ; } /// /// Salvataggio in Redis info di dettaglio di un EgwACC /// /// Cod univoco impegno /// public void EgwAccDictDetailSet(string CodImp, Dictionary Payload) { // salvo in hash info CodImp + versione RedisKey reqKey = (RedisKey)$"{rKeyEACCDetail}:{CodImp}"; // imposto scadenza a 60gg... double scadenza = 60 * 24 * 60; redisHashDictSet(reqKey, Payload, scadenza); #if false // salvo in altra hash (con key da CodImp) le info specifiche ricevute foreach (var res in Payload) { redisHashKeySet(reqKey, res.Key, res.Value, scadenza); } #endif // invio string in messagepipe x forzare refresh... UpdActMessPipe.sendMessage(CodImp); } /// /// recupero da Redis info x elenco EgwACC secondo key richiesta /// /// Chiave Richiesta (es Vers, Last, ...) public Dictionary EgwAccDictValGet(string KeyName) { RedisKey reqKey = (RedisKey)$"{rKeyEACCList}:{KeyName}"; Dictionary answ = redisHashDictGet(reqKey); return answ; } /// /// Salvataggio in Redis info x elenco EgwACC sul campo /// /// Cod univoco impegno /// Chiave salvata (es Vers, Last, ...) /// Valore serializzato public void EgwAccDictValSet(string CodImp, string KeyName, string Value) { // salvo in hash info CodImp + versione RedisKey reqKey = (RedisKey)$"{rKeyEACCList}:{KeyName}"; // salvo in altra hash (con key da CodImp) le info specifiche ricevute redisHashKeySet(reqKey, CodImp, Value); // invio string in messagepipe x forzare refresh... UpdActMessPipe.sendMessage(CodImp); } /// /// Crea record richiesta enroll (univoco rispetto richieste correnti) /// /// Dati da associare alla richeista /// public async Task EnrollReqCreate(Dictionary MachineInfo) { Stopwatch sw = new Stopwatch(); sw.Start(); // svuoto elenco richieste scaduteù var cleanDone = await EnrollReqPurgeInvalid(); // prendo elenco attive x evitare duplicazioni codici TOTP... var resList = await EnrollReqGetActive(); int totpCode = rnd.Next(1, 100000000); // verifico che non sia preesistente... if (resList.Count > 0) { // cerco se fosse già presente... while (resList.Where(x => x.Passcode == totpCode).Any()) { // genero nuovo... await Task.Delay(rnd.Next(50)); totpCode = rnd.Next(1, 100000000); } } // serializzo i dati della richiesta.. string reqPayload = JsonConvert.SerializeObject(MachineInfo); // preparo il record da registrare... EnrollRequestModel newReq = new EnrollRequestModel() { DtReq = DateTime.Now, Passcode = totpCode, ReqPayload = reqPayload }; var dbRec = await EnrollReqUpsert(newReq); sw.Stop(); TimeSpan ts = sw.Elapsed; Log.Trace($"Effettuata EnrollReqCreate: {ts.TotalMilliseconds} ms"); // invio string in messagepipe x forzare refresh... EnrollMessPipe.sendMessage("NewEnrollReq"); // restituisce record return dbRec; } /// /// Esegue aggiunta file dato ticket e list uploadResult /// /// Identificativo del ticket /// Directory di salvataggio dei file /// lista risultati della funzione di upload /// public async Task FileAdd(int idxTicket, string baseDir, List fileUploaded) { bool fatto = false; // inserimento! Stopwatch sw = new Stopwatch(); sw.Start(); fatto = dbController.FileAdd(idxTicket, baseDir, fileUploaded); sw.Stop(); TimeSpan ts = sw.Elapsed; Log.Trace($"Effettuata inserimento con FileAdd: {ts.TotalMilliseconds} ms"); // restituisce elenco return await Task.FromResult(fatto); } /// /// Effettua registrazione chiamata verificando se vada messa sul DB o in redis... /// /// /// /// public async Task recordCall(string codInst, string codApp, string targetUrl) { bool fatto = false; // in primis recupero statistiche (e nel mentre eventualmente salvo su DB) SampleStats currStats = await getCurrStats(); // preparo chiave x dato da loggare string currKey = $"{rKeySampleVars}:{codInst}:{codApp}:{targetUrl}"; // verifico presenza contatore corrente altrimenti aggiungo e salvo... if (!currStats.VList.Contains(currKey)) { currStats.VList.Add(currKey); // salvo! await setCurrStats(currStats); } // incremento valore contatore corrente await redCountIncr(currKey); return fatto; } /// /// Effettua registrazione da codice chiave... /// /// /// public async Task recordCall(string chiave, string targetUrl) { bool fatto = false; // valutare se cache key --> lic... if (!string.IsNullOrEmpty(chiave)) { var currLic = await LicenzaByMasterKey(chiave); if (currLic != null) { fatto = await recordCall(currLic.CodInst, currLic.CodApp, targetUrl); } } return fatto; } /// /// Elenco Release dato Applicativo /// /// Codice Applicazione /// public async Task> ReleaseDtoGetByApp(string CodApp) { await Task.Delay(1); List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.ReleaseDtoGetByApp(CodApp); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB per ReleaseDtoGetByApp | {CodApp} | {ts.TotalMilliseconds} ms"); return dbResult; } /// /// Elenco Release dato Applicativo + versione minima /// /// Codice Applicazione /// Versione minima richiesta /// public async Task> ReleaseDtoGetByAppVers(string CodApp, string VersMin) { await Task.Delay(1); List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.ReleaseDtoGetByAppVers(CodApp, VersMin); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB per ReleaseDtoGetByAppVers | {CodApp} | vers >= {VersMin} | {ts.TotalMilliseconds} ms"); return dbResult; } /// /// Elenco Release dato Applicativo + versione minima /// /// Codice Applicazione /// Versione minima richiesta /// Versione massima consentita /// public async Task> ReleaseGetByAppVersLimit(string CodApp, string VersMin, string VersMax) { await Task.Delay(1); List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.ReleaseDtoGetByAppVersLimit(CodApp, VersMin, VersMax); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB per ReleaseDtoGetByAppVersLimit | {CodApp} | vers >= {VersMin} | {ts.TotalMilliseconds} ms"); return dbResult; } /// /// Elenco di TUTTE le release CRITICAL come dizionario CodApp - release /// /// public async Task> ReleaseGetCritical() { Dictionary dbResult = new Dictionary(); Stopwatch sw = new Stopwatch(); sw.Start(); string cacheKey = $"{rKeyAttivByLic}:AppReleases:CRITICAL"; string source = "REDIS"; trackCache(cacheKey); string rawData = await getRSV(cacheKey); if (!string.IsNullOrEmpty(rawData)) { dbResult = JsonConvert.DeserializeObject>(rawData); } else { source = "DB"; dbResult = dbController.ReleaseGetCritical(); rawData = JsonConvert.SerializeObject(dbResult); await setRSV(cacheKey, rawData, redCacheTtlFast); } sw.Stop(); Log.Trace($"Effettuata lettura da DB per ReleaseGetCritical | {source} | # found: {dbResult.Count} | {sw.ElapsedMilliseconds} ms"); return dbResult; } /// /// Registro su DB le statistiche delle chiavi in elenco, resettando i vari contatori quando res /// /// Elenco key nel formato {rKeySampleVars}:{codInst}:{codApp}:{targetUrl} public async Task saveStatsToDb(List keyList) { bool fatto = false; // ciclo x eseguire 1:1 foreach (var item in keyList) { // recupero counter... var currCount = await redCount(item); // scompongo key... senza url di base string[] valStr = item.Replace($"{rKeySampleVars}:", "").Split(":"); if (valStr.Length > 2) { LogCallModel newRec = new LogCallModel() { CodInst = valStr[0], CodApp = valStr[1], TargetUrl = item.Replace($"{rKeySampleVars}:", "").Replace($"{valStr[0]}:{valStr[1]}:", ""), DataRif = DateTime.Now, NumCall = currCount }; fatto = await dbController.LogCallUpsert(newRec); if (fatto) { await redCountClear(item); } } } return fatto; } /// /// Restituisce i task associati ad un dato EgwACC /// /// /// public Dictionary TaskListGet(string CodImp) { RedisKey currKey = (RedisKey)$"{rKeyTaskReq}:{CodImp}"; Dictionary answ = redisHashDictGet(currKey); return answ; } /// /// Resetta i task di un dato EgwACC /// /// /// public Dictionary TaskListReset(string CodImp) { Dictionary answ = new Dictionary(); // svuoto dizoinari specifici bool ok01 = redisHashDictDelete((RedisKey)$"{rKeyTaskReq}:{CodImp}"); bool ok02 = redisHashDictDelete((RedisKey)$"{rKeyTaskRun}:{CodImp}"); bool ok03 = redisHashDictDelete((RedisKey)$"{rKeyTaskDone}:{CodImp}"); // svuoto in elenco task x imp... redisHashKeyDelete((RedisKey)$"{rKeyTaskReq}List", CodImp); redisHashKeyDelete((RedisKey)$"{rKeyTaskRun}List", CodImp); redisHashKeyDelete((RedisKey)$"{rKeyTaskDone}List", CodImp); // leggo remaining x verificare sia ok.. var dictReq = new Dictionary(redisHashDictGet((RedisKey)$"{rKeyTaskReq}:{CodImp}")); var dictRun = new Dictionary(redisHashDictGet((RedisKey)$"{rKeyTaskRun}:{CodImp}")); var dictDone = new Dictionary(redisHashDictGet((RedisKey)$"{rKeyTaskDone}:{CodImp}")); answ = new Dictionary(dictReq); foreach (var item in dictRun) { answ.TryAdd(item.Key, item.Value); } foreach (var item in dictDone) { answ.TryAdd(item.Key, item.Value); } // invio string in messagepipe x forzare refresh... UpdActMessPipe.sendMessage(CodImp); return answ; } /// /// Salva il risultato dell'esecuzione dei task effettuata /// /// /// /// /// public int TaskSetDone(string CodImp, Dictionary DataPayload) { int done = 0; // imposto scadenza a 30gg... double scadenza = 60 * 24 * 30; // upsert per ogni singolo record + rimozione da running foreach (var res in DataPayload) { bool answ = redisHashKeySet((RedisKey)$"{rKeyTaskDone}:{CodImp}", res.Key, res.Value, scadenza); redisHashKeyDelete((RedisKey)$"{rKeyTaskRun}:{CodImp}", res.Key); // sistemo hashset... redisHashKeySet((RedisKey)$"{rKeyTaskDone}List", CodImp, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); redisHashKeyDelete((RedisKey)$"{rKeyTaskRun}List", CodImp); // segnalo fatto done += answ ? 1 : 0; } // invio string in messagepipe x forzare refresh... UpdActMessPipe.sendMessage(CodImp); return done; } /// /// Salva come running i task indicati (togliendo da richiesti) /// /// /// /// /// public int TaskSetRunning(string CodImp, Dictionary DataPayload) { int done = 0; // imposto scadenza a 30gg... double scadenza = 60 * 24 * 30; // upsert per ogni singolo record + rimozione da richiesti foreach (var res in DataPayload) { // sposto il valore in key bool answ = redisHashKeySet((RedisKey)$"{rKeyTaskRun}:{CodImp}", res.Key, res.Value, scadenza); redisHashKeyDelete((RedisKey)$"{rKeyTaskReq}:{CodImp}", res.Key); // sistemo list task... redisHashKeySet((RedisKey)$"{rKeyTaskRun}List", CodImp, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); redisHashKeyDelete((RedisKey)$"{rKeyTaskReq}List", CodImp); // segnalo fatto done += answ ? 1 : 0; } // invio string in messagepipe x forzare refresh... UpdActMessPipe.sendMessage(CodImp); return done; } /// /// Esegue aggiunta Ticket richiesto + restitusice aperti x cliente /// /// /// /// public async Task TicketAdd(SupportRequest currRequest) { bool fatto = false; // inserimento! Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); fatto = dbController.TicketAddNew(currRequest); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata inserimento con TicketAdd: {ts.TotalMilliseconds} ms"); // restituisce elenco return await Task.FromResult(fatto); } /// /// Elenco ticket dato cliente + App + MasterKey /// /// /// /// /// /// public async Task> TicketByCliente(string CodInst, string CodApp, string MasterKey, int numRec = 1000) { List dbResult = new List(); Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); dbResult = dbController.TicketGetFilt(false, TipologiaTicket.ND, CodApp, CodInst, MasterKey, numRec); stopWatch.Stop(); TimeSpan ts = stopWatch.Elapsed; Log.Trace($"Effettuata lettura da DB per TicketByCliente: {ts.TotalMilliseconds} ms"); return await Task.FromResult(dbResult); } /// /// Salva ultima dataora di comunicazione con un updater /// /// /// /// public int UpdaterRecordAction(string CodImp, string CodAction) { int done = 0; // salvo info sul TIPO di chiamata bool answ = redisHashKeyIncDecBy((RedisKey)$"{rKeyUpdLastAct}:{CodImp}", CodAction, 1); // registro ultima chiamata redisHashKeySet((RedisKey)$"{rKeyUpdLastAct}List", CodImp, $"{DateTime.Now:yyyy-MM-dd HH:mm:ss}"); // invio string in messagepipe x forzare refresh... UpdActMessPipe.sendMessage(CodImp); // scambio tipo di pipe messaggi (update e non task) //TaskMessPipe.sendMessage(CodImp); return done; } #endregion Public Methods #region Protected Fields protected new static Logger Log = LogManager.GetCurrentClassLogger(); #endregion Protected Fields #region Protected Methods /// /// Recupera statistiche correnti /// /// protected async Task getCurrStats() { DateTime adesso = DateTime.Now; SampleStats answ = new SampleStats() { Name = "ApiStats" }; // in primis check data/ora prima/ultima scrittura del set... (2 date, lista chiavi gestite) string rawData = await getRSV(rKeySampleStats); if (rawData != null) { answ = JsonConvert.DeserializeObject(rawData); // aggiorno ultimo controllo e salvo... answ.DtLast = adesso; // salvo! await setCurrStats(answ); } // controllo se scadute... if (adesso.Subtract(answ.DtFirst).TotalMinutes > 60) { // se scaduto --> registrazione set sul DB (async), resettando i vari contatori... bool salvato = await saveStatsToDb(answ.VList); // inizio NUOVO set vuoto con record corrente answ = new SampleStats() { Name = "ApiStats" }; // salvo! await setCurrStats(answ); } // restituisco record! return answ; } /// /// Salva statistiche correnti /// /// protected async Task setCurrStats(SampleStats newVal) { bool answ = false; string rawData = JsonConvert.SerializeObject(newVal); answ = await setRSV(rKeySampleStats, rawData, 24 * hourTTL); return answ; } #endregion Protected Methods } }