From f95e7c441b58d7ba2d934a559e72210c72cb2629 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 08:03:23 +0200 Subject: [PATCH 01/13] Aggiunta progetto API Routing semplificata (solo routing) --- Directory.Packages.props | 4 +- MP-RIOC/Data/MpDataService.cs | 5006 +++++++++++++++++++ MP-RIOC/MP-RIOC.http | 6 + MP-RIOC/MP-RIOC.slnx | 4 + MP-RIOC/MP.RIOC.csproj | 41 + MP-RIOC/Pages/Index.cshtml | 25 + MP-RIOC/Pages/Index.cshtml.cs | 12 + MP-RIOC/Program.cs | 131 + MP-RIOC/Properties/launchSettings.json | 41 + MP-RIOC/Services/IWeightProvider.cs | 29 + MP-RIOC/Services/InMemoryWeightProvider.cs | 56 + MP-RIOC/Services/PreserveBodyTransformer.cs | 38 + MP-RIOC/Services/RedisWeightProvider.cs | 157 + MP-RIOC/Services/RouteManager.cs | 167 + MP-RIOC/Services/RouteStatsManager.cs | 71 + MP-RIOC/WeatherForecast.cs | 13 + MP-RIOC/appsettings.Development.json | 8 + MP-RIOC/appsettings.json | 79 + MP-RIOC/compilerconfig.json | 6 + MP-RIOC/compilerconfig.json.defaults | 59 + MP-RIOC/post-build.ps1 | 32 + MP.IOC/Program.cs | 13 +- MP.IOC/appsettings.json | 9 + 23 files changed, 6004 insertions(+), 3 deletions(-) create mode 100644 MP-RIOC/Data/MpDataService.cs create mode 100644 MP-RIOC/MP-RIOC.http create mode 100644 MP-RIOC/MP-RIOC.slnx create mode 100644 MP-RIOC/MP.RIOC.csproj create mode 100644 MP-RIOC/Pages/Index.cshtml create mode 100644 MP-RIOC/Pages/Index.cshtml.cs create mode 100644 MP-RIOC/Program.cs create mode 100644 MP-RIOC/Properties/launchSettings.json create mode 100644 MP-RIOC/Services/IWeightProvider.cs create mode 100644 MP-RIOC/Services/InMemoryWeightProvider.cs create mode 100644 MP-RIOC/Services/PreserveBodyTransformer.cs create mode 100644 MP-RIOC/Services/RedisWeightProvider.cs create mode 100644 MP-RIOC/Services/RouteManager.cs create mode 100644 MP-RIOC/Services/RouteStatsManager.cs create mode 100644 MP-RIOC/WeatherForecast.cs create mode 100644 MP-RIOC/appsettings.Development.json create mode 100644 MP-RIOC/appsettings.json create mode 100644 MP-RIOC/compilerconfig.json create mode 100644 MP-RIOC/compilerconfig.json.defaults create mode 100644 MP-RIOC/post-build.ps1 diff --git a/Directory.Packages.props b/Directory.Packages.props index de7d50f2..00d6ceed 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -28,9 +28,9 @@ - + - + diff --git a/MP-RIOC/Data/MpDataService.cs b/MP-RIOC/Data/MpDataService.cs new file mode 100644 index 00000000..12eecbd8 --- /dev/null +++ b/MP-RIOC/Data/MpDataService.cs @@ -0,0 +1,5006 @@ +using Microsoft.EntityFrameworkCore; +using MP.Core.Conf; +using MP.Core.DTO; +using MP.Core.Objects; +using MP.Data; +using MP.Data.Controllers; +using MP.Data.DbModels; +using MP.Data.DbModels.Anag; +using MP.Data.MgModels; +using MP.Data.Services.IOC; +using MP.Data.Services.Mtc; +using Newtonsoft.Json; +using NLog; +using StackExchange.Redis; +using System.Data; +using System.Diagnostics; +using System.Globalization; +using static MP.Core.Objects.Enums; + +namespace MP.RIOC.Data +{ + public class MpDataService : IDisposable + { + #region Public Constructors + + public MpDataService( + IConfiguration configuration, + ILogger logger, + IServiceScopeFactory scopeFactory, + IMtcSetupService mtcServ) + { + _logger = logger; + _logger.LogInformation("Starting MpDataService INIT"); + _configuration = configuration; + _scopeFactory = scopeFactory; + + // setup compoenti REDIS + redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); + redisConnAdmin = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("RedisAdmin")); + redisDb = redisConn.GetDatabase(); + BroadastMsgPipe = new MessagePipe(redisConn, Constants.BROADCAST_M_PIPE); + // leggo cache (lungo periodo e corto periodo) + int.TryParse(_configuration.GetValue("ServerConf:redisLongTimeCache"), out redisLongTimeCache); + int.TryParse(_configuration.GetValue("ServerConf:redisShortTimeCache"), out redisShortTimeCache); + Log.Info($"Redis INIT | redisLongTimeCache: {redisLongTimeCache} min | redisShortTimeCache {redisShortTimeCache} min"); + + // gestione scope factory + useFactory = _configuration.GetValue("ServerConf:useFactory"); + // conf base x servizi condivisi IO/IOC + MpIoNS = _configuration.GetValue("ServerConf:MpIoNS") ?? "MP"; + Log.Info($"MpDataService | useFactory {useFactory} | MpIoNS {MpIoNS}"); + + // conf DB + string connStr = _configuration.GetConnectionString("MP.Data") ?? ""; + if (string.IsNullOrEmpty(connStr)) + { + Log.Error("DbController: ConnString empty!"); + } + else + { + SpecDbController = new MpSpecController(configuration); + IocDbController = new MpIocController(configuration); + Log.Info("DbControllers INIT OK"); + } + + MtcService = mtcServ; + + // conf mongo... + connStr = _configuration.GetConnectionString("mdbConnString") ?? ""; + if (string.IsNullOrEmpty(connStr)) + { + Log.Error("MongoController: ConnString empty!"); + } + else + { + mongoController = new MpMongoController(configuration); + Log.Info("MongoController INIT OK"); + } + + } + + #endregion Public Constructors + + #region Public Properties + + public static MpIocController IocDbController { get; set; } = null!; + public static MpMongoController mongoController { get; set; } = null!; + public static MpSpecController SpecDbController { get; set; } = null!; + public MessagePipe BroadastMsgPipe { get; set; } = null!; + + /// + /// Dizionario dei tag configurati per IOB + /// + public Dictionary> currTagConf { get; set; } = new Dictionary>(); + + #endregion Public Properties + + #region Public Methods + + /// + /// Recupera eventuali azioni richieste + /// + /// + public async Task ActionGetReq() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + DisplayAction? result = null; + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(Utils.redisActionReq); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject($"{rawData}"); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ActionGetReq Read from REDIS: {ts.TotalMilliseconds}ms"); + } + if (result == null) + { + result = new DisplayAction(); + } + return result; + } + + /// + /// Salva richiesta azione + /// + /// + /// + public bool ActionSetReq(DisplayAction? act2save) + { + bool fatto = false; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + // cerco in redis... + string rawData = JsonConvert.SerializeObject(act2save); + // invio broadcast + salvo in redis + BroadastMsgPipe.saveAndSendMessage(Utils.redisActionReq, rawData); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ActionSetReq REDIS send to broadcast + Write cache: {ts.TotalMilliseconds}ms"); + return fatto; + } + + /// + /// Verifica se sia da reinviare un tName alla macchina dall'elenco di quelli salvati (in + /// modalità upsert) se non scaduti + /// + /// + /// + /// + /// + public bool AddCheckTask4Machine(string idxMacchina, taskType taskKey, string taskVal) + { + bool answ = false; + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + Log.Info($"addCheckTask4Machine idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); + try + { + Dictionary savedTask = mSavedTaskMacchina(idxMacchina); + // cerco valOut saved + string savedVal = savedTask[taskKey.ToString()]; + // se ho un valOut != "" --> rimetto in coda di invio... + if (!string.IsNullOrEmpty(savedVal) && (savedVal != taskVal)) + { + // leggo tName attuali... + Dictionary currTask = mTaskMacchina(idxMacchina); + // rimetto in tName da eseguire... + currTask[taskKey.ToString()] = savedVal; + RedisSetHashDict(currHash, currTask); + answ = true; + Log.Info($"re-issued task4machine: idxMacchina: {idxMacchina} | taskKey: {taskKey} | savedVal: {savedVal}"); + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in AddCheckTask4Machine{Environment.NewLine}{exc}"); + } + + return answ; + } + + /// + /// Aggiunge un PARAMETRO OPZIONALE all'elenco di quelli salvati (in modalità upsert) + /// + /// + /// + /// + /// + public bool AddOptPar4Machine(string idxMacchina, string taskKey, string taskVal) + { + bool answ = false; + RedisKey currHash = Utils.RedKeyOptPar(idxMacchina, MpIoNS); + try + { + // leggo tName attuali... + var currVal = mOptParMacchina(idxMacchina); + // verifico se esista o se vada aggiunto... + if (currVal.ContainsKey(taskKey)) + { + currVal[taskKey] = taskVal; + } + else + { + currVal.Add(taskKey, taskVal); + } + RedisSetHashDict(currHash, currVal); + answ = true; + } + catch (Exception exc) + { + Log.Error($"Eccezione in addOptPar4Machine{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// Aggiunge un tName all'elenco di quelli salvati (in modalità upsert) + /// + /// + /// + /// + /// + public bool AddTask4Machine(string idxMacchina, Enums.taskType taskKey, string taskVal) + { + bool answ = false; + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + var currSavedParHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina, MpIoNS); + Dictionary currTask = new Dictionary(); + Dictionary savedTask = new Dictionary(); + try + { + // leggo tName attuali... + currTask = mTaskMacchina(idxMacchina); + if (currTask.ContainsKey($"{taskKey}")) + { + currTask[$"{taskKey}"] = taskVal; + } + else + { + currTask.Add($"{taskKey}", taskVal); + } + RedisSetHashDict(currHash, currTask); + answ = true; + Log.Info($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); + } + catch { } + // verifico in base al tipo di tName se fare backup... + switch (taskKey) + { + case Enums.taskType.setArt: + case Enums.taskType.setComm: + case Enums.taskType.setPzComm: + case Enums.taskType.setProg: + // leggo tName SALVATI attuali... + savedTask = mSavedTaskMacchina(idxMacchina); + savedTask[taskKey.ToString()] = taskVal; + RedisSetHashDict(currSavedParHash, savedTask); + answ = true; + break; + + case Enums.taskType.endProd: + // salvo un DICT vuoto x resettare + savedTask = new Dictionary(); + RedisSetHashDict(currSavedParHash, savedTask); + answ = true; + break; + + default: + break; + } + return answ; + } + + /// + /// Aggiunge un set di task x macchina all'elenco di quelli salvati (in modalità upsert) + /// + /// + /// + /// + /// + public async Task AddTask4MacListAsync(string idxMacchina, Dictionary taskDict) + { + bool answ = false; + bool needSaveParams = false; + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + var currSavedParHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina, MpIoNS); + Dictionary currTask = new Dictionary(); + Dictionary savedTask = new Dictionary(); + + // leggo valori attuali... + currTask = await mTaskMacchinaAsync(idxMacchina); + // verifico processing dei ricevuti + foreach (var item in taskDict) + { + if (currTask.ContainsKey($"{item.Key}")) + { + currTask[$"{item.Key}"] = item.Value; + } + else + { + currTask.Add($"{item.Key}", item.Value); + } + Log.Trace($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {item.Key} | taskVal: {item.Value}"); + + // verifico in base al tipo di tName se fare backup... + switch (item.Key) + { + case Enums.taskType.setArt: + case Enums.taskType.setComm: + case Enums.taskType.setPzComm: + case Enums.taskType.setProg: + // leggo tName SALVATI attuali... + savedTask = mSavedTaskMacchina(idxMacchina); + savedTask[item.Key.ToString()] = item.Value; + needSaveParams = true; + break; + + case Enums.taskType.endProd: + // salvo un DICT vuoto x resettare + savedTask = new Dictionary(); + needSaveParams = true; + break; + + default: + break; + } + } + // salvo! + await RedisSetHashDictAsync(currHash, currTask); + answ = true; + + if (needSaveParams) + { + await RedisSetHashDictAsync(currSavedParHash, savedTask); + } + return answ; + } + + /// + /// Insert record allarme + /// + /// Data evento + /// Idx macchina + /// area memoria + /// indice memoria + /// valOut status + /// valOut decodificato + /// + public async Task AlarmInsertAsync(DateTime dtRif, string idxMacchina, string memAddress, int memIndex, int statusVal, string valDecoded) + { + // aggiorno record sul DB + bool answ = await IocDbController.AlarmLogInsertAsync(dtRif, idxMacchina, memAddress, memIndex, statusVal, valDecoded); + + return answ; + } + + public async Task> AnagStatiComm() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + List? result = new List(); + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(Utils.redisStatoCom); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"AnagStatiComm Read from REDIS: {ts.TotalMilliseconds}ms"); + } + else + { + result = await Task.FromResult(SpecDbController.AnagStatiComm()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(Utils.redisStatoCom, rawData, getRandTOut(redisLongTimeCache)); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"AnagStatiComm Read from DB: {ts.TotalMilliseconds}ms"); + } + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Restituisce l'anagrafica STATI per intero + /// + /// + public async Task> AnagStatiGetAllAsync() + { + List dbResult = new List(); + // cerco in redis... + var currKey = Utils.redisAnagStati; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + dbResult = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + dbResult = await IocDbController.AnagStatiGetAllAsync(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(dbResult); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + return dbResult; + } + + public async Task> AnagTipoArtLV() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string source = "DB"; + List? result = new List(); + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(Utils.redisTipoArt); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + source = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.AnagTipoArtLV()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(Utils.redisTipoArt, rawData, getRandTOut(redisLongTimeCache)); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"AnagTipoArtLV Read from {source}: {ts.TotalMilliseconds}ms"); + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Elenco Codice articolo con dati dossier gestiti + /// + /// + public async Task> ArticleWithDossier() + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = Utils.redisArtByDossier; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.ArticleWithDossier()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ArticleWithDossier | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Eliminazione record selezionato + /// + /// + /// + public async Task ArticoliDeleteRecord(AnagArticoliModel currRec) + { + bool fatto = await SpecDbController.ArticoliDeleteRecord(currRec); + await resetCacheArticoli(); + return fatto; + } + + /// + /// Elenco ultimi articoli data amcchina + /// + /// + /// + public async Task> ArticoliGetLastByMaccAsync(string idxMacc) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisArtList}:Last:{idxMacc}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await IocDbController.ArticoliGetLastByMaccAsync(idxMacc); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ArticoliGetLastByMaccAsync | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Restitusice elenco articoli cercati + /// + /// + /// + /// + public async Task> ArticoliGetSearch(int numRecord, string azienda, string searchVal) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string sKey = string.IsNullOrEmpty(searchVal) ? "***" : searchVal; + string currKey = $"{Utils.redisArtList}:{azienda}:{sKey}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.ArticoliGetSearch(numRecord, azienda, searchVal)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ArticoliGetSearch | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Aggiornamento record selezionato + /// + /// + /// + public async Task ArticoliUpdateRecord(AnagArticoliModel currRec) + { + bool fatto = await SpecDbController.ArticoliUpdateRecord(currRec); + await resetCacheArticoli(); + return fatto; + } + + /// + /// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su + /// tab veto da DB + /// + /// + /// + public bool ArticoloDelEnabled(object CodArt) + { + bool answ = false; + string codArticolo = $"{CodArt}"; + int cacheCheckArtUsato = 1; + int.TryParse(_configuration.GetValue("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato); + TimeSpan TTLCache = getRandTOut(cacheCheckArtUsato); + // cerco in cache redis... + string redKeyArtUsed = $"{Utils.redKeyArtUsed}:{codArticolo}"; + string redKeyTabCheckArt = Utils.redKeyTabCheckArt; + var rawData = redisDb.StringGet(redKeyArtUsed); + if (!string.IsNullOrEmpty(rawData)) + { + bool.TryParse(rawData, out answ); + } + else + { + // controllo non sia stato mai prodotto sennò non posso cancellare... + try + { + // cerco in cache se ci sia la tabella con gli articoli impiegati... + var rawTable = redisDb.StringGet(redKeyTabCheckArt); + List? artList = new List(); + if (!string.IsNullOrEmpty(rawTable)) + { + artList = JsonConvert.DeserializeObject>($"{rawTable}"); + } + // rileggo... + if (artList == null || artList.Count == 0) + { + artList = new List(); + var tabArticoli = SpecDbController.ArticoliGetUsed(); + var codList = tabArticoli.Select(x => x.CodArticolo); + foreach (string cod in codList) + { + artList.Add(cod); + } + // SE fosse vuoto aggiungo comunque il cado "ND"... + if (artList.Count == 0) + { + artList.Add("ND"); + } + // salvo + rawTable = JsonConvert.SerializeObject(artList); + redisDb.StringSet(redKeyTabCheckArt, rawTable, TTLCache); + } + // cerco nella tabella: se ci fosse --> disabilitato delete + bool usato = false; + if (artList != null && artList.Count > 0) + { + usato = artList.Contains(codArticolo); + } + answ = !usato; + redisDb.StringSet(redKeyArtUsed, $"{answ}", TTLCache); + } + catch + { } + } + return answ; + } + + /// + /// Effettua split ODL + /// + /// macchina + /// effettuare conferma qty + /// imposta la qty prox ODL da ODL che si chiude + /// matricola operatore + /// Round Step quantità prox ODL (corrente come riferimento) + /// Cod ext da associare all'ODL + /// + public async Task AutoStartOdlAsync(string idxMacchina, bool doConfirm, bool qtyFromLast, int matrOpr, int roundStep = 100, string keyRichiesta = "") + { + string answ = "KO"; + DateTime adesso = DateTime.Now; + + // verifico NON CI SIA un veto a NUOVI split... + string redKey = $"{Utils.redisVetoSplitOdl}:{idxMacchina}"; + var rawData = await redisDb.StringGetAsync(redKey); + if (rawData.HasValue) + { + Log.Info($"VETO ATTIVO | Richiesto forceSplitOdl per impianto {idxMacchina} | impossibile procedere"); + } + else + { + // registro VETO x altri split... 5 minuti + await redisDb.StringSetAsync(redKey, $"Inizio SPLIT-ODL {adesso}", TimeSpan.FromMinutes(5)); + // setup dati da config + bool confRett = false; + int modoConfProd = -1; + bool slaveConfirmPzProd = false; + var configData = await ConfigGetAllAsync(); + if (configData != null) + { + var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); + if (currRec != null) + { + bool.TryParse(currRec.Valore, out confRett); + } + currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); + if (currRec != null) + { + int.TryParse(currRec.Valore, out modoConfProd); + } + currRec = configData.FirstOrDefault(x => x.Chiave == "SlaveConfirmPzProd"); + if (currRec != null) + { + bool.TryParse(currRec.Valore, out slaveConfirmPzProd); + } + } + // calcolo la qta da gestire + int qtyConf = 0; + int qtyNew = 0; + int qtyScarto = 0; + if (doConfirm) + { + try + { + var rigaProd = await IocDbController.PezziProdMacchinaAsync(idxMacchina); + var statoProd = await IocDbController.StatoProdMacchinaAsync(idxMacchina, adesso); + qtyConf = rigaProd.pezziNonConfermati; + qtyScarto = statoProd.Pz2RecScarto; + // calcolo nuovi pezzi da confermare + if (qtyFromLast) + { + roundStep = roundStep == 0 ? 1 : roundStep; + double ratio = (double)qtyConf / roundStep; + qtyNew = (int)Math.Round(Math.Ceiling(ratio) * roundStep, 0); + } + } + catch (Exception exc) + { + Log.Error($"AutoStartOdlAsync | Errore recupero pezzi da confermare | idxMacchina {idxMacchina}{Environment.NewLine}{exc}"); + } + } + + // chiamo metodo redis/db... + try + { + // recupero ODL corrente + var currData = await IocDbController.OdlCurrByMaccAsync(idxMacchina); + if (currData != null && currData.IdxOdl > 0) + { + // preparo var x master/slave + bool isMachMaster = await IobIsMasterAsync(idxMacchina); + List slaveList = new(); + if (isMachMaster) + { + List allSlaveList = await IocDbController.Macchine2SlaveAsync(); + slaveList = allSlaveList.Where(x => x.IdxMacchina == idxMacchina).ToList(); + } + + // registro un evento di inizio attrezzaggio (idxTipoEv = 2) + int idxEvento = 2; + Log.Info($"Invio evento ODL-SPLIT per macchina {idxMacchina} | ev: {idxEvento} | art: {currData.CodArticolo} | qty conf: {qtyConf} | sty sca: {qtyScarto} | new qty: {qtyNew}"); + + // creo evento + EventListModel newRecEv = new EventListModel() + { + CodArticolo = currData.CodArticolo, + IdxMacchina = idxMacchina, + IdxTipo = idxEvento, + InizioStato = adesso, + MatrOpr = matrOpr, + pallet = "", + Value = "ODL-SPLIT" + }; + inputComandoMapo resCmd = await scriviRigaEventoBarcodeAsync(newRecEv); + + // era 100, messo 50... + int wTime = 50; + + // eventuale conferma produzione + if (doConfirm) + { + await Task.Delay(wTime); + adesso = DateTime.Now; + // chiamo conferma produzione... + try + { + string chiamata = confRett ? "confermaProdMacchinaFull" : "confermaProdMacchina"; + Log.Info($"Chiamata a {chiamata} con parametri {currData.IdxMacchina} |{matrOpr} | {DateTime.Now.AddDays(-10)} | {DateTime.Now} | {qtyConf} | 0 | {qtyScarto} | {DateTime.Now} | false"); + string idxMacchinaSel = currData.IdxMacchina; + bool fatto = false; + if (confRett) + { + // confermo al netto dei pezzi lasciati... + fatto = await IocDbController.ConfermaProdMacchinaFullAsync(idxMacchinaSel, modoConfProd, qtyConf, 0, qtyScarto, adesso, matrOpr); + if (slaveConfirmPzProd) + { + foreach (var machine in slaveList) + { + await IocDbController.ConfermaProdMacchinaFullAsync(machine.IdxMacchinaSlave, modoConfProd, qtyConf, 0, qtyScarto, adesso, matrOpr); + } + } + } + else + { + fatto = await IocDbController.ConfermaProdMacchinaAsync(idxMacchinaSel, modoConfProd, qtyConf, qtyScarto, adesso, matrOpr); + if (slaveConfirmPzProd) + { + foreach (var machine in slaveList) + { + await IocDbController.ConfermaProdMacchinaAsync(idxMacchinaSel, modoConfProd, qtyConf, qtyScarto, adesso, matrOpr); + } + } + } + if (!fatto) + { + Log.Error($"ERRORE in chiamata {chiamata}"); + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in ConfermaProduzione{Environment.NewLine}{exc}"); + } + } + // 2025.02.19 portato da 100 a 1000 ms attesa x evitare che ODL sia avviato PRIMA della conferma + // attendo 1000 msec x chiudere ODL + await Task.Delay(1000); + // chiamo splitOdl + await IocDbController.AutoStartOdlAsync(currData.IdxOdl, 0, idxMacchina, currData.TCRichAttr, currData.PzPallet, $"Nuovo ODL da forceSplitOdl.autoStart", true, qtyNew, keyRichiesta); + + // elimino eventuale ODL precedente... + string currKey = $"{Utils.redisOdlCurrByMac}:{idxMacchina}"; + await redisDb.KeyDeleteAsync(currKey); + + // ricalcola ODL macchina + var newOdl = await GetCurrOdlAsync(idxMacchina); + // attendo 1000 msec + await Task.Delay(1000); + + adesso = DateTime.Now; + // registro fine ODL (idxTipoEv = 1) + idxEvento = 1; + Log.Info($"Invio evento FINE ODL-SPLIT | idx: {idxMacchina} | ev: {idxEvento} | art: {currData.CodArticolo}"); + // costruisco nuovo evento + newRecEv = new EventListModel() + { + CodArticolo = currData.CodArticolo, + IdxMacchina = idxMacchina, + IdxTipo = idxEvento, + InizioStato = adesso, + MatrOpr = matrOpr, + pallet = "", + Value = "ODL-START" + }; + resCmd = await scriviRigaEventoBarcodeAsync(newRecEv); + // invio eventi setup a macchina.... + string setArtVal = $"{currData.CodArticolo}"; + string setPzCommVal = $"{qtyNew}"; + string setCommVal = $"ODL{newOdl}"; + if (!string.IsNullOrEmpty(keyRichiesta)) + { + setCommVal = $"{keyRichiesta} ODL{newOdl}"; + } + try + { + // invio task caricamento dati ODL + Dictionary updDict = new Dictionary(); + updDict.Add(taskType.setArt, setArtVal); + updDict.Add(taskType.setComm, setCommVal); + updDict.Add(taskType.setPzComm, setPzCommVal); +#if false + addTask4Machine(idxMacchina, taskType.setArt, setArtVal); + addTask4Machine(idxMacchina, taskType.setComm, setCommVal); + addTask4Machine(idxMacchina, taskType.setPzComm, setPzCommVal); + + updateMachineParameter(idxMacchina, "setArt", setArtVal); + updateMachineParameter(idxMacchina, "setComm", setCommVal); + updateMachineParameter(idxMacchina, "setPzComm", setPzCommVal); +#endif + // recupero set attuale parametri + var currMachPar = await MachineParamListAsync(idxMacchina); + List list2upd = new(); + // aggiorno i valori interessati tra quelli nel dizionario + foreach (var item in updDict) + { + var cRec = currMachPar.FirstOrDefault(x => x.uid == $"{item.Key}"); + if (cRec != null) + { + list2upd.Add(cRec); + } + } + // aggiungo task di setParameters x la commessa... + updDict.Add(taskType.setParameter, setCommVal); + // salvo + await AddTask4MacListAsync(idxMacchina, updDict); + await MachineParamUpsertAsync(idxMacchina, list2upd); + } + catch + { } + // chiamo refresh MSE + await IocDbController.RecalcMseAsync(idxMacchina, 0); + // resetto stato macchina... + var kStatoMacc = $"{Utils.redisStatoMacch}:{idxMacchina}"; + await redisDb.KeyDeleteAsync(kStatoMacc); + answ = "OK"; + Log.Info($"Effettuato reset e ricalcoli x split ODL per macchina {idxMacchina}"); + // se è una master richiamo fix x child... + if (isMachMaster) + { + Dictionary updDict = new Dictionary(); + string ts = ""; + string outData = ""; + await IocDbController.OdlFixMachineSlaveAsync(idxMacchina, 30, 1); + foreach (var machine in slaveList) + { + // invio chiusura attrezzaggio + ts = string.Format("{0:yyMMdd}T{0:HHmmss.fff}Z", DateTime.Now); + outData = $"TS:{ts}|MATR:{matrOpr}|ODL:{newOdl}"; + + updDict.Clear(); + updDict.Add(taskType.setArt, setArtVal); + updDict.Add(taskType.setComm, setCommVal); + updDict.Add(taskType.setPzComm, setPzCommVal); + +#if false + addTask4Machine(machine.IdxMacchinaSlave, taskType.fixStopSetup, outData); + addTask4Machine(machine.IdxMacchinaSlave, taskType.forceResetPzCount, outData); + // invio task caricamento dati ODL + addTask4Machine(machine.IdxMacchinaSlave, taskType.setArt, setArtVal); + addTask4Machine(machine.IdxMacchinaSlave, taskType.setComm, setCommVal); + addTask4Machine(machine.IdxMacchinaSlave, taskType.setPzComm, setPzCommVal); + + updateMachineParameter(machine.IdxMacchinaSlave, "setArt", setArtVal); + updateMachineParameter(machine.IdxMacchinaSlave, "setComm", setCommVal); + updateMachineParameter(machine.IdxMacchinaSlave, "setPzComm", setPzCommVal); +#endif + + // recupero set attuale parametri + var currMachPar = await MachineParamListAsync(machine.IdxMacchinaSlave); + List list2upd = new(); + // aggiorno i valori interessati tra quelli nel dizionario + foreach (var item in updDict) + { + var cRec = currMachPar.FirstOrDefault(x => x.uid == $"{item.Key}"); + if (cRec != null) + { + list2upd.Add(cRec); + } + } + // aggiungo task di setParameters x la commessa... + updDict.Add(taskType.fixStopSetup, outData); + updDict.Add(taskType.forceResetPzCount, outData); + updDict.Add(taskType.setParameter, setCommVal); + // salvo + await AddTask4MacListAsync(machine.IdxMacchinaSlave, updDict); + await MachineParamUpsertAsync(machine.IdxMacchinaSlave, list2upd); + } + } + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}"); + } + } + return answ; + } + + public string CalcRecipe(RecipeModel currRecipe) + { + return mongoController.CalcRecipe(currRecipe); + } + + /// + /// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE + /// + /// idx macchina + /// valOut ingresso + /// data-ora evento (server) + /// sequenza dati inviati + /// dati macchina in cache (opzionale, se null fa lookup) + /// + public async Task CheckMicroStatoAsync(string idxMacchina, string valore, DateTime dtEve, string contatore, Dictionary? datiMaccCache = null) + { + // recupero SE IMPIEGATO REDIS i valori del Dictionary della macchina... + Dictionary datiMacc = datiMaccCache ?? await mDatiMacchineAsync(idxMacchina); + + // processing + inputComandoMapo answ = new inputComandoMapo(); + + // SE no trovassi verifico se esista la macchina altrimenti la creo... REDIS compliant + if (!datiMacc.ContainsKey(idxMacchina)) + { + await verificaIdxMacchinaAsync(idxMacchina); + } + + // continuo processing... + string CodArticolo = datiMacc["CodArticolo"]; + if (string.IsNullOrEmpty(CodArticolo)) + { + var allDatiMacch = await IocDbController.DatiMacchineGetAllAsync(); + var recMacc = allDatiMacch.FirstOrDefault(x => x.IdxMacchina == idxMacchina); + if (recMacc != null) + { + CodArticolo = recMacc.CodArticoloA; + } + } + // preparo gestione val ingresso + int? valINT = 0; + int idxTipoEv = 0; + int next_idxMS = 0; + try + { + valINT = int.Parse(valore, NumberStyles.HexNumber); + int idxFamIn = Convert.ToInt32(datiMacc["IdxFamIn"]); + int idxMicroStato = Convert.ToInt32(datiMacc["IdxMicroStato"]); + int valIOB = Convert.ToInt32(valINT); + next_idxMS = idxMicroStato; + // recupero singolo valOut (stringa) x chiave + string todoSMI = await ValoreSmiAsync(idxFamIn, idxMicroStato, valIOB); + // solo se ho trovato un risultato nella tab SMI della famiglia macchina... + if (!string.IsNullOrEmpty(todoSMI) && todoSMI.Contains("_")) + { + // splitto e salvo valori OUT... + string[] valori = todoSMI.Split('_'); + idxTipoEv = Convert.ToInt32(valori[0]); + next_idxMS = Convert.ToInt32(valori[1]); + } + } + catch (Exception exc) + { + Log.Error($"[ChkMiSt_5a] - - Eccezione in recupero riga Trans ingressi | idxMacchina {idxMacchina} | valINT {valINT}:{Environment.NewLine}{exc}", Environment.NewLine, exc, valINT, idxMacchina); + } + + // effettuo update vari SU DB!!! + MicroStatoMacchinaModel newRecMsm = new MicroStatoMacchinaModel() + { + IdxMacchina = idxMacchina, + IdxMicroStato = next_idxMS, + InizioStato = dtEve, + Value = valore + }; + if (idxTipoEv > 0) + { + // preparo record + string valEsteso = string.Format("[{0}] {1}", contatore.PadLeft(3, '0'), valore); + // creo evento + EventListModel newRecEv = new EventListModel() + { + CodArticolo = CodArticolo, + IdxMacchina = idxMacchina, + IdxTipo = idxTipoEv, + InizioStato = dtEve, + MatrOpr = 0, + pallet = "-", + Value = valEsteso + }; + // salva e processa evento + microstato + answ = await scriviRigaEventoAsync(newRecMsm, newRecEv); + } + else + { + if (useFactory) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var iocService = scope.ServiceProvider.GetRequiredService(); + // solo microstato + await iocService.MicroStatoMacchinaUpsertAsync(newRecMsm); + } + else + { + // solo microstato + await IocDbController.MicroStatoMacchinaUpsertAsync(newRecMsm); + } + } + return answ; + } + + public async Task> ConfigGetAllAsync() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + List? result = new List(); + // cerco in redis... + RedisValue rawData = await redisDb.StringGetAsync(Utils.redisConfKey); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ConfigGetAllAsync Read from REDIS: {ts.TotalMilliseconds}ms"); + } + else + { + result = await IocDbController.ConfigGetAllAsync(); + //result = await Task.FromResult(SpecDbController.ConfigGetAllAsync()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(Utils.redisConfKey, rawData, getRandTOut(redisLongTimeCache)); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ConfigGetAllAsync Read from DB: {ts.TotalMilliseconds}ms"); + } + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Reset dati cache config + /// + /// + public async Task ConfigResetCache() + { + await redisDb.StringSetAsync(Utils.redisConfKey, ""); + } + + /// + /// Update chiave config + /// + /// + public async Task ConfigUpdate(ConfigModel updRec) + { + return await Task.FromResult(SpecDbController.ConfigUpdate(updRec)); + } + + /// + /// Elenco completo valori DatiMacchine + /// + /// + public async Task> DatiMacchineGetAll() + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisBaseAddr}:TabDatiMacchine:ALL"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.DatiMacchineGetAll()); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"DatiMacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + public async Task> DecNumArtGetFiltAsync(string codArt = "") + { + List result = new(); + string tag = string.IsNullOrEmpty(codArt) ? "ALL" : codArt; + string currKey = $"{Utils.redisDecNumArtKey}:{tag}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + result = await IocDbController.DecNumArtGetFiltAsync(codArt); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + return result; + } + + /// + /// Dispose del connettore ai dati + /// + public void Dispose() + { + // Clear database controller + SpecDbController.Dispose(); + mongoController.Dispose(); + redisConn.Dispose(); + } + + /// + /// Restituisce l'elenco delle date dei dossier x una macchina (se presenti) Impiegata anche + /// cache redis + /// + /// + /// + public async Task> DossierLastByMachAsync(string idxMacchina) + { + List result = new List(); + + var currKey = $"{Utils.redisDossByMacLast}:{idxMacchina}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + result = await IocDbController.DossGetLastByMaccAsync(idxMacchina); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Eliminazione di un dossier + /// + /// record dossier da eliminare + /// + public async Task DossiersDeleteRecord(DossierModel selRecord) + { + bool result = false; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + result = await SpecDbController.DossiersDeleteRecord(selRecord); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{Utils.redisDossByMac}:*"); + bool answ = await RedisFlushPatternAsync(pattern); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"DossiersDeleteRecord | IdxMacchina {selRecord.IdxMacchina} | DtRif {selRecord.DtRif} | IdxODL {selRecord.IdxODL} | {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco ultimi n record DOssiers (che contengono ad esempio "salvataggi" di FLuxLog) dato + /// macchina (ordinato x data registrazione) + /// + /// * = tutte, altrimenti solo x una data macchina + /// Data minima per estrazione records + /// Data Massima per estrazione records + /// Num Max records da recuperare + /// + public async Task> DossiersGetLastFilt(string IdxMacchina, string CodArticolo, DateTime DtStart, DateTime DtEnd, int MaxRec) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisDossByMac}:{IdxMacchina}:{CodArticolo}:{DtStart:yyyyMMddHHmm}:{DtEnd:yyyyMMddHHmm}:{MaxRec}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await SpecDbController.DossiersGetLastFiltAsync(IdxMacchina, CodArticolo, DtStart, DtEnd, MaxRec); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"DossiersGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Inserimento nuovo record dossier + /// + /// + /// + public async Task DossiersInsert(DossierModel currDoss) + { + // aggiorno record sul DB + bool answ = await SpecDbController.DossiersInsert(currDoss); + + return answ; + } + + /// + /// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache redis + /// + /// macchina + /// NUm massimo secondi per recuperare dati correnti + /// DataOra riferimento x cui prendere valori antecedenti + /// + public async Task DossiersTakeParamsSnapshotLast(string IdxMacchina, DateTime dtMin, DateTime dtMax) + { + bool answ = false; + await Task.Delay(1); + Log.Info($"Richiesta snapshot per macchina {IdxMacchina} | periodo {dtMin} --> {dtMax}"); + // chiamo stored x salvare parametri + SpecDbController.DossiersTakeParamsSnapshotLast(IdxMacchina, dtMin, dtMax); + // elimino cache redis... + RedisValue pattern = new RedisValue($"{Utils.redisDossByMac}:*"); + answ = await RedisFlushPatternAsync(pattern); + Log.Info($"Svuotata cache dossier | {pattern}"); + return answ; + } + + /// + /// Update valOut dossier + /// + /// + /// + public async Task DossiersUpdateValore(DossierModel currDoss) + { + // aggiorno record sul DB + bool answ = await SpecDbController.DossiersUpdateValore(currDoss); + + return answ; + } + + /// + /// Restitusice elenco aziende + /// + /// + public Task> ElencoAziende() + { + return Task.FromResult(SpecDbController.AnagGruppiAziende()); + } + + /// + /// Restitusice elenco fasi + /// + /// + public Task> ElencoGruppiFase() + { + List result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisAnagGruppi}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + var rawResult = JsonConvert.DeserializeObject>($"{rawData}"); + if (rawResult != null) + { + result = rawResult; + } + readType = "REDIS"; + } + else + { + result = SpecDbController.AnagGruppiFase(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ElencoGruppiFase | Read from {readType}: {ts.TotalMilliseconds}ms"); + return Task.FromResult(result); + } + + public Task> ElencoLink() + { + return Task.FromResult(SpecDbController.ElencoLink()); + } + + /// + /// Aggiunta record EventList + /// + /// + /// + public async Task EvListInsert(EventListModel newRec) + { + return await SpecDbController.EvListInsert(newRec); + } + + /// + /// Imposta in redis la scadenza della pagina x il reload + /// + /// + /// + public DateTime ExpiryReloadParamGet() + { + DateTime dtRif = DateTime.Now; + string currKey = $"{Utils.redisParamPageExp}"; + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + dtRif = JsonConvert.DeserializeObject($"{rawData}"); + } + return dtRif; + } + + /// + /// Imposta in redis la scadenza della pagina x il reload + /// + /// + /// + public bool ExpiryReloadParamSet(DateTime expTime) + { + bool fatto = false; + string currKey = $"{Utils.redisParamPageExp}"; + string rawData = JsonConvert.SerializeObject(expTime); + fatto = redisDb.StringSet(currKey, rawData); + return fatto; + } + + /// + /// Task completo sistemazione dossier quotidiani mancanti + /// + /// + /// + public async Task FixDailyDossierAsync(string idxMacc) + { + string answ = ""; + // verifico se si possa processare, ovvero tab ConfFlux x macchina sia valorizzata... + var confDataMach = await ConfFluxMach(idxMacc); + if (confDataMach.Count > 0) + { + // determino ultima data da processare (inizio oggi, a mezzanotte) + DateTime dtTo = DateTime.Today; + DateTime dtFrom = dtTo; + // determino data di partenza, prima da dossier esistenti + var listaDoss = await DossierLastByMachAsync(idxMacc); + if (listaDoss.Count > 0) + { + // primo giorno DOPO ultima registrazione + dtFrom = listaDoss + .Select(x => x.DtRif) + .OrderByDescending(x => x) + .FirstOrDefault() + .AddDays(1); + } + else + { + // ...o da fluxLog acquisiti... + var listaFL = await FluxLogFirstByMachAsync(idxMacc); + if (listaFL.Count > 0) + { + // giorno successivo a prima registrazione + dtFrom = listaFL + .OrderBy(x => x) + .FirstOrDefault() + .AddDays(1); + } + } + string caller = $"takeFlogSnapshot({idxMacc})"; + DateTime dtStart = dtFrom.Date; + DateTime dtEnd = dtFrom; + // max 10 dossier alla volta (se non configurato diversamente) + int maxAdd = 1; + string confVal = await tryGetConfigAsync("IO_numDossMaxCreate"); + if (!string.IsNullOrEmpty(confVal)) + { + int.TryParse(confVal, out maxAdd); + } + + if (dtStart < dtTo) + { + // verifico di avere almeno 1 dossier da produrre ciclo fino ad esaurire le + // date da processare + while (dtStart < dtTo && maxAdd > 0) + { + // sistemo end + dtEnd = dtStart.AddDays(1); + // effettuo chiamata registrazione snapshot! + answ = await FluxLogSaveSnapshotAsync(idxMacc, dtStart, dtEnd, caller); + // incremento START... + dtStart = dtEnd; + // riduco il numero di chiamate ammesse x singolo task + maxAdd--; + } + // reset cache dossier... + await DossierLastByMachResetAsync(idxMacc); + answ = "OK"; + } + else + { + Log.Warn("FixDailyDossierAsync | NO more to add"); + } + } + else + { + Log.Warn("FixDailyDossierAsync | NO ConfFluxData"); + } + return answ; + } + + public async Task FlushRedisCache() + { + await Task.Delay(1); + RedisValue pattern = Utils.RedValue("*"); + bool answ = await RedisFlushPatternAsync(pattern); + // rileggo vocabolario.,.. + ObjVocabolario = VocabolarioGetAll(); + return answ; + } + + public async Task FlushRedisKey(string redKey) + { + await Task.Delay(1); + RedisValue pattern = Utils.RedValue(redKey); + bool answ = await RedisFlushPatternAsync(pattern); + return answ; + } + + public List FluxLogDtoGetByFlux(string Valore) + { + List answ = new List(); + DossierFluxLogDTO? result = JsonConvert.DeserializeObject(Valore); + if (result != null) + { + if (result.ODL != null) + { + answ = result + .ODL + .OrderBy(x => x.CodFlux) + .ToList(); + // inizializzo SE necessario + foreach (var item in answ) + { + item.ValoreEdit = String.IsNullOrEmpty(item.ValoreEdit) ? item.Valore : item.ValoreEdit; + } + } + } + return answ; + } + + /// + /// Elenco ultimi n record flux log dato macchina e flusso (ordinato x data registrazione) + /// + /// Data massima x eventi + /// Data minima x eventi + /// * = tutte, altrimenti solo x una data macchina + /// *=tutti, altrimenti solo selezionato + /// numero massimo record da restituire + /// + public async Task> FluxLogGetLastFilt(DateTime DtMax, DateTime DtMin, string IdxMacchina, string CodFlux, int MaxRec, double redisCacheSec) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisFluxLogFilt}:{IdxMacchina}:{CodFlux}:{MaxRec}:{DtMax:yyyyMMddHHmm}:{DtMin:yyyyMMddHHmm}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.FluxLogGetLastFilt(DtMax, DtMin, IdxMacchina, CodFlux, MaxRec)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + if (string.IsNullOrEmpty(canCacheParametri)) + { + canCacheParametri = await tryGetConfigAsync("SPEC_ParametriEnableRedisCache"); + } + if (canCacheParametri != "false") + { + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisCacheSec)); + } + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"FluxLogGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Restituisce il valOut dell'ODL corrente (ODL deve esserci per gestione contapezzi, senza + /// ODL NO invio/gestione ODL) + /// + /// + /// + public async Task GetCurrOdlAsync(string idxMacchina) + { + string result = ""; + string currKey = $"{Utils.redisOdlCurrByMac}:{idxMacchina}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = $"{rawData}"; + } + else + { + result = await GetCurrOdlByProdAsync(idxMacchina); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + return result; + } + + /// + /// Restituisce il valOut dell'ODL corrente (ODL deve esserci per gestione contapezzi, senza + /// ODL NO invio/gestione ODL) + /// + /// + /// indica se forzare lettura da db (true) o meno + /// + public async Task GetCurrOdlAsync(string idxMacchina, bool forceDb) + { + string answ = ""; + // se ho forceDB leggo dai dati prod... + if (forceDb) + { + var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now, true); + if (datiProd != null) + { + answ = datiProd.IdxOdl.ToString(); + } + } + else + { + answ = await GetCurrOdlAsync(idxMacchina); + } + return answ; + } + + /// + /// Restituisce il valOut dell'ultimo ODL + /// + /// + /// + public async Task GetLastOdlAsync(string idxMacchina) + { + ODLExpModel result = new ODLExpModel(); + string currKey = $"{Utils.redisOdlLastByMac}:{idxMacchina}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject($"{rawData}") ?? new(); + } + else + { + result = await IocDbController.OdlLastByMaccAsync(idxMacchina); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + return result; + } + + /// + /// Effettua calcolo data-ora di riferimento per il server a partire da + /// + /// + /// + /// + public DateTime GetSrvDtEvent(string dtEve, string dtCurr) + { + DateTime dataOraEvento = DateTime.Now; + // 2017.09.14 trimmo eventualmente lo zero finale dalle date SE supera i millisecondi... + dtEve = dtEve.Length > 17 ? dtEve.Substring(0, 17) : dtEve; + dtCurr = dtCurr.Length > 17 ? dtCurr.Substring(0, 17) : dtCurr; + DateTime dtEvento, dtCorrente; + // controllo: se ho valori dt x evento e orario DIVERSI per acquisitore IOB calcolo + // dataOraEvento corretto + if (dtEve != dtCurr) + { + Int64 delta = 0; + try + { + // se ho meno decimali x evento rispetto dtCorrente... + if (dtEve.Length < dtCurr.Length) + { + dtEve = dtEve.PadRight(dtCurr.Length, '0'); + } + delta = Convert.ToInt64(dtCurr) - Convert.ToInt64(dtEve); + // se meno di 60'000 ms ... + if (delta < 59999) + { + dataOraEvento = dataOraEvento.AddMilliseconds(-delta); + } + else + { + // in questo caso elimino i MS dalle stringhe e converto i datetime.... + CultureInfo provider = CultureInfo.InvariantCulture; + string format = "yyyyMMddHHmmssfff"; + dtEvento = DateTime.ParseExact(dtEve, format, provider); + dtCorrente = DateTime.ParseExact(dtCurr, format, provider); + TimeSpan deltaTS = dtCorrente.Subtract(dtEvento); + dataOraEvento = dataOraEvento.Add(-deltaTS); + } + } + catch (Exception exc) + { + Log.Error($"getSrvDtEvent | Errore calcolo ora corrente da IOB remoto | dtEve: {dtEve} | dtCurr: {dtCurr}{Environment.NewLine}" + + $"{exc}"); + } + } + + return dataOraEvento; + } + + /// + /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato + /// + /// + /// + public async Task> GetTask2ExeMacchinaAsync(string idxMacchina) + { + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + return await RedisGetHashDictAsync(currHash); + } + + /// + /// Init ricetta + /// + /// + /// + /// + /// + public RecipeModel InitRecipe(string confPath, int idxPODL, Dictionary CalcArgs) + { + return mongoController.InitRecipe(confPath, idxPODL, CalcArgs); + } + + /// + /// Restituisce il valOut booleano se la macchina sia abilitata all'input + /// + /// + /// + public async Task IobInsEnabAsync(string idxMacchina) + { + var key = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); + + string? val = await redisDb.HashGetAsync(key, "insEnabled"); + + if (val == null) + { + var data = await ResetDatiMacchinaAsync(idxMacchina); + data.TryGetValue("insEnabled", out val); + } + + return val != null && (val == "1" || val.ToLower() == "true"); + } + + /// + /// Restituisce il valOut booleano se la macchina sia master + /// + /// + /// + public async Task IobIsMasterAsync(string idxMacchina) + { + var key = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); + + // 1. Tentativo ottimizzato: leggiamo solo il campo che ci serve + // Supponendo che tu usi StackExchange.Redis direttamente o un wrapper + string? val = await redisDb.HashGetAsync(key, "Master"); + + // 2. Se non c'è in cache, carichiamo/resettiamo tutto + if (val == null) + { + //var data = ResetDatiMacchina(idxMacchina); + var data = await ResetDatiMacchinaAsync(idxMacchina); + data.TryGetValue("Master", out val); + } + + // 3. Parsing sicuro + return val != null && (val == "1" || val.ToLower() == "true"); + } + + /// + /// Restituisce il valOut booleano se la macchina sia abilitata all'inserimento COMPLETO nel + /// Signal Log + /// + /// + /// + public async Task IobSLogEnabAsync(string idxMacchina) + { + bool answ = false; + // ORA recupero da memoria redis... + try + { + var currHash = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); + RedisValue rawData = await redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled"); + // se è vuoto... leggo da DB e popolo! + if (!rawData.HasValue) + { + await ResetDatiMacchinaAsync(idxMacchina); + // riprovo + rawData = await redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled"); + } + + // provo conversione + bool.TryParse($"{rawData}", out answ); + } + catch (Exception exc) + { + Log.Error($"Errore IobSLogEnabAsync | idxMacchina {idxMacchina}:{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// + /// idxMacc odl da cercare + /// + public async Task> ListGiacenze(int IdxOdl) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisGiacenzaList}:{IdxOdl}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.ListGiacenze(IdxOdl)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ListGiacenze | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + public async Task> ListValuesFilt(string tabName, string fieldName) + { + List resultList = new List(); + string tag = ""; + tag += string.IsNullOrEmpty(tabName) ? "" : $"{tabName}:"; + tag += string.IsNullOrEmpty(fieldName) ? "" : $"{fieldName}:"; + var currKey = $"{Utils.redisConfFlux}:{tag}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + resultList = await IocDbController.ListValuesFiltAsync(tabName, fieldName); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(resultList); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (resultList == null) + { + resultList = new(); + } + return resultList; + } + + /// + /// Elenco completo valori Macchine 2 Slave + /// + /// + public async Task> Macchine2SlaveGetAllAsync() + { + List? result = new List(); + string currKey = $"{Utils.redisBaseAddr}:M2STab"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + } + else + { + if (useFactory) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var mtcService = scope.ServiceProvider.GetRequiredService(); + result = await mtcService.Macchine2SlaveAsync(); + } + else + { + result = await IocDbController.Macchine2SlaveAsync(); + } + + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); + } + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Elenco di tutte le macchine gestite + /// + /// + /// + public async Task> MacchineGetFilt(string codGruppo) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string keyGrp = codGruppo != "*" ? codGruppo : "ALL"; + string currKey = $"{Utils.redisMacList}:{keyGrp}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.MacchineGetFilt(codGruppo)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Verifica se la macchina abbia un codice PATH ricette associato + /// + /// + /// + public async Task MacchineRecipeArchive(string idxMacchina) + { + string? result = ""; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisMacRecipePath}:{idxMacchina}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject($"{rawData}"); + readType = "REDIS"; + } + else + { + //recupero elenco macchine... + var machineList = await MacchineGetFilt("*"); + var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); + result = currMach != null ? currMach.RecipeArchivePath : null; + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineRecipeArchive | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result ?? ""; + } + + /// + /// Verifica se la macchina abbia un codice CONF ricetta associato + /// + /// + /// + public async Task MacchineRecipeConf(string idxMacchina) + { + string? result = ""; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisMacRecipeConf}:{idxMacchina}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject($"{rawData}"); + readType = "REDIS"; + } + else + { + //recupero elenco macchine... + var machineList = await MacchineGetFilt("*"); + var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); + result = currMach != null ? currMach.RecipePath : null; + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineRecipeConf | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result ?? ""; + } + + /// + /// Elenco idxMacc Macchine che abbiano dati FLuxLog, nel periodo indicato + /// + /// + /// + /// + public async Task> MacchineWithFlux(DateTime dtStart, DateTime dtEnd) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisMacByFlux}:{dtStart:yyyyMMddHHmm}:{dtEnd:yyyyMMddHHmm}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await SpecDbController.MacchineWithFlux(dtStart, dtEnd); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"MacchineWithFlux | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) + /// + /// + /// + public List MachineParamList(string idxMacchina) + { + // setup parametri costanti + string source = "NA"; + Stopwatch sw = new Stopwatch(); + sw.Start(); + List? result = new List(); + // cerco in _redisConn... + var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue && rawData.Length() > 2) + { + var rawVal = JsonConvert.DeserializeObject>($"{rawData}"); + // ordino! + result = rawVal + .OrderBy(x => x.displOrdinal) + .ThenBy(x => x.description) + .ToList(); + source = "REDIS"; + } + if (result == null) + { + result = new List(); + source = "NONE"; + } + sw.Stop(); + Log.Debug($"MachineParamList | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) + /// + /// + /// + public async Task> MachineParamListAsync(string idxMacchina) + { + // setup parametri costanti + string source = "NA"; + Stopwatch sw = new Stopwatch(); + sw.Start(); + List? result = new List(); + // cerco in _redisConn... + var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue && rawData.Length() > 2) + { + var rawVal = JsonConvert.DeserializeObject>($"{rawData}"); + // ordino! + result = rawVal + .OrderBy(x => x.displOrdinal) + .ThenBy(x => x.description) + .ToList(); + source = "REDIS"; + } + if (result == null) + { + result = new List(); + source = "NONE"; + } + sw.Stop(); + Log.Debug($"MachineParamListAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Lista parametri correnti che necessitano di write della macchina (ex getCurrObjItems) + /// + /// + /// + public async Task> MachineParamListPendingWriteAsync(string idxMacchina) + { + List? result = new List(); + // recupero tutti i parametri + var allData = await MachineParamListAsync(idxMacchina); + result = allData.Where(x => x.writable && !string.IsNullOrEmpty(x.reqValue)).ToList(); + return result; + } + + /// + /// Esegue aggiornamento MachineParamList (ex CurrObjItems) Async + /// + /// + /// + /// + public async Task MachineParamListSetAsync(string idxMacchina, List currData) + { + string serVal = JsonConvert.SerializeObject(currData); + var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); + bool fatto = await redisDb.StringSetAsync(currKey, serVal); + return fatto; + } + + /// + /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) + /// + /// + /// + /// + public async Task MachineParamUpsertAsync(string idxMacchina, List innovations) + { + bool answ = false; + if (innovations != null) + { + Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); + // leggo i valori attuali... + List actValues = await MachineParamListAsync(idxMacchina); + // per ogni valOut passatomi faccio insert o update rispetto elenco valori correnti + // in REDIS + foreach (var item in actValues) + { + // cerco nelle innovazioni SE CI SIA il valOut... + var trovato = innovations.Find(obj => obj.uid == item.uid); + // se non trovato nelle innovazioni... + if (trovato == null) + { + // lo ri-aggiungo x non perderlo + innovations.Add(item); + // in base a value (value == null) uso debug/info + if (string.IsNullOrEmpty(item.value)) + { + Log.Debug($"idxMacchina: {idxMacchina} | innovations | add | item.uid: {item.uid} | item.value: {item.value}"); + } + else + { + Log.Info($"idxMacchina: {idxMacchina} | innovations | add | item.uid: {item.uid} | item.value: {item.value}"); + } + } + // altrimenti aggiorno campo (non trasmesso) name e tengo il resto... + else + { + trovato.name = item.name; + Log.Debug($"idxMacchina: {idxMacchina} | innovations | update | item.uid: {item.uid} | item.value: {item.value} --> {trovato.value} "); + } + } + // serializzo e salvo + answ = await MachineParamListSetAsync(idxMacchina, innovations); + } + return answ; + } + + /// + /// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato + /// + /// + /// + public async Task> mDatiMacchineAsync(string idxMacchina) + { + // hard coded dimensione vettore DatiMacchine + Dictionary answ = new Dictionary(); + // ORA recupero da memoria redis... + try + { + var currHash = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); + answ = await RedisGetHashDictAsync(currHash); + // se è vuoto... leggo da DB e popolo! + if (answ.Count == 0) + { + answ = await ResetDatiMacchinaAsync(idxMacchina); + } + } + catch (Exception exc) + { + Log.Error($"Errore in compilazione dati Macchine x Redis - idxMacchina {idxMacchina}:{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato + /// + /// + /// + public Dictionary mOptParMacchina(string idxMacchina) + { + // hard coded dimensione vettore DatiMacchine + Dictionary answ = new Dictionary(); + // ORA recupero da memoria redis... + RedisKey currHash = Utils.RedKeyOptPar(idxMacchina, MpIoNS); + answ = RedisGetHashDict(currHash); + return answ; + } + + /// + /// Restitusice elenco KVP dei TASK SALVATI (da passare a IOB-WIN) per l'impianto indicato + /// + /// + /// + public Dictionary mSavedTaskMacchina(string idxMacchina) + { + // hard coded dimensione vettore DatiMacchine + Dictionary answ = new Dictionary(); + // ORA recupero da memoria redis... + try + { + RedisKey currHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina); + answ = RedisGetHashDict(currHash); + } + catch (Exception exc) + { + Log.Info($"Errore in recupero dati SAVED TASK x Redis mSavedTaskMacchina | idxMacchina {idxMacchina}{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// Elenco da tabella MappaStatoExplModel + /// + /// + public async Task> MseGetAllAsync(bool forceDb = false) + { + Stopwatch sw = new Stopwatch(); + string source = "DB"; + sw.Start(); + List? result = new List(); + // cerco in _redisConn... + RedisValue rawData = await redisDb.StringGetAsync(Constants.redisMseKey); + if (rawData.HasValue && !forceDb) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + source = "REDIS"; + } + else + { + result = await IocDbController.MseGetAllAsync(maxAge); + // serializzp e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(Constants.redisMseKey, rawData, getRandTOut(redisShortTimeCache / 2)); + } + if (result == null) + { + result = new List(); + } + sw.Stop(); + Log.Debug($"MseGetAllAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Restitusice elenco KVP (async) per evitare blocchi quando chiamato da metodi asincroni + /// + /// + /// + public async Task[]> mTabMSMIAsync(string idxMacchina) + { + KeyValuePair[] answ = new KeyValuePair[1]; + answ[0] = new KeyValuePair("0", "0"); + try + { + var currHash = Utils.RedKeyMsmi(idxMacchina); + answ = await RedisGetHashAsync(currHash); + if (answ == null || answ.Length == 0) + { + answ = await resetMSMIAsync(idxMacchina); + } + } + catch (Exception exc) + { + Log.Error($"Errore in compilazione Tabella Multi State Machine Ingressi x Redis:{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato + /// + /// + /// + public Dictionary mTaskMacchina(string idxMacchina) + { + // hard coded dimensione vettore DatiMacchine + Dictionary answ = new Dictionary(); + // ORA recupero da memoria redis... + try + { + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + answ = RedisGetHashDict(currHash); + } + catch (Exception exc) + { + Log.Error(string.Format("Errore in mTaskMacchina | idxMacchina {2}:{0}{1}", Environment.NewLine, exc, idxMacchina)); + } + return answ; + } + + /// + /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato + /// + /// + /// + public async Task> mTaskMacchinaAsync(string idxMacchina) + { + // hard coded dimensione vettore DatiMacchine + Dictionary answ = new Dictionary(); + // ORA recupero da memoria redis... + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + answ = await RedisGetHashDictAsync(currHash); + return answ; + } + + /// + /// Generazione autoOdl + /// + /// + /// + /// + /// + /// + public async Task OdlAutoDayGenAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo) + { + var result = await IocDbController.OdlAutoDayGenAsync(idxMacchina, dataInizio, dataFine, codArticolo); + return result; + } + + /// + /// Generazione autoOdl + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task OdlAutoDayGenFullAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo, int? pzPODL, int? pzPallet, string? keyRichiesta, int? tcAssegnato, string? codGruppo, bool flgCreaPODL, bool flgCheckTC) + { + var result = await IocDbController.OdlAutoDayGenFullAsync(idxMacchina, dataInizio, dataFine, codArticolo, pzPODL, pzPallet, keyRichiesta, tcAssegnato, codGruppo, flgCreaPODL, flgCheckTC); + return result; + } + + /// + /// Elenco ODL dato batch selezionato + /// + /// Batch richiesto + /// + public async Task> OdlByBatch(string BatchSel) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = Utils.redisOdlByBatch; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.OdlByBatch(BatchSel)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlByBatch | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// ODL da chiave + /// + /// + /// + public ODLExpModel OdlByKey(int IdxOdl) + { + ODLExpModel? result = new ODLExpModel(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + result = SpecDbController.OdlByKey(IdxOdl); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Effettua chiusura dell'ODL indicato, andand + /// + /// idx odl da chiudere + /// idx macchina + /// matricola operatore + /// indica se confermare i pezzi priam di chiudere ODL + public async Task ODLClose(int idxOdl, string idxMacchina, int matrOpr, bool confPezzi) + { + bool fatto = false; + await Task.Delay(1); + // recupero dati x conf modalità conferma + var configData = await ConfigGetAllAsync(); + if (configData != null) + { + bool confRett = false; + var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); + if (currRec != null) + { + bool.TryParse(currRec.Valore, out confRett); + } + int modoConfProd = 0; + currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); + if (currRec != null) + { + int.TryParse(currRec.Valore, out modoConfProd); + } + // chiamo metodo conferma! + fatto = await SpecDbController.ODLClose(idxOdl, idxMacchina, matrOpr, confPezzi, confRett, modoConfProd); + } + + return fatto; + } + + public async Task OdlCurrByMaccAsync(string IdxMacchina) + { + ODLExpModel result = new(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisOdlList}:Current:{IdxMacchina}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject($"{rawData}") ?? new(); + readType = "REDIS"; + } + else + { + result = await IocDbController.OdlCurrByMaccAsync(IdxMacchina); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"OdlCurrByMaccAsync | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Record ODL da chaive + /// + /// + public async Task OdlGetByKey(int IdxOdl) + { + await Task.Delay(1); + var dbResult = await SpecDbController.OdlGetByKey(IdxOdl); + return dbResult; + } + + /// + /// ODL correnti (tutti) + /// + /// + /// + public List OdlGetCurrent() + { + List? dbResult = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisOdlCurrByMac}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + try + { + dbResult = JsonConvert.DeserializeObject>($"{rawData}"); + } + catch + { } + readType = "REDIS"; + } + else + { + dbResult = SpecDbController.OdlGetCurrent().Select(x => x.IdxMacchina).Distinct().ToList(); + rawData = JsonConvert.SerializeObject(dbResult); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(3)); + } + if (dbResult == null) + { + dbResult = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlGetCurrent | Read from {readType}: {ts.TotalMilliseconds}ms"); + + return dbResult; + } + + /// + /// elenco TUTTI gli ODL + /// + /// + /// + public List OdlListAll() + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + result = SpecDbController.OdlListAll(); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlListAll | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco ODL filtrati x stato, articolo, KeyRich (che contiene stato) + /// + /// Stato ODL: true=in corso/completato + /// Cod articolo + /// KeyRich (parziale) da cercare (es cod stato x yacht) + /// Reparto selezionato + /// Macchina selezionata + /// Data inizio + /// Data fine + /// + public async Task> OdlListGetFilt(bool inCorso, string codArt, string keyRichPart, string Reparto, string IdxMacchina, DateTime startDate, DateTime endDate) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisOdlList}:{inCorso}:{codArt}:{keyRichPart}:{Reparto}:{IdxMacchina}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.ListODLFilt(inCorso, codArt, keyRichPart, Reparto, IdxMacchina, startDate, endDate)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"OdlListGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elenco di tutti i parametri filtrati x macchina + /// + /// * = tutte, altrimenti solo x una data macchina + /// + public async Task> ParametriGetFilt(string IdxMacchina) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisFluxByMac}:{IdxMacchina}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.ParametriGetFilt(IdxMacchina)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"ParametriGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Eliminazione record selezionato + /// + /// + /// + public async Task POdlDeleteRecord(PODLExpModel currRec) + { + var dbResult = await SpecDbController.PODLDeleteRecord(currRec); + // elimino cache redis... + await POdlFlushCache(); + await Task.Delay(1); + return dbResult; + } + + /// + /// Avvio fase setup per il record selezionato + /// + /// + /// + public async Task POdlDoSetup(PODLExpModel currRec) + { + var dbResult = await SpecDbController.PODL_startSetup(currRec, 0, 1, 1, "", DateTime.Now); + // elimino cache redis... + await POdlFlushCache(); + await Task.Delay(1); + return dbResult; + } + + /// + /// Recupero PODL da chiave + /// + /// + /// + public async Task POdlGetByKey(int idxPODL) + { + PODLModel result = new PODLModel(); + if (idxPODL != 0) + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisPOdlByPOdl}:{idxPODL}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + var rawResult = JsonConvert.DeserializeObject($"{rawData}"); + if (rawResult != null) + { + result = rawResult; + readType = "REDIS"; + } + } + else + { + result = await SpecDbController.PODL_getByKey(idxPODL); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new PODLModel(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"POdlGetByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); + } + else + { + Log.Debug("Errore IdxPODL = 0"); + } + return result; + } + + public async Task> POdlGetByMaccArtAsync(string idxMacchina, string codArticolo, string codGruppo, bool onlyFree) + { + List result = new List(); + + var currKey = $"{Utils.redisPOdlByMaccArt}:{idxMacchina}"; + if (!string.IsNullOrEmpty(codArticolo)) + { + currKey += $":A{codArticolo}"; + } + if (!string.IsNullOrEmpty(codGruppo)) + { + currKey += $":G{codGruppo}"; + } + currKey += onlyFree ? $":FREE" : ":ALL"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + result = await IocDbController.POdlGetByMaccArtAsync(idxMacchina, codArticolo, codGruppo, onlyFree); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + return result; + } + + /// + /// Recupero PODL da IdxODL + /// + /// + /// + public PODLModel POdlGetByOdl(int idxODL) + { + PODLModel result = new PODLModel(); + if (idxODL != 0) + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisPOdlByOdl}:{idxODL}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + var rawResult = JsonConvert.DeserializeObject($"{rawData}"); + if (rawResult != null) + { + result = rawResult; + } + readType = "REDIS"; + } + else + { + result = SpecDbController.PODL_getByOdl(idxODL); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new PODLModel(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Trace($"POdlGetByOdl | Read from {readType}: {ts.TotalMilliseconds}ms"); + } + else + { + Log.Debug("Errore IdxODL = 0"); + } + return result; + } + + /// + /// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) + /// + /// Solo lanciati (1) o ancora disponibili (0) + /// KeyRich (parziale) da cercare (es cod stato x yacht) + /// Macchina + /// Gruppo + /// Data inizio + /// Data fine + /// + public async Task> POdlListGetFilt(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate) + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisPOdlList}:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; + // cerco in redis dato valOut sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = await Task.FromResult(SpecDbController.ListPODLFilt(lanciato, keyRichPart, idxMacchina, codGruppo, startDate, endDate)); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"POdlListGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Aggiornamento record selezionato + /// + /// + /// + public async Task POdlUpdateRecord(PODLModel currRec) + { + var dbResult = await SpecDbController.PODLUpdateRecord(currRec); + // elimino cache redis... + await POdlFlushCache(); + return dbResult; + } + + /// + /// Processa registrazione FL da IOB + /// + /// + /// + /// + /// + /// + /// + /// + /// + public async Task ProcessFluxLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, bool disabKA) + { + // se non vietato... + if (!disabKA) + { + // scrivo keep alive!!! (se necessario, altrimenti è in cache...) + await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); + } + + string answ = ""; + DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); + // inizio processing vero e proprio INPUT... + + if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(valore)) + { + string errore = "processFluxLog | Errore: parametri macchina/valOut vuoti"; + Log.Error(errore); + answ = errore; + } + else + { + FluxLogModel newRec = new FluxLogModel() + { + IdxMacchina = idxMacchina, + dtEvento = dataOraEvento, + CodFlux = flux, + Valore = valore, + Cnt = contatore + }; + await IocDbController.FluxLogInsertAsync(newRec); + // 2022.06.06 salvo su redis il valOut ULTIMO del flux x recupero rapido ultimo valOut + var currKey = Utils.RedKeyLastFLog(idxMacchina, flux, MpIoNS); + // 10 min cache max... + await redisDb.StringSetAsync(currKey, valore, TimeSpan.FromMinutes(10)); + // registro in risposta che è andato tutto bene... + answ = "OK"; + } + + return answ; + } + + /// + /// Validazione preliminare valori input + /// + /// + /// + /// + /// + /// + private bool ValidateinputParams(string idxMacchina, string valore, string dtEve, string dtCurr) + { + bool isValid = false; + // preparo stringa valori correnti + string currVals = $"idxMacchina: {idxMacchina} | valOut: {valore} | dtEve: {dtEve} | dtCurr:{dtCurr}"; + if (dtEve == null || dtCurr == null) + { + Log.Warn($"procInput: null found | {currVals}"); + } + else if (dtEve.Length < 17 || dtCurr.Length < 17) + { + Log.Info($"procInput: invalid data | {currVals}"); + } + else if (string.IsNullOrEmpty(idxMacchina)) + { + Log.Info($"procInput: missing IdxMacchina | {currVals}"); + } + else if (string.IsNullOrEmpty(valore)) + { + Log.Info($"procInput: missing valOut | {currVals}"); + } + else + { + isValid = true; + } + return isValid; + } + + /// + /// Calcola dataora evento da info dt ricevute + /// + /// + /// + /// + private DateTime ParseEventTime(string dtEve, string dtCurr) + { + DateTime dataOraEvento = DateTime.Now; + + // fix formato dataora in ingresso + string stdEve = dtFormStd(dtEve); + string stdCurr = dtFormStd(dtCurr); + + // 2. Se le stringhe normalizzate coincidono, skip dei calcoli + if (stdEve != stdCurr) + { + // 3. Parsing sicuro + if (DateTime.TryParseExact(stdEve, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtEvento) && + DateTime.TryParseExact(stdCurr, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtCorrente)) + { + TimeSpan diff = dtCorrente - dtEvento; + double ms = Math.Abs(diff.TotalMilliseconds); + + // 4. Classificazione delta con switch expression (più leggibile e performante) + string deltaClass = ms switch + { + <= 10 => "0-10 ms", + <= 100 => "10-100 ms", + <= 1000 => "100-1000 ms", + <= 10000 => "1-10 sec", + _ => "> 10 sec" + }; + + Log.Debug($"Correzione delta {deltaClass}"); + + // 5. Correzione data/ora server: sottrao lo scarto calcolato + dataOraEvento = dataOraEvento.Subtract(diff); + } + else + { + Log.Error($"Errore parsing date: {stdEve} | {stdCurr}"); + } + } + return dataOraEvento; + } + + /// + /// Processa input da IOB eventualmente registrando i segnali inviati + /// + /// + /// + /// + /// + /// + /// + public async Task ProcessInputAsync(string idxMacchina, string valore, string dtEve, string dtCurr, string contatore) + { + string answ = ""; + if (ValidateinputParams(idxMacchina, valore, dtEve, dtCurr)) + { + DateTime dataOraEvento = ParseEventTime(dtEve, dtCurr); + + // se abilitato registro evento sul DB + if (await IobSLogEnabAsync(idxMacchina)) + { + int cntVal = 0; + int.TryParse(contatore, out cntVal); + await saveSigLogAsync(idxMacchina, valore, dataOraEvento, cntVal); + } + // continuo col resto + try + { + // scrivo keep alive!!! (se necessario, altrimenti è in cache...) + await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); + // Cache dati macchina per evitare lookup ridondanti + Dictionary datiMacc = await mDatiMacchineAsync(idxMacchina); + // verifico se sia una macchina MULTI.... + if (isMulti(idxMacchina, datiMacc)) + { + // inizio preprocessing + string newVal = ""; + // processo OGNI macchina a stati dell'impianto... (KEY:IdxMacchina / IdxMacchina#qualcosa, Val = IdxFamIn) + foreach (var item in await mTabMSMIAsync(idxMacchina)) + { + newVal = preProcInput(item.Key, valore, datiMacc); + // ora processo e salvo il valOut del microstato... + // INTERNAMENTE gestisce i casi DB/REDIS secondo necessità + await CheckMicroStatoAsync(item.Key, newVal, dataOraEvento, contatore, datiMacc); + } + } + else + { + // ora processo e salvo il valOut del microstato... INTERNAMENTE + // gestisce i casi DB/REDIS secondo necessità + await CheckMicroStatoAsync(idxMacchina, valore, dataOraEvento, contatore, datiMacc); + } + // forzo RESET dati macchina... + await ResetDatiMacchinaAsync(idxMacchina); + // registro in risposta che è andato tutto bene... + answ = "OK"; + } + catch (Exception exc) + { + string errore = $"Errore: {Environment.NewLine}{exc}"; + Log.Error(errore); + answ = errore; + } + } + return answ; + } + + /// + /// Processa registrazione UserLog da IOB + /// + /// Macchina + /// Flusso: DI/RC/RC + /// valOut = note/valString + /// data evento + /// data corrente + /// contatore invio + /// Matricola Operatore + /// label = causale scarto / tagCode + /// valNum = esitoOk (0/1) / Quantità di scarto associata + /// + public async Task ProcessUserLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, int matrOpr, string label, int valNum) + { + // scrivo keep alive!!! (se necessario, altrimenti è in cache...) + await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); + // 2017.09.14 trimmo eventualmente lo zero finale dalle date SE supera i millisecondi... + dtEve = dtEve.Length > 17 ? dtEve.Substring(0, 17) : dtEve; + dtCurr = dtCurr.Length > 17 ? dtCurr.Substring(0, 17) : dtCurr; + + string answ = ""; + DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); + + // inizio processing vero e proprio INPUT... + if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(flux)) + { + string errore = "processFluxLog | Errore: parametri macchina/flux vuoti"; + Log.Error(errore); + answ = errore; + } + else + { + // in base al flusso decido dove e cosa scrivere... + switch (flux) + { + case "DI": + RegistroDichiarazioniModel recDich = new RegistroDichiarazioniModel() + { + IdxMacchina = idxMacchina, + DtRec = dataOraEvento, + MatrOpr = matrOpr, + ValString = valore, + TagCode = label + }; + await IocDbController.RegDichiarInsertAsync(recDich); + break; + + case "RC": + bool esitoOk = valNum != 0; + await IocDbController.RegControlliInsertAsync(idxMacchina, matrOpr, esitoOk, valore, dataOraEvento); + break; + + case "RS": + RegistroScartiModel recSca = new RegistroScartiModel() + { + IdxMacchina = idxMacchina, + DataOra = dataOraEvento, + Causale = label, + Qta = valNum, + Note = valore, + MatrOpr = matrOpr + }; + await IocDbController.RegScartiInsertAsync(recSca); + break; + + default: + break; + } + // registro in risposta che è andato tutto bene... + answ = "OK"; + } + return answ; + } + + /// + /// Restituisce il contapezzi salvato per la macchina + /// + /// + /// + public async Task pzCounter(string idxMacchina) + { + int answ = -1; + try + { + var currKey = Utils.RedKeyPzCount(idxMacchina, MpIoNS); + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + int.TryParse(rawData, out answ); + } + else + { + answ = await PzCounterTcAsync(idxMacchina); + // salvo in _redisConn... + await redisDb.StringSetAsync(currKey, answ.ToString(), TimeSpan.FromSeconds(1)); + } + } + catch (Exception exc) + { + Log.Error($"Eccezione in pzCounter{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// Restituisce il contapezzi come CONTEGGIO da TCRilevati per la macchina - ASYNC + /// + /// + /// + public async Task PzCounterTcAsync(string idxMacchina) + { + int answ = -1; + DateTime dataRif = DateTime.Now; + var datiProd = await StatoProdMacchinaAsync(idxMacchina, dataRif); + if (datiProd != null) + { + answ = datiProd.PzTotODL; + } + return answ; + } + + /// + /// Ricerca ricetta su MongoDB dato PODL + /// + /// + /// + public async Task RecipeGetByPODL(int idxPODL) + { + RecipeModel? result = null; + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "MongoDB"; + result = await mongoController.RecipeGetByPODL(idxPODL); + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"RecipeGetByPODL | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } + + /// + /// Salva ricetta su MongoDB + /// + /// + /// + public async Task RecipeSetByPODL(RecipeModel currRecord) + { + bool answ = false; + answ = await mongoController.RecipeSetByPODL(currRecord); + if (answ) + { + await POdlFlushCache(); + } + return answ; + } + + /// + /// Effettua conteggio chaivi REDIS dato pattern ricerca + /// + /// + /// + public int RedisCountKey(string keyPattern) + { + int num = 0; + keyPattern = (string.IsNullOrEmpty(keyPattern) ? "**" : keyPattern); + try + { + var listEndpoints = redisConnAdmin.GetEndPoints(); + foreach (var endPoint in listEndpoints) + { + var server = redisConnAdmin.GetServer(endPoint); + foreach (RedisKey item in server.Keys(pattern: keyPattern, database: redisDb.Database, pageSize: 250, cursor: 0L)) + { + num++; + } + } + } + catch (Exception arg) + { + Log.Error($"Eccezione in RedisCountKey{Environment.NewLine}{arg}"); + } + + return num; + } + + /// + /// Esegue eliminazione memoria redis keyVal + /// + /// + /// + public bool RedisDelKey(string keyVal) + { + bool answ = redisDb.KeyDelete((RedisKey)keyVal); +#if false + var listEndpoints = redisConnAdmin.GetEndPoints(); + foreach (var endPoint in listEndpoints) + { + var server = redisConnAdmin.GetServer(endPoint); + if (server != null) + { + redisDb.KeyDelete((RedisKey)keyVal); + answ = true; + } + } +#endif + return answ; + } + + /// + /// Esegue eliminazione memoria redis keyVal + /// + /// + /// + public async Task RedisDelKeyAsync(RedisKey keyVal) + { + return await redisDb.KeyDeleteAsync(keyVal); + } + + /// + /// Esegue flush memoria redis dato keyVal + /// + /// + /// + public bool RedisFlushPattern(string pattern) + { + bool answ = false; + var listEndpoints = redisConnAdmin.GetEndPoints(); + foreach (var endPoint in listEndpoints) + { + var server = redisConnAdmin.GetServer(endPoint); + if (server != null) + { + var keyList = server.Keys(redisDb.Database, pattern); + foreach (var item in keyList) + { + redisDb.KeyDelete(item); + } + answ = true; + } + } + return answ; + } + + /// + /// Esegue flush memoria redis dato keyVal, async + /// + /// + /// + public async Task RedisFlushPatternAsync(RedisValue pattern) + { + Log.Debug($"Richiesta flush pattern: {pattern}"); + + // 1. Target ONLY master (le replica sono in read-only) + var master = redisConnAdmin.GetEndPoints() + .Where(ep => redisConnAdmin.GetServer(ep).IsConnected && !redisConnAdmin.GetServer(ep).IsReplica) + .FirstOrDefault(); + + if (master == null) + { + Log.Warn($"Nessun master Redis raggiungibile per il pattern {pattern}"); + return false; + } + + // 2. Flush intero DB se richiesto + if (pattern.ToString() == "*") + { + Log.Debug($"Full DB reset da pattern {pattern}"); + if (master != null) + { + redisConnAdmin.GetServer(master).FlushDatabase(redisDb.Database); + Log.Info($"Flush database {redisDb.Database} completato"); + } + return true; + } + // altrimenti faccio ciclo! + var server = redisConnAdmin.GetServer(master); + var db = redisConnAdmin.GetDatabase(redisDb.Database); + const int batchSize = 500; + var batch = new List(batchSize); + int deletedCount = 0; + + try + { + // KeysAsync usa SCAN automaticamente quando i risultati sono grandi + await foreach (var key in server.KeysAsync( + database: redisDb.Database, + pattern: pattern.ToString(), + pageSize: batchSize)) + { + batch.Add(key); + if (batch.Count >= batchSize) + { + // Esecuzione batch in parallelo controllato + await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); + batch.Clear(); + deletedCount += batchSize; + } + } + + // Restanti + if (batch.Count > 0) + { + await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); + deletedCount += batch.Count; + } + + Log.Info("Flush pattern {Pattern}: eliminate {Count} chiavi", pattern, deletedCount); + return true; + } + catch (RedisConnectionException ex) + { + Log.Error(ex, "Connessione Redis persa durante il flush di {pattern}", pattern); + return false; + } + catch (Exception ex) + { + Log.Error(ex, "Errore imprevisto nel flush pattern {pattern}", pattern); + throw; + } + } + + public KeyValuePair[] RedisGetHash(RedisKey redKey) + { + HashEntry[] rawData = redisDb.HashGetAll(redKey); + var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair($"{x.Name}", $"{x.Value}")).ToArray(); + return result; + } + + public async Task[]> RedisGetHashAsync(RedisKey redKey) + { + HashEntry[] rawData = await redisDb.HashGetAllAsync(redKey); + var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair($"{x.Name}", $"{x.Value}")).ToArray(); + return result; + } + + public Dictionary RedisGetHashDict(RedisKey hashKey) + { + HashEntry[] rawData = redisDb.HashGetAll(hashKey); + var result = rawData.Where(x => !x.Name.IsNull).ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); + return result; + } + + public async Task> RedisGetHashDictAsync(RedisKey hashKey) + { + HashEntry[] rawData = await redisDb.HashGetAllAsync(hashKey); + var result = rawData + .Where(x => !x.Name.IsNull) + .ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); + return result; + } + + public async Task RedisGetHashFieldAsync(RedisKey key, string hashField) + { + return await redisDb.HashGetAsync(key, hashField); + } + + public bool RedisKeyPresent(RedisKey key) + { + return redisDb.KeyExists(key); + } + + public async Task RedisKeyPresentAsync(RedisKey key) + { + return await redisDb.KeyExistsAsync(key); + } + + public void RedisSetHash(RedisKey redKey, KeyValuePair[] valori, double expireSeconds = -1.0) + { + HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); + redisDb.HashSet(redKey, redHash); + if (expireSeconds > 0.0) + { + redisDb.KeyExpire(redKey, DateTime.Now.AddSeconds(expireSeconds)); + } + } + + public async Task RedisSetHashAsync(RedisKey redKey, KeyValuePair[] valori, double expireSeconds = -1.0) + { + HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); + await redisDb.HashSetAsync(redKey, redHash); + if (expireSeconds > 0.0) + { + await redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds)); + } + } + + public void RedisSetHashDict(RedisKey redKey, Dictionary valori, double expireSeconds = -1.0) + { + HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); + redisDb.HashSet(redKey, redHash); + if (expireSeconds > 0.0) + { + redisDb.KeyExpire(redKey, DateTime.Now.AddSeconds(expireSeconds)); + } + } + + public async Task RedisSetHashDictAsync(RedisKey redKey, Dictionary valori, double expireSeconds = -1.0) + { + HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); + await redisDb.HashSetAsync(redKey, redHash); + if (expireSeconds > 0.0) + { + await redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds)); + } + } + + /// + /// Inserisce record RRL + fa pulizia vecchi record + /// + /// + public async Task RemRebootLogAddAsync(RemoteRebootLogModel newRec) + { + // verifica preliminare ultima esecuzione (max 1 ogni 60 min...) + DateTime adesso = DateTime.Now; + bool doClean = false; + if (adesso.Subtract(lastCleanupRRL).TotalMinutes > 60) + { + lastCleanupRRL = adesso; + doClean = true; + } + + string confVal = await tryGetConfigAsync("IO_NumReboot2Keep"); + int num2keep = int.TryParse(confVal, out int n) ? n : 5; + // insert del record + pulizia + bool fatto = await IocDbController.RemRebootLogAddAndCleanAsync(newRec, doClean, num2keep); + if (fatto) + { + // svuota cache + var currKey = $"{Utils.redisRemRebLog}:*"; + await RedisFlushPatternAsync(currKey); + } + return fatto; + } + + /// + /// Recupera tutti i record di RemoteRebootLog + /// + /// + public async Task> RemRebootLogGetAllAsync() + { + // setup parametri costanti + string source = "DB"; + Stopwatch sw = new Stopwatch(); + sw.Start(); + List result = new List(); + // cerco in _redisConn... + var currKey = $"{Utils.redisRemRebLog}:ALL"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + //if (!string.IsNullOrEmpty($"{rawData}")) + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + source = "REDIS"; + } + else + { + result = await IocDbController.RemRebootLogGetAllAsync(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); + } + if (result == null) + { + result = new List(); + } + sw.Stop(); + Log.Debug($"RemRebootLogGetAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Recupera ultimo record x ogni IdxMacchina x avere ultimo attivo + /// + /// + public async Task> RemRebootLogGetLast() + { + // setup parametri costanti + string source = "DB"; + Stopwatch sw = new Stopwatch(); + sw.Start(); + List result = new List(); + // cerco in _redisConn... + var currKey = $"{Utils.redisRemRebLog}:LAST"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + source = "REDIS"; + } + else + { + result = await IocDbController.RemRebootLogGetLastAsync(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); + } + if (result == null) + { + result = new List(); + } + sw.Stop(); + Log.Debug($"RemRebootLogGetLast | {source} | {sw.Elapsed.TotalMilliseconds}ms"); + return result; + } + + /// + /// Elimina da elenco KVP il TASK per l'impianto indicato + /// + /// + /// + /// + public async Task> RemTask2ExeMacchinaAsync(string idxMacchina, taskType tName) + { + // hard coded dimensione vettore DatiMacchine + Dictionary answ = new Dictionary(); + // ORA recupero da memoria redis... + try + { + var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); + answ = await RedisGetHashDictAsync(currHash); + + answ.Remove($"{tName}"); + // riscrivo! + await RedisDelKeyAsync(currHash); + await RedisSetHashDictAsync(currHash, answ); + Log.Info($"Task REM - idxMacchina: {idxMacchina} | taskKey: {tName.ToString()}"); + } + catch (Exception exc) + { + Log.Info(string.Format("Errore in RemTask2ExeMacchinaAsync | idxMacchina {2}:{0}{1}", Environment.NewLine, exc, idxMacchina)); + } + return answ; + } + + /// + /// Resetta (rileggendo) i dati della State Machine multi ingressi nel formato + /// currKey: IdxMacchina + /// value: IdxFamigliaIngresso + /// + /// + /// + public async Task[]> resetMSMIAsync(string idxMacchina) + { + var currHash = Utils.RedKeyMsmi(idxMacchina); + // recupero records + var tabMSMI = await IocDbController.VMSFDGetMultiByMaccAsync(idxMacchina); + + KeyValuePair[] answ = new KeyValuePair[tabMSMI.Count]; + // salvo tutti i valori StateMachineIngressi... + int i = 0; + foreach (var item in tabMSMI) + { + answ[i] = new KeyValuePair(item.IdxMacchina, item.IdxFamigliaIngresso.ToString()); + i++; + } + // verifico il timeout (default 60 sec...) + var sTOutSmi = await tryGetConfigAsync("TmOut.MSMI"); + int tOut = 60; + int.TryParse(sTOutSmi, out tOut); + tOut = tOut <= 60 ? 60 : tOut; + // salvo in redis! + await RedisSetHashAsync(currHash, answ, tOut); + return answ; + } + + /// + /// Processa registrazione EVENTO CONTEGGIO PEZZI x una data macchina IOB + /// + /// Macchina + /// Pezzi da registrare + /// + public async Task saveCaricoPezzi(string idxMacchina, string qty) + { + // default: 0, non registrato x cautela... + string answ = "0"; + // controllo per proseguire + if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(qty)) + { + string errore = $"Errore: mancano parametri macchina/incremento: idxMacchina {idxMacchina} | qty {qty}"; + Log.Error(errore); + answ = errore; + } + else + { + int numPzIncr = -1; + int.TryParse(qty, out numPzIncr); + // se il conteggio è >= 0 SALVO evento... + if (numPzIncr >= 0) + { + // recupero info tra cui ODL corrente + Dictionary datiMacc = await mDatiMacchineAsync(idxMacchina); + // registro evento 120 --> contapezzi in blocco !!!HARD CODED!!! !!!FIXME!!! + int idxEvento = 120; + DateTime adesso = DateTime.Now; + string codArticolo = "ND"; + if (datiMacc.ContainsKey("CodArticolo")) + { + codArticolo = datiMacc["CodArticolo"]; + } + + // creo evento + EventListModel newRecEv = new EventListModel() + { + CodArticolo = codArticolo, + IdxMacchina = idxMacchina, + IdxTipo = idxEvento, + InizioStato = adesso, + MatrOpr = 0, + pallet = "-", + Value = qty + }; + // salva e processa + var resp = await scriviRigaEventoAsync(newRecEv); + // registro in risposta che è andato tutto bene... ovvero la qty richiesta... + answ = qty; + } + } + return answ; + } + + /// + /// Processa registrazione EVENTO CONTEGGIO PEZZI x una data macchina IOB + /// + /// Macchina + /// Pezzi da registrare + /// DataOra evento + /// DataOra corrente + /// + public async Task SaveCaricoPezziAsync(string idxMacchina, string qty, string dtEve = "", string dtCurr = "") + { + // default: 0, non registrato x cautela... + string answ = "0"; + // Verifica se evento realtime oppure ho data specificata x processing @dtEve + DateTime adesso = DateTime.Now; + DateTime dtEvent = adesso; + bool rtimeProc = string.IsNullOrEmpty(dtEve); + if (!rtimeProc) + { + dtEvent = GetSrvDtEvent(dtEve, dtCurr); + } + // controllo per proseguire + if (!string.IsNullOrEmpty(idxMacchina) && !string.IsNullOrEmpty(qty)) + { + int numPzIncr = -1; + int.TryParse(qty, out numPzIncr); + // se il conteggio è >= 0 SALVO evento... + if (numPzIncr >= 0) + { + var listOdl = await IocDbController.OdlListByMaccPeriodoAsync(idxMacchina, dtEvent, dtEvent.AddSeconds(1)); + if (listOdl != null && listOdl.Count > 0) + { + string codArticolo = listOdl.FirstOrDefault()?.CodArticolo ?? "ND"; + // registro evento 120 --> contapezzi in blocco !!!HARD CODED!!! !!!FIXME!!! + int idxEvento = 120; + // creo evento + EventListModel newRecEv = new EventListModel() + { + CodArticolo = codArticolo, + IdxMacchina = idxMacchina, + IdxTipo = idxEvento, + InizioStato = dtEvent, + MatrOpr = 0, + pallet = "-", + Value = qty + }; + // salva e processa + var resp = await scriviRigaEventoAsync(newRecEv); + // registro in risposta che è andato tutto bene... ovvero la qty richiesta... + answ = qty; + } + } + } + else + { + string errore = $"Errore: mancano parametri macchina/incremento: idxMacchina {idxMacchina} | qty {qty}"; + Log.Error(errore); + answ = errore; + } + return answ; + } + + /// + /// Processa registrazione di un counter x una data macchina IOB + /// + /// + /// contapezzi + /// + public async Task SaveCounterAsync(string idxMacchina, string counter) + { + string answ = "0"; + // inizio processing vero e proprio INPUT... + if (string.IsNullOrEmpty(idxMacchina)) + { + string errore = "Errore: parametro macchina vuoto"; + Log.Info(errore); + answ = errore; + } + else + { + if (string.IsNullOrEmpty(counter)) + { + string errore = "Errore: parametro counter vuoto"; + Log.Error(errore); + answ = errore; + } + else + { + int newCounter = -1; + int.TryParse(counter, out newCounter); + // se il conteggio è >= 0 SALVO come nuovo conteggio... + if (newCounter >= 0) + { + var currKey = Utils.RedKeyPzCount(idxMacchina, MpIoNS); + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + // salvo per + tempo... + await redisDb.StringSetAsync(currKey, answ.ToString()); + answ = counter; + } + else + { + int currCount = await PzCounterTcAsync(idxMacchina); + answ = currCount.ToString(); + // salvo per meno tempo... + await redisDb.StringSetAsync(currKey, answ); + } + } + } + } + return answ; + } + + public async Task SaveDataItemsAsync(string id, List dataList) + { + bool answ = false; + if (mongoController != null) + { + answ = mongoController.SaveMachineDataItems(id, dataList); + } + else + { + // modalità con SqlSb (no mongo) + if (useFactory) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var mtcService = scope.ServiceProvider.GetRequiredService(); + answ = await mtcService.ReplaceMachineDataAsync(id, dataList); + } + else + { + answ = await MtcService.ReplaceMachineDataAsync(id, dataList); + } + } + + return answ; + } + + public async Task SaveMachine2Iob(string idxMacchina, string serData) + { + bool fatto = false; + var currKey = Utils.RedKeyMach2Iob(idxMacchina); + // se != null --> salvo! + if (!string.IsNullOrEmpty(serData)) + { + fatto = await redisDb.StringSetAsync(currKey, serData); + } + return fatto; + } + + public async Task SaveMachineIobConf(string idxMacchina, Dictionary currDict) + { + bool fatto = false; + var currKey = Utils.RedKeyMachIobConf(idxMacchina); + // se != null --> salvo! + if (currDict != null) + { + await RedisSetHashDictAsync(currKey, currDict); + fatto = true; + } + return fatto; + } + + /// + /// salva il segnale di "microstato" (segnale) ASYNC + /// + /// idx macchina + /// valOut ingresso + /// data-ora evento (server) + /// contatore sequenza dati inviati + /// + public async Task saveSigLogAsync(string idxMacchina, string valore, DateTime dtEve, int contatore) + { + SignalLogModel newRec = new SignalLogModel() + { + IdxMacchina = idxMacchina, + DtCurr = DateTime.Now, + DtEve = dtEve, + Contatore = contatore, + Valore = valore + }; + return await IocDbController.SignalLogInsertAsync(newRec); + } + + /// + /// scrive un evento di keepalive sulla tabella + /// + /// + /// + /// + public async Task ScriviKeepAliveAsync(string IdxMacchina, DateTime oraMacchina) + { + // cerco se ho keep alive in redis, + var currKey = Utils.RedKeyHash($"KeepAlive:{IdxMacchina}"); + bool keyPresent = await RedisKeyPresentAsync(currKey); + // se NON presente salvo in REDIS con TTL 10 sec e sul DB... + if (!keyPresent) + { + DateTime adesso = DateTime.Now; + await redisDb.StringSetAsync(currKey, adesso.ToString("s"), TimeSpan.FromSeconds(30)); + // effettuo scrittura sul DB + await IocDbController.KeepAliveUpsertAsync(IdxMacchina, DateTime.Now, oraMacchina); + } + } + + /// + /// Salvataggio YAML completo di configurazione dell'IOB + /// + /// + /// + /// + public async Task SetIobConfYamlAsync(string idxMacchina, string iobConfFull) + { + bool answ = false; + // se ho un area memoria valida... + if (!string.IsNullOrEmpty(iobConfFull)) + { + // salvo! + var currKey = Utils.RedKeyIobConfYaml(idxMacchina, MpIoNS); + answ = await redisDb.StringSetAsync(currKey, iobConfFull); + } + return answ; + } + + public async Task SetIobMemMap(string idxMacchina, PlcMemMapDto currMap) + { + bool answ = false; + // se ho un area memoria valida... + if (currMap != null) + { + // salvo! + var currKey = Utils.RedKeyIobMemMap(idxMacchina, MpIoNS); + string serVal = JsonConvert.SerializeObject(currMap); + answ = await redisDb.StringSetAsync(currKey, serVal); + } + return answ; + } + + /// + /// Restitusice elenco KVP dei campi della State Machine ingressi nel formato + /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) + /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE + /// + /// + /// + public async Task[]> StateMachInByKeyAsync(int idxFamIn) + { + // hard coded dimensione vettore DatiMacchine + KeyValuePair[] answ = new KeyValuePair[1]; + // iniziualizzo con un valOut... 0/0 + answ[0] = new KeyValuePair("0", "0"); + // ORA recupero da memoria redis... + try + { + var currHash = Utils.GetHashSMI(idxFamIn); + answ = await RedisGetHashAsync(currHash); + // se è vuoto... leggo da DB e popolo! + if (answ.Length == 0) + { + answ = await resetSMIAsync(idxFamIn); + } + } + catch (Exception exc) + { + Log.Error($"Errore in compilazione State Machine Ingressi x Redis:{Environment.NewLine}{exc}"); + } + return answ; + } + + /// + /// Statistiche ODL calcolate (da stored stp_STAT_ODL) + /// + /// + public Task> StatOdl(int IdxOdl) + { + return SpecDbController.OdlStart(IdxOdl); + } + + /// + /// restituisce il valOut da REDIS associato al tag richeisto + /// + /// Chiave in cui cercare il valOut + /// + public string TagConfGetKey(string redKey) + { + string outVal = ""; + // cerco in REDIS la conf x l'IOB + var rawData = redisDb.StringGet(redKey); + if (!string.IsNullOrEmpty(rawData)) + { + outVal = $"{rawData}"; + } + return outVal; + } + + /// + /// Elenco setup dei tag conf correnti + /// + /// + public Task>> TagsGetAll() + { + return Task.FromResult(currTagConf); + } + + /// + /// Esegue traduzione dato vocabolario da Lingua + Lemma + /// + /// + /// + /// + public string Traduci(string lemma, string lingua) + { + string answ = $"[{lemma}]"; + // verifico se ho qualcosa nell'obj vocabolario... + if (ObjVocabolario == null || ObjVocabolario.Count == 0) + { + // inizializzo il vocabolario... + ObjVocabolario = VocabolarioGetAll(); + } + var record = ObjVocabolario.Where(x => x.Lingua == lingua && x.Lemma == lemma).FirstOrDefault(); + if (record != null) + { + answ = record.Traduzione; + } + return answ; + } + + public async Task updateDossierValue(DossierModel currDoss, FluxLogDTO editFL) + { + bool answ = false; + // recupero intero set valori dossier deserializzando... + var fluxLogList = FluxLogDtoGetByFlux(currDoss.Valore); + await Task.Delay(1); + + // se tutto ok + if (fluxLogList != null) + { + // da provare...!!!! + + // elimino vecchio record + var currRec = fluxLogList.FirstOrDefault(x => x.CodFlux == editFL.CodFlux && x.dtEvento == editFL.dtEvento); + if (currRec != null) + { + fluxLogList.Remove(currRec); + // aggiungo nuovo + fluxLogList.Add(editFL); + } + + // serializzo nuovamente valOut + DossierFluxLogDTO? result = new DossierFluxLogDTO(); + var ODLflux = result.ODL.ToList(); + foreach (var item in fluxLogList) + { + ODLflux.Add(item); + } + + DossierFluxLogDTO updatedResult = new DossierFluxLogDTO() { ODL = ODLflux }; + + string rawVal = JsonConvert.SerializeObject(updatedResult); + currDoss.Valore = rawVal; + // aggiorno record sul DB + await SpecDbController.DossiersUpdateValore(currDoss); + } + + return answ; + } + + /// + /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) + /// + /// + /// + /// + public async Task UpsertCurrObjItemsAsync(string idxMacchina, List innovations) + { + bool answ = false; + if (innovations != null) + { + Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); + // leggo i valori attuali... + List actValues = MachineParamList(idxMacchina); + // per ogni valOut passatomi faccio insert o update rispetto elenco valori correnti in REDIS + foreach (var item in actValues) + { + // cerco nelle innovazioni SE CI SIA il valOut... + var trovato = innovations.Find(obj => obj.uid == item.uid); + // se non trovato nelle innovazioni... + if (trovato == null) + { + // lo ri-aggiungo x non perderlo + innovations.Add(item); + Log.Trace($"innovations | add | item.uid: {item.uid} | item.value: {item.value}"); + } + else + // altrimenti aggiorno campo (non trasmesso) name e tengo il resto... + { + trovato.name = item.name; + Log.Info($"innovations | update | item.uid: {item.uid} | item.value: {item.value} --> {trovato.value} "); + } + } + // serializzo e salvo + string serVal = JsonConvert.SerializeObject(innovations); + + var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); + RedisValue rawData = redisDb.StringSet(currKey, serVal); + } + return answ; + } + + /// + /// Restituisce il valore SPECIFICATO per la state machine ingressi + /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) + /// + /// + /// + /// + /// + public async Task ValoreSmiAsync(int idxFamIn, int idxMicroStato, int valoreIn) + { + string valOut = ""; + var currHash = Utils.GetHashSMI(idxFamIn); + string field = $"{idxMicroStato}_{valoreIn}"; + var searchVal = await RedisGetHashFieldAsync(currHash, field); + if (!searchVal.HasValue) + { + // ricarico tabella (salvando in redis x ricerca successiva)! + var valori = await StateMachInByKeyAsync(idxFamIn); + if (valori.Any(x => x.Key == field)) + { + searchVal = valori.FirstOrDefault(x => x.Key == field).Value; + } + } + valOut = $"{searchVal}"; + return valOut; + } + + /// + /// Elenco completo tabella Vocabolario + /// + /// + public List VocabolarioGetAll() + { + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + List? result = new List(); + string source = "REDIS"; + // cerco in redis... + RedisValue rawData = redisDb.StringGet(Utils.redisVocabolario); + if (!string.IsNullOrEmpty($"{rawData}")) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + } + else + { + result = SpecDbController.VocabolarioGetAll(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(Utils.redisVocabolario, rawData, getRandTOut(redisLongTimeCache / 5)); + source = "DB"; + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"VocabolarioGetAll Read from {source}: {ts.TotalMilliseconds}ms"); + if (result == null) + { + result = new List(); + } + return result; + } + + #endregion Public Methods + + #region Internal Methods + + /// + /// Recupera ArtNum dato CodArt (per impianti che accettano solo INT in scrittura) + /// + /// + /// CodArt richiesto, se vuoto restituisce TUTTI i valori in tabella di decodifica + /// + /// Dizionario contenente le righe delle codifiche attive Articolo/Numero + internal async Task> GetArtNumAsync(string codArt) + { + Dictionary answ = new Dictionary(); + var currData = await DecNumArtGetFiltAsync(codArt); + foreach (var item in currData) + { + answ.Add(item.CodArticolo, item.NumART); + } + return answ; + } + + #endregion Internal Methods + + #region Protected Fields + + protected Random rand = new Random(); + + #endregion Protected Fields + + #region Protected Properties + + protected string canCacheParametri { get; set; } = ""; + + #endregion Protected Properties + + #region Protected Methods + + /// + /// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec + /// + /// + /// + protected TimeSpan getRandTOut(double stdMinutes) + { + double rndValue = stdMinutes + (double)rand.Next(1, 60) / 60; + return TimeSpan.FromMinutes(rndValue); + } + + #endregion Protected Methods + + #region Private Fields + + private static IConfiguration _configuration = null!; + private static ILogger _logger = null!; + private static Logger Log = LogManager.GetCurrentClassLogger(); + private static IMtcSetupService MtcService = null!; +#if false + /// + /// Elenco completo valori Macchine 2 Slave + /// + /// + public List Macchine2SlaveGetAll() + { + List? result = new List(); + Stopwatch stopWatch = new Stopwatch(); + stopWatch.Start(); + string readType = "DB"; + string currKey = $"{Utils.redisBaseAddr}:M2STab"; + // cerco in redis dato valore sel macchina... + RedisValue rawData = redisDb.StringGet(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}"); + readType = "REDIS"; + } + else + { + result = IocDbController.Macchine2Slave(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (result == null) + { + result = new List(); + } + stopWatch.Stop(); + TimeSpan ts = stopWatch.Elapsed; + Log.Debug($"Macchine2SlaveGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); + return result; + } +#endif + private readonly IServiceScopeFactory _scopeFactory; + + /// + /// Provider CultureInfo x parse valori (es dataora) + /// + private CultureInfo ciProvider = CultureInfo.InvariantCulture; + + /// + /// Formato dataora standard x parsing + /// + private string dtFormat = "yyyyMMddHHmmssfff"; + + /// + /// Ultima esecuzione pulizia RRL x evitgare congestioni + /// + private DateTime lastCleanupRRL = DateTime.Now.AddHours(-1); + + /// + /// MS max age x dato MSE + /// + private int maxAge = 2000; + + private string MpIoNS = ""; + + /// + /// Oggetto vocabolario x uso continuo traduzione + /// + private List ObjVocabolario = new List(); + + /// + /// Oggetto per connessione a REDIS + /// + private ConnectionMultiplexer redisConn = null!; + + /// + /// Oggetto per connessione a REDIS modalità admin (ex flux dati) + /// + private ConnectionMultiplexer redisConnAdmin = null!; + + /// + /// Oggetto DB redis da impiegare x chiamate R/W + /// + private IDatabase redisDb = null!; + + private int redisLongTimeCache = 5; + + private int redisShortTimeCache = 2; + + /// + /// Generatore random classe + /// + private Random rnd = new Random(); + + private bool useFactory = false; + + #endregion Private Fields + + #region Private Methods + + /// + /// Verifica se sia necessario inserire un cambio di stato impianto (DiarioDi Bordo) in modalità batch + /// + /// + /// + /// + /// + /// + /// + /// + /// + private async Task CheckCambiaStatoBatchAsync(tipoInputEvento tipoInput, string IdxMacchina, DateTime InizioStato, int IdxTipo, string CodArt, string Value, int MatrOpr, string pallet) + { + await IocDbController.CheckCambiaStatoBatchAsync(tipoInput, IdxMacchina, InizioStato, IdxTipo, CodArt, Value, MatrOpr, pallet); +#if false + List listTransit = new List(); + TransizioneStatiModel? rigaTrans = null; + switch (tipoInput) + { + case tipoInputEvento.barcode: + // effettuo cambio stato INDIPENDENTEMENTE da stato precedente + listTransit = await IocDbController.SMES_getUserForcedAsync(IdxMacchina, IdxTipo); + + if (listTransit.Count > 0) + { + rigaTrans = listTransit.FirstOrDefault(); + // solo se cambia stato... + if (rigaTrans != null && rigaTrans.IdxStato != rigaTrans.next_IdxStato) + { + await IocDbController.DDB_InsStatoBatchAsync(IdxMacchina, InizioStato, rigaTrans.next_IdxStato, CodArt, Value, MatrOpr, pallet); + // aggiorno MSE + await IocDbController.RecalcMseAsync(IdxMacchina, 0); + } + } + else + { + Log.Debug($"Non trovata riga per: BARCODE | IdxMacchina: {IdxMacchina} | IdxTipo: {IdxTipo} | CodArt: {CodArt} | Value: {Value} | MatrOpr: {MatrOpr} | pallet: {pallet}"); + } + break; + + case tipoInputEvento.hw: + // verifico se ci sia necessità di cambio stato + listTransit = await IocDbController.SMES_getHwTransitionsAsync(IdxMacchina, IdxTipo); + if (listTransit.Count > 0) + { + rigaTrans = listTransit.FirstOrDefault(); + if (rigaTrans != null && rigaTrans.IdxStato != rigaTrans.next_IdxStato) + { + await IocDbController.DDB_InsStatoBatchAsync(IdxMacchina, InizioStato, rigaTrans.next_IdxStato, CodArt, Value, MatrOpr, pallet); + // aggiorno MSE + await IocDbController.RecalcMseAsync(IdxMacchina, 0); + } + } + else + { + Log.Debug($"Non trovata riga per: HW | IdxMacchina: {IdxMacchina} | IdxTipo: {IdxTipo} | CodArt: {CodArt} | Value: {Value} | MatrOpr: {MatrOpr} | pallet: {pallet}"); + } + break; + + default: + break; + } +#endif + } + + /// + /// Restituisce l'elenco codici flusso (da confFlux) x una macchina (se presenti) Impiegata + /// anche cache redis + /// + /// + /// + private async Task> ConfFluxMach(string idxMacchina) + { + List resultList = new List(); + string tag = string.IsNullOrEmpty(idxMacchina) ? "ALL" : idxMacchina; + var currKey = $"{Utils.redisConfFlux}:{tag}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + var dbData = await IocDbController.ConfFluxFiltAsync(idxMacchina); + resultList = dbData + .Select(x => x.CodFlux) + .ToList(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(resultList); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (resultList == null) + { + resultList = new(); + } + return resultList; + } + + /// + /// Svuota la cache redis x l'elenco delle righe di confFlux x una macchina (se presenti) + /// + /// + /// + private async Task DossierLastByMachResetAsync(string idxMacchina) + { + bool answ = false; + var currKey = $"{Utils.redisDossByMacLast}:{idxMacchina}"; + await redisDb.KeyDeleteAsync(currKey); + return answ; + } + + /// + /// Helper standardizzazione valOut dataora ricevuto da IOB remoti + /// + /// + /// + private string dtFormStd(string? s) + { + return s?.Length > 17 ? s[..17] : s?.PadRight(17, '0') ?? string.Empty; + } + + /// + /// Restituisce l'elenco delle data-ora di confFlux x una macchina (se presenti) Impiegata + /// anche cache redis + /// + /// + /// num record da recuperare + /// + private async Task> FluxLogFirstByMachAsync(string idxMacchina, int numMax = 10) + { + List resultList = new List(); + + var currKey = $"{Utils.redisFluxByMacFirst}:{idxMacchina}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + var dbData = await IocDbController.FluxLogFirstByMaccAsync(idxMacchina, numMax); + resultList = dbData + .Select(x => x.dtEvento) + .ToList(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(resultList); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); + } + if (resultList == null) + { + resultList = new List(); + } + return resultList; + } + + /// + /// Effettua vera chiamata x salvataggio snapshot dati FluxLog + /// + /// + /// + /// + /// + private async Task FluxLogSaveSnapshotAsync(string id, DateTime dtStart, DateTime dtEnd, string caller) + { + string answ = ""; + DateTime dataOraEvento = DateTime.Now; + Log.Debug($"{caller} | Richiesta snapshot dati FluxLog macchina: id: {id} | periodo: {dtStart} - {dtEnd}"); + try + { + bool fatto = await IocDbController.FluxLogTakeSnapshotLastAsync(id, dtStart, dtEnd); + answ = fatto ? "OK" : "KO"; + } + catch (Exception exc) + { + Log.Error($"Errore in {caller}{Environment.NewLine}{exc}"); + answ = "NO"; + } + return answ; + } + + /// + /// Recupero info ODL corrente da dati prod macchina + /// + /// + /// + private async Task GetCurrOdlByProdAsync(string idxMacchina) + { + string answ = ""; + // recupero stato... + var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now); + if (datiProd != null) + { + answ = datiProd.IdxOdl.ToString(); + } + // ultimo controllo su idxOdl... + answ = answ == "" ? "0" : answ; + // restituisco! + return answ; + } + + /// + /// Restituisce il valOut booleano se la macchina sia di tipo MULTI (con più state machine x INGRESSI) + /// usando dati macchina in cache per evitare lookup ridondanti + /// + /// + /// + /// + private bool isMulti(string idxMacchina, Dictionary datiMacc) + { + bool answ = false; + try + { + answ = Convert.ToBoolean(datiMacc.ContainsKey("Multi") && datiMacc["Multi"] == "1"); + } + catch (Exception exc) + { + Log.Error($"Eccezione in isMulti{Environment.NewLine}{exc}"); + } + return answ; + } + + private async Task> ListMasterAsync() + { + HashSet result = new(); + string currKey = $"{Utils.redisBaseAddr}:ListMaster"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + var fullList = await Macchine2SlaveGetAllAsync(); + result = fullList.Select(x => x.IdxMacchina).ToHashSet(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); + } + return result; + } + + private async Task> ListSlaveAsync() + { + HashSet result = new(); + string currKey = $"{Utils.redisBaseAddr}:ListSlave"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue) + { + result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); + } + else + { + var fullList = await Macchine2SlaveGetAllAsync(); + result = fullList.Select(x => x.IdxMacchinaSlave).Distinct().ToHashSet(); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); + } + return result; + } + + private async Task POdlFlushCache() + { + bool answ = false; + RedisValue pattern = new RedisValue($"{Utils.redisXdlData}:*"); + answ = await RedisFlushPatternAsync(pattern); + pattern = new RedisValue($"{Utils.redisPOdlByOdl}:*"); + answ = await RedisFlushPatternAsync(pattern); + pattern = new RedisValue($"{Utils.redisPOdlByPOdl}:*"); + answ = await RedisFlushPatternAsync(pattern); + pattern = new RedisValue($"{Utils.redisPOdlList}:*"); + answ = await RedisFlushPatternAsync(pattern); + + return answ; + } + + /// + /// Calcola l'effettivo valOut da passare alla macchina a stati INGRESSI usando dati macchina in cache + /// + /// + /// + /// + /// + private string preProcInput(string idxMacchina, string valore, Dictionary datiMacc) + { + string newVal = ""; + try + { + // variabili + int valINT = 0; + int BitFilt = 0; + int BSR = 0; + bool ExplodeBit = false; + int NumBit = 0; + int newValInt = 0; + // recupero parametri dalla cache... + int.TryParse(datiMacc.ContainsKey("BitFilt") ? datiMacc["BitFilt"] : "0", out BitFilt); + int.TryParse(datiMacc.ContainsKey("BSR") ? datiMacc["BSR"] : "0", out BSR); + Boolean.TryParse(datiMacc.ContainsKey("ExplodeBit") ? datiMacc["ExplodeBit"] : "false", out ExplodeBit); + // non usato (x ora) + int.TryParse(datiMacc.ContainsKey("NumBit") ? datiMacc["NumBit"] : "0", out NumBit); + + // recupero valOut + valINT = int.Parse(valore, NumberStyles.HexNumber); + // filtro + newValInt = MP.Core.Utils.bMaskInt(valINT, BitFilt); + // effettuo eventuale BitShiftRight + if (BSR > 0) + { + newValInt = newValInt >> BSR; + } + // effettuo eventuale esplosione in BIT esclusivi + if (ExplodeBit) + { + newValInt = Convert.ToInt32(1 << newValInt); + } + // riconverto a STRING HEX!!! + newVal = newValInt.ToString("X"); + } + catch + { + newVal = valore; + } + + return newVal; + } + + private async Task resetCacheArticoli() + { + RedisValue pattern = new RedisValue($"{Utils.redisArtByDossier}:*"); + await RedisFlushPatternAsync(pattern); + pattern = new RedisValue($"{Utils.redisArtList}:*"); + await RedisFlushPatternAsync(pattern); + } + + /// + /// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato + /// + /// + /// + private async Task> ResetDatiMacchinaAsync(string idxMacc) + { + Dictionary result = new Dictionary(); + VMSFDModel? dbResult = null; + if (useFactory) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var mtcService = scope.ServiceProvider.GetRequiredService(); + dbResult = await mtcService.VMSFDGetByMaccAsync(idxMacc); + } + else + { + dbResult = await IocDbController.VMSFDGetByMaccAsync(idxMacc); + } + + if (dbResult == null) return new Dictionary(); + + double numSecCache = redisLongTimeCache; + // converto in formato dizionario... + if (dbResult != null) + { + // salvo 1:1 i valori... STATO + result.Add("IdxMicroStato", $"{dbResult.IdxMicroStato}"); + result.Add("IdxStato", $"{dbResult.IdxStato}"); + result.Add("CodArticolo", $"{dbResult.CodArticolo}"); + result.Add("insEnabled", $"{dbResult.InsEnabled}"); + result.Add("sLogEnabled", $"{dbResult.SLogEnabled}"); + result.Add("pallet", $"{dbResult.Pallet}"); + result.Add("CodArticolo_A", $"{dbResult.CodArticoloA}"); + result.Add("CodArticolo_B", $"{dbResult.CodArticoloB}"); + result.Add("TempoCicloBase", $"{dbResult.TempoCicloBase}"); + result.Add("PzPalletProd", $"{dbResult.PzPalletProd}"); + result.Add("MatrOpr", $"{dbResult.MatrOpr}"); + result.Add("lastVal", $"{dbResult.LastVal}"); + result.Add("TCBase", $"{dbResult.TempoCicloBase}"); + + //...e SETUP + result.Add("CodMacc", $"{dbResult.Codmacchina}"); + result.Add("IdxFamIn", $"{dbResult.IdxFamigliaIngresso}"); + result.Add("Multi", $"{dbResult.Multi}"); + result.Add("BitFilt", $"{dbResult.BitFilt}"); + result.Add("MaxVal", $"{dbResult.MaxVal}"); + result.Add("BSR", $"{dbResult.Bsr}"); + result.Add("ExplodeBit", $"{dbResult.ExplodeBit}"); + result.Add("NumBit", $"{dbResult.NumBit}"); + result.Add("IdxFamMacc", $"{dbResult.IdxFamiglia}"); + result.Add("simplePallet", $"{dbResult.SimplePallet}"); + result.Add("palletChange", $"{dbResult.PalletChange}"); + // durata cache in secondi dal valOut insEnabled... + //double numSecCache = ((result["insEnabled"].ToLower() == "true") ? redisShortTimeCache : redisLongTimeCache); + numSecCache = dbResult.InsEnabled ? redisShortTimeCache : redisLongTimeCache; + } + else + { + + + } + // dati master/slave + string isMaster = (await ListMasterAsync()).Contains(idxMacc) ? "1" : "0"; + string isSlave = (await ListSlaveAsync()).Contains(idxMacc) ? "1" : "0"; + result.Add("Master", isMaster); + result.Add("Slave", isSlave); + + // Processing redis in transazoine... + var redKey = Utils.RedKeyDatiMacc(idxMacc, MpIoNS); + + // variazione con redis transaction... + var transaction = redisDb.CreateTransaction(); + // 1. Eliminiamo la chiave (rimuove i vecchi campi) + _ = transaction.KeyDeleteAsync(redKey); + // 2. Prepariamo gli HashEntry per il batch (più efficiente di un Dictionary) + HashEntry[] entries = result.Select(kvp => new HashEntry(kvp.Key, kvp.Value)).ToArray(); + // 3. Inseriamo i nuovi valori + _ = transaction.HashSetAsync(redKey, entries); + // 4. Impostiamo l'expiration in un unico colpo + _ = transaction.KeyExpireAsync(redKey, TimeSpan.FromSeconds(numSecCache)); + // Eseguiamo tutto in un unico viaggio verso Redis + bool success = await transaction.ExecuteAsync(); + + return result; + } + + /// + /// Resetta (rileggendo) i dati della State Machine ingressi nel formato + /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) + /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) + /// + /// + /// + private async Task[]> resetSMIAsync(int idxFamIn) + { + var currHash = Utils.GetHashSMI(idxFamIn); + // leggo da DB... + var tabSMI = await IocDbController.StateMachineIngressiAsync(idxFamIn); + + KeyValuePair[] answ = new KeyValuePair[tabSMI.Count]; + // salvo tutti i valori StateMachineIngressi... + int i = 0; + string key = ""; + string val = ""; + foreach (var item in tabSMI) + { + key = string.Format("{0}_{1}", item.IdxMicroStato, item.ValoreIngresso); + val = string.Format("{0}_{1}", item.IdxTipoEvento, item.NextIdxMicroStato); + answ[i] = new KeyValuePair(key, val); + i++; + } + // verifico il timeout (default 60 sec...) + int tOut = 300; + var sTOutSmi = await tryGetConfigAsync("TmOut.SMI"); + if (!string.IsNullOrEmpty(sTOutSmi)) + { + int.TryParse(sTOutSmi, out tOut); + } + // salvo in redis! + await RedisSetHashAsync(currHash, answ, tOut); + return answ; + } + +#if false + /// + /// Scrive una riga di evento nel db + check cambio stato DiarioDiBordo + /// + /// codice macchina + /// + private inputComandoMapo scriviRigaEvento(EventListModel newRec) + { + bool inserito = false; + try + { + // inserisco evento + inserito = IocDbController.EvListInsert(newRec); + // faccio controllo per eventuale cambio stato da tab transizioni... + CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); + } + catch (Exception exc) + { + Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); + } + // formatto output + inputComandoMapo answ = new inputComandoMapo(); + answ.outValue = inserito.ToString(); + answ.needStatusRefresh = true; + return answ; + } +#endif + + /// + /// Scrive una riga di evento nel db + check cambio stato DiarioDiBordo + /// + /// codice macchina + /// + private async Task scriviRigaEventoAsync(EventListModel newRec) + { + bool inserito = false; + try + { + // inserisco evento + inserito = await IocDbController.EvListInsertAsync(newRec); + // faccio controllo per eventuale cambio stato da tab transizioni... + await CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); + } + catch (Exception exc) + { + Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); + } + // formatto output + inputComandoMapo answ = new inputComandoMapo(); + answ.outValue = inserito.ToString(); + answ.needStatusRefresh = true; + return answ; + } + + /// + /// Scrive una riga evento + una riga microstato insieme, ed effettua verifica necessità cambio stato + /// + /// Record MicroStatoMacchina + /// record EventList + /// + private async Task scriviRigaEventoAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv) + { + bool inserito = false; + if (useFactory) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var iocService = scope.ServiceProvider.GetRequiredService(); + + // inserisco evento + inserito = await iocService.EvListMicroStatoInsertAsync(newRecMsm, newRecEv); + // faccio controllo per eventuale cambio stato da tab transizioni... + + await iocService.CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet); + } + else + { + + try + { + // inserisco evento + inserito = await IocDbController.EvListMicroStatoInsertAsync(newRecMsm, newRecEv); + // faccio controllo per eventuale cambio stato da tab transizioni... + await CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet); + } + catch (Exception exc) + { + Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRecEv.IdxMacchina} | IdxTipo {newRecEv.IdxTipo} | codArticolo {newRecEv.CodArticolo} | Value {newRecEv.Value} | MatrOpr {newRecEv.MatrOpr} | Pallet {newRecEv.pallet} | dTime {newRecEv.InizioStato}{Environment.NewLine}{exc}"); + } + } + // formatto output + inputComandoMapo answ = new inputComandoMapo(); + answ.outValue = inserito.ToString(); + answ.needStatusRefresh = true; + return answ; + } + + + + /// + /// Scrive una riga di evento manuale (barcode) nel db + check cambio stato DiarioDiBordo + /// + /// codice macchina + /// + private async Task scriviRigaEventoBarcodeAsync(EventListModel newRec) + { + bool inserito = false; + try + { + // inserisco evento + inserito = await IocDbController.EvListInsertAsync(newRec); + // faccio controllo per eventuale cambio stato da tab transizioni... + await CheckCambiaStatoBatchAsync(tipoInputEvento.barcode, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); + } + catch (Exception exc) + { + Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); + } + // formatto output + inputComandoMapo answ = new inputComandoMapo(); + answ.outValue = inserito.ToString(); + answ.needStatusRefresh = true; + return answ; + } + + /// + /// Stato prod macchina (completo) + /// + /// + /// + /// + private async Task StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false) + { + // setup parametri costanti + string source = "DB"; + StatoProdModel? result = new StatoProdModel(); + // cerco in _redisConn... + string currKey = $"{Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}"; + RedisValue rawData = await redisDb.StringGetAsync(currKey); + if (rawData.HasValue && !forceDb) + { + result = JsonConvert.DeserializeObject($"{rawData}"); + source = "REDIS"; + } + else + { + result = await IocDbController.StatoProdMacchinaAsync(idxMacchina, dtReq); + // serializzo e salvo... + rawData = JsonConvert.SerializeObject(result); + await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(60)); + } + if (result == null) + { + result = new StatoProdModel(); + } + return result; + } + + /// + /// Restituisce valOut della stringa (SE disponibile) + /// + /// + /// + private async Task tryGetConfigAsync(string keyName) + { + string answ = ""; + // preselezione valori + var configData = await ConfigGetAllAsync(); + var currRec = configData.FirstOrDefault(x => x.Chiave == keyName); + if (currRec != null) + { + answ = currRec.Valore; + } + return answ; + } + +#if false + /// + /// Restituisce il valOut SPECIFICATO per la state machine ingressi + /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) + /// + /// + /// + /// + /// + private string valoreSMI(int idxFamIn, int idxMicroStato, int valoreIn) + { + var currHash = Utils.GetHashSMI(idxFamIn); + string field = string.Format("{0}_{1}", idxMicroStato, valoreIn); + return RedisGetHashFieldAsync(currHash, field); + } +#endif + + /// + /// cerca codice in anagrafica macchine ed eventualmente inserisce nuova macchina + /// + /// + private async Task verificaIdxMacchinaAsync(string IdxMacchina) + { + bool needDB = false; + try + { + // esecuzione in REDIS...cerco status macchina... + if ((await mDatiMacchineAsync(IdxMacchina)).Count == 0) + { + needDB = true; + } + } + catch { } + + if (needDB) + { + // verifico se esiste su DB + var dbRec = await IocDbController.MacchineGetByIdxAsync(IdxMacchina); + if (dbRec == null) + { + MacchineModel newRec = new MacchineModel() + { + IdxMacchina = IdxMacchina, + CodMacchina = "0000", + Nome = IdxMacchina, + Descrizione = "Macchina non codificata", + Note = "-", + locazione = "", + RecipeArchivePath = "", + RecipePath = "" + }; + await IocDbController.MacchineUpsertAsync(newRec); + + // verifico ci sia un microstato macchina... + var recMSM = await IocDbController.MicroStatoMacchinaGetByIdxMaccAsync(IdxMacchina); + if (recMSM.Count == 0) + { + // inserisco nuovo stato... + MicroStatoMacchinaModel msRec = new MicroStatoMacchinaModel() + { + IdxMacchina = IdxMacchina, + IdxMicroStato = 0, + InizioStato = DateTime.Now, + Value = "00" + }; + await IocDbController.MicroStatoMacchinaUpsertAsync(msRec); + } + } + } + } + + #endregion Private Methods + } +} diff --git a/MP-RIOC/MP-RIOC.http b/MP-RIOC/MP-RIOC.http new file mode 100644 index 00000000..af71d627 --- /dev/null +++ b/MP-RIOC/MP-RIOC.http @@ -0,0 +1,6 @@ +@MP_RIOC_HostAddress = http://localhost:5290 + +GET {{MP_RIOC_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/MP-RIOC/MP-RIOC.slnx b/MP-RIOC/MP-RIOC.slnx new file mode 100644 index 00000000..455ea819 --- /dev/null +++ b/MP-RIOC/MP-RIOC.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/MP-RIOC/MP.RIOC.csproj b/MP-RIOC/MP.RIOC.csproj new file mode 100644 index 00000000..0c63a52d --- /dev/null +++ b/MP-RIOC/MP.RIOC.csproj @@ -0,0 +1,41 @@ + + + + net8.0 + enable + enable + MP_RIOC + 8.16.2605.808 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MP-RIOC/Pages/Index.cshtml b/MP-RIOC/Pages/Index.cshtml new file mode 100644 index 00000000..48b156b7 --- /dev/null +++ b/MP-RIOC/Pages/Index.cshtml @@ -0,0 +1,25 @@ +@page +@{ + Layout = null; +} + + + + MP.RIOC - Router Status + + + +
+

MP.RIOC

+ MAPO Server Router +

Stato del Servizio: ONLINE

+
+

Questo server smista il traffico tra MP.IO (API Legacy dotNet 4.7.2) e MP.IOC (nuove API .NET 8)

+ Versione Router: @System.Reflection.Assembly.GetExecutingAssembly().GetName().Version +
+ + \ No newline at end of file diff --git a/MP-RIOC/Pages/Index.cshtml.cs b/MP-RIOC/Pages/Index.cshtml.cs new file mode 100644 index 00000000..0d5b03ed --- /dev/null +++ b/MP-RIOC/Pages/Index.cshtml.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace MP.RIOC.Pages +{ + public class IndexModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/MP-RIOC/Program.cs b/MP-RIOC/Program.cs new file mode 100644 index 00000000..acbc5ca6 --- /dev/null +++ b/MP-RIOC/Program.cs @@ -0,0 +1,131 @@ +using MP.RIOC.Services; +using NLog; +using NLog.Web; +using StackExchange.Redis; +using System.Diagnostics; +using System.Net; +using System.Reflection; + +var builder = WebApplication.CreateBuilder(args); + +// recupero env corrente +var env = builder.Environment; +var logger = LogManager.Setup() + .LoadConfigurationFromAppSettings() + .GetCurrentClassLogger(); + +var assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version?.ToString(); +logger.Info($"MP.RIOC | Program.cs: startup | v.{assemblyVersion}"); +logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}"); + +// Config setup +ConfigurationManager configuration = builder.Configuration; +// REDIS setup +logger.Info("Config OK"); +string confRedis = configuration.GetConnectionString("Redis"); +string redisSrvAddr = confRedis.Substring(0, confRedis.IndexOf(":")); +logger.Info("Setup REDIS OK"); + +// 1. Configurazione dell'invoker personalizzato (Risolve i tuoi errori) +var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler +{ + UseProxy = false, + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + UseCookies = false, + // Correzione per il tracing: usa il propagatore corrente di sistema + ActivityHeadersPropagator = DistributedContextPropagator.Current, + ConnectTimeout = TimeSpan.FromSeconds(15), + + // Gestione certificato (ignora errori per localhost/test) + SslOptions = new System.Net.Security.SslClientAuthenticationOptions + { + RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + } +}); + +builder.Services.AddSingleton(httpClientInvoker); +builder.Services.AddHttpForwarder(); + +// avvio oggetto shared x redis... +var redisMux = ConnectionMultiplexer.Connect(confRedis); +builder.Services.AddSingleton(redisMux); + +// Registrazione dei servizi custom +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); + +logger.Info("Standard service configured"); + +// WeightProvider: Redis/Memory da config +var weightOnRedis = builder.Configuration.GetValue("ServerConf:RedisWeight", false); +if (weightOnRedis) +{ + builder.Services.AddSingleton(); +} +else +{ + builder.Services.AddSingleton(); +} +logger.Info($"Weight service configured | use Redis: {weightOnRedis}"); + +// RouteManager registration (singleton) +builder.Services.AddSingleton(); +logger.Info("Singleton Route Manager registered"); + +// aggiunta pagina razor di stato +builder.Services.AddRazorPages(); + +var app = builder.Build(); + +// 1. Configurazione Base Path +string baseUrl = configuration.GetValue("ServerConf:BaseUrlIoc") ?? "/MP/RIOC"; +app.UsePathBase(baseUrl); + +// 2. Middleware statici (essenziali per CSS/JS delle pagine Razor) +app.UseStaticFiles(); + +// 3. Abilita il Routing (necessario per MapGet e MapRazorPages) +app.UseRouting(); + +// 4. Logging middleware +app.Use(async (ctx, next) => +{ + logger.Debug($"Incoming request PathBase='{ctx.Request.PathBase}' Path='{ctx.Request.Path}'"); + await next(); +}); + +// 5. Il cuore del Proxy (MapWhen è terminale per le richieste che lo soddisfano) +string routePath = configuration.GetValue("ServerConf:RoutePath") ?? "/api/IOB"; +string fullPath = $"{baseUrl}{routePath}".Replace("//", "/"); +logger.Info($"BaseUrl: {baseUrl}"); +app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(routePath, StringComparison.OrdinalIgnoreCase), + builder => + { + builder.Run(async ctx => + { + var routeManager = ctx.RequestServices.GetRequiredService(); + await routeManager.HandleAsync(ctx); + }); + }); + +// 6. Definizione degli Endpoints locali +app.MapRazorPages(); + +app.MapGet("/router-status", () => Results.Ok(new +{ + Status = "Online", + Version = assemblyVersion, + Mode = weightOnRedis ? "Redis" : "InMemory", + Time = DateTime.Now +})); + +// 7. Fallback "intelligente" +// Invece di app.Run, usiamo MapFallback che viene eseguito SOLO se nessun altro endpoint o MapWhen ha risposto +app.MapFallback(async context => +{ + context.Response.StatusCode = 404; + await context.Response.WriteAsync("Router: Endpoint non trovato o non mappato."); +}); + +app.Run(); \ No newline at end of file diff --git a/MP-RIOC/Properties/launchSettings.json b/MP-RIOC/Properties/launchSettings.json new file mode 100644 index 00000000..b57a323d --- /dev/null +++ b/MP-RIOC/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:15280", + "sslPort": 44323 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "MP/RIOC/api/IOB/", + "applicationUrl": "http://localhost:5290", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "MP/RIOC/api/IOB/", + "applicationUrl": "https://localhost:7120;http://localhost:5290", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "MP/RIOC/api/IOB/", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/MP-RIOC/Services/IWeightProvider.cs b/MP-RIOC/Services/IWeightProvider.cs new file mode 100644 index 00000000..b18e4fcb --- /dev/null +++ b/MP-RIOC/Services/IWeightProvider.cs @@ -0,0 +1,29 @@ +using MP.Core.DTO; + +namespace MP.RIOC.Services +{ + public interface IWeightProvider + { + #region Public Methods + + /// + /// Ritorna l'intero elenco dei weight attivi nel formato WeightDTO + /// + /// + Task> GetAllWeightsAsync(); + + /// + /// Ritorna la coppia (oldWeight, newWeight) per scegliere dove instradare il metodo tra i 2 sistemi API. + /// + (int oldWeight, int newWeight) GetWeightsFor(string method); + + /// + /// Aggiorna/Aggiuinge il valore del weight richiesto + /// + /// + /// + bool UpsertWeight(WeightDTO updRecord); + + #endregion Public Methods + } +} diff --git a/MP-RIOC/Services/InMemoryWeightProvider.cs b/MP-RIOC/Services/InMemoryWeightProvider.cs new file mode 100644 index 00000000..f4baf415 --- /dev/null +++ b/MP-RIOC/Services/InMemoryWeightProvider.cs @@ -0,0 +1,56 @@ +using MP.Core.DTO; +using System.Collections.Concurrent; + +namespace MP.RIOC.Services +{ + public class InMemoryWeightProvider : IWeightProvider + { + private readonly ConcurrentDictionary _map = new(); + private readonly int _defaultOld; + private readonly int _defaultNew; + + public InMemoryWeightProvider(IConfiguration config) + { + _defaultOld = config.GetValue("RouteMan:DefaultWeightOld", 100); + _defaultNew = config.GetValue("RouteMan:DefaultWeightNew", 0); + } + + public (int oldWeight, int newWeight) GetWeightsFor(string method) + { + if (string.IsNullOrEmpty(method)) method = "unknown"; + return _map.GetOrAdd(method, _ => (_defaultOld, _defaultNew)); + } + + public void SetWeights(string method, int oldWeight, int newWeight) + { + _map[method] = (Math.Clamp(oldWeight, 0, 100), Math.Clamp(newWeight, 0, 100)); + } + + public async Task> GetAllWeightsAsync() + { + var result = new List(); + await Task.Delay(1); + + foreach (var kvp in _map) + { + result.Add(new WeightDTO + { + Method = kvp.Key, + OldWeight = Math.Clamp(kvp.Value.oldW, 0, 100), + NewWeight = Math.Clamp(kvp.Value.newW, 0, 100) + }); + } + return result; + } + + public bool UpsertWeight(WeightDTO updRecord) + { + if (updRecord == null || string.IsNullOrEmpty(updRecord.Method)) + return false; + + _map[updRecord.Method] = (Math.Clamp(updRecord.OldWeight, 0, 100), Math.Clamp(updRecord.NewWeight, 0, 100)); + + return true; + } + } +} diff --git a/MP-RIOC/Services/PreserveBodyTransformer.cs b/MP-RIOC/Services/PreserveBodyTransformer.cs new file mode 100644 index 00000000..2434f658 --- /dev/null +++ b/MP-RIOC/Services/PreserveBodyTransformer.cs @@ -0,0 +1,38 @@ +using Yarp.ReverseProxy.Forwarder; + +namespace MP.RIOC.Services +{ + public class PreserveBodyTransformer : HttpTransformer + { + public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) + { + // Chiama il base (usa overload con cancellationToken) + await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); + + // Imposta il method correttamente + proxyRequest.Method = new HttpMethod(httpContext.Request.Method); + + // Se vuoi leggere/loggare il body, abilita buffering e rewind. + // NON assegnare proxyRequest.Content: YARP copierà il body da HttpContext.Request. + if (httpContext.Request.ContentLength > 0 || httpContext.Request.Body.CanRead) + { + // Abilita buffering solo se necessario (attenzione a payload grandi) + httpContext.Request.EnableBuffering(); + + // Rewind per sicurezza + httpContext.Request.Body.Position = 0; + + // Se vuoi leggere il body per logging, fallo qui ma non sostituire proxyRequest.Content. + // Esempio (opzionale): leggere senza consumare + // using var sr = new StreamReader(httpContext.Request.Body, leaveOpen: true); + // var bodyText = await sr.ReadToEndAsync(); + // httpContext.Request.Body.Position = 0; + } + } + + public override ValueTask TransformResponseAsync(HttpContext httpContext, HttpResponseMessage? proxyResponse, CancellationToken cancellationToken) + { + return base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken); + } + } +} diff --git a/MP-RIOC/Services/RedisWeightProvider.cs b/MP-RIOC/Services/RedisWeightProvider.cs new file mode 100644 index 00000000..e003c8e5 --- /dev/null +++ b/MP-RIOC/Services/RedisWeightProvider.cs @@ -0,0 +1,157 @@ +using MP.Core.DTO; +using StackExchange.Redis; + +namespace MP.RIOC.Services +{ + public class RedisWeightProvider : IWeightProvider + { + #region Public Constructors + + public RedisWeightProvider(IConnectionMultiplexer mux, IConfiguration config) + { + _config = config; + _db = mux.GetDatabase(); + _mux = mux; + _defaultOld = config.GetValue("RouteMan:DefaultWeightOld", 100); + _defaultNew = config.GetValue("RouteMan:DefaultWeightNew", 0); + _redisBaseKey = config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; + _keyPrefix = $"{_redisBaseKey}:route_weight:"; + } + + #endregion Public Constructors + + #region Public Methods + + /// + /// Ritorna (oldWeight, newWeight) per il metodo. Se non esiste, crea la chiave con i default. + /// + public (int oldWeight, int newWeight) GetWeightsFor(string method) + { + if (string.IsNullOrEmpty(method)) method = "unknown"; + var key = _keyPrefix + method; + + // Leggi entrambi i campi + var oldVal = _db.HashGet(key, "old"); + var newVal = _db.HashGet(key, "new"); + + // Se entrambi mancanti, inizializza con default (usando HSet con When.NotExists per evitare overwrite) + if (oldVal.IsNull && newVal.IsNull) + { + // Imposta i campi singolarmente con When.NotExists per evitare overwrite + _db.HashSet(key, "old", _defaultOld, When.NotExists); + _db.HashSet(key, "new", _defaultNew, When.NotExists); + + // Rileggi per essere sicuri + oldVal = _db.HashGet(key, "old"); + newVal = _db.HashGet(key, "new"); + } + + // Se uno dei due manca, impostalo al default (non sovrascrive l'altro) + if (oldVal.IsNull) + { + _db.HashSet(key, "old", _defaultOld, When.NotExists); + oldVal = _defaultOld; + } + if (newVal.IsNull) + { + _db.HashSet(key, "new", _defaultNew, When.NotExists); + newVal = _defaultNew; + } + + if (!int.TryParse(oldVal.ToString(), out var oldW)) oldW = _defaultOld; + if (!int.TryParse(newVal.ToString(), out var newW)) newW = _defaultNew; + + // clamp 0..100 + oldW = Math.Clamp(oldW, 0, 100); + newW = Math.Clamp(newW, 0, 100); + + return (oldW, newW); + } + + // API per aggiornare i pesi a runtime (opzionale) + public void SetWeights(string method, int oldWeight, int newWeight) + { + var key = _keyPrefix + (string.IsNullOrEmpty(method) ? "unknown" : method); + _db.HashSet(key, new HashEntry[] { + new HashEntry("old", Math.Clamp(oldWeight,0,100)), + new HashEntry("new", Math.Clamp(newWeight,0,100)) + }); + } + + public async Task> GetAllWeightsAsync() + { + var result = new List(); + var server = _mux.GetServer(_mux.GetEndPoints().First()); + + if (server.IsReplica) + { + return result; + } + + await foreach (var key in server.KeysAsync(pattern: $"{_keyPrefix}*")) + { + var methodName = KeyToString(key.ToString()); + if (string.IsNullOrEmpty(methodName)) continue; + + var oldVal = _db.HashGet(key, "old"); + var newVal = _db.HashGet(key, "new"); + + int oldW = 100; + int newW = 0; + + if (!oldVal.IsNull && int.TryParse(oldVal.ToString(), out var parsedOld)) + oldW = Math.Clamp(parsedOld, 0, 100); + + if (!newVal.IsNull && int.TryParse(newVal.ToString(), out var parsedNew)) + newW = Math.Clamp(parsedNew, 0, 100); + + result.Add(new WeightDTO { Method = methodName, OldWeight = oldW, NewWeight = newW }); + } + + // riordino desc x NEW poi alfabetico... + result = result + .OrderByDescending(x => x.NewWeight) + .ThenBy(x => x.Method) + .ToList(); + + return result; + } + + public bool UpsertWeight(WeightDTO updRecord) + { + if (updRecord == null || string.IsNullOrEmpty(updRecord.Method)) + return false; + + var key = _keyPrefix + updRecord.Method; + _db.HashSet(key, new HashEntry[] { + new HashEntry("old", Math.Clamp(updRecord.OldWeight, 0, 100)), + new HashEntry("new", Math.Clamp(updRecord.NewWeight, 0, 100)) + }); + + return true; + } + + private string KeyToString(string key) + { + if (string.IsNullOrEmpty(key)) return ""; + var prefix = _keyPrefix ?? ""; + if (key.StartsWith(prefix)) + return key.Substring(prefix.Length); + return key; + } + + #endregion Public Methods + + #region Private Fields + + private static string _keyPrefix = "route_weight:"; + private static string _redisBaseKey = ""; + private readonly IConfiguration _config; + private readonly IDatabase _db; + private readonly IConnectionMultiplexer _mux; + private readonly int _defaultNew; + private readonly int _defaultOld; + + #endregion Private Fields + } +} diff --git a/MP-RIOC/Services/RouteManager.cs b/MP-RIOC/Services/RouteManager.cs new file mode 100644 index 00000000..3febe3c9 --- /dev/null +++ b/MP-RIOC/Services/RouteManager.cs @@ -0,0 +1,167 @@ +using NLog; +using System.Diagnostics; +using Yarp.ReverseProxy.Forwarder; + +namespace MP.RIOC.Services +{ + public class RouteManager + { + private readonly IHttpForwarder _forwarder; + private readonly HttpMessageInvoker _httpClientInvoker; + private readonly PreserveBodyTransformer _transformer; + private readonly RouteStatsManager _stats; + private readonly IWeightProvider _weightProvider; + private readonly IConfiguration _config; + private readonly ForwarderRequestConfig _forwarderConfig; + + public RouteManager( + IHttpForwarder forwarder, + HttpMessageInvoker httpClientInvoker, + PreserveBodyTransformer transformer, + RouteStatsManager stats, + IWeightProvider weightProvider, + IConfiguration config) + { + _forwarder = forwarder; + _httpClientInvoker = httpClientInvoker; + _transformer = transformer; + _stats = stats; + _weightProvider = weightProvider; + _config = config; + _routePath = _config.GetValue("ServerConf:RoutePath") ?? "/api/IOB"; + _forwarderConfig = new ForwarderRequestConfig + { + ActivityTimeout = TimeSpan.FromSeconds(30), + // Parse della versione (es. "1.1") + Version = Version.Parse(_config.GetValue("ServerConf:HttpVersion") ?? "1.1"), + // Policy per la versione + VersionPolicy = _config.GetValue("ServerConf:HttpVersionPolicy") + ?? HttpVersionPolicy.RequestVersionExact + }; + } + + private string _routePath = ""; + + private static Logger Log = LogManager.GetCurrentClassLogger(); + + + public async Task HandleAsync(HttpContext context) + { + var sw = Stopwatch.StartNew(); + var routePrefix = new PathString(_routePath); + var fullPrefix = context.Request.PathBase.Add(routePrefix); + + string relativePath; + string query = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : ""; + + if (context.Request.Path.StartsWithSegments(fullPrefix, out var remaining)) + { + relativePath = remaining.Value.TrimStart('/'); + } + else if (context.Request.Path.StartsWithSegments(routePrefix, out remaining)) + { + relativePath = remaining.Value.TrimStart('/'); + } + else + { + var fullPath = (context.Request.PathBase + context.Request.Path).Value ?? ""; + var idx = fullPath.IndexOf("/RIOB/", StringComparison.OrdinalIgnoreCase); + relativePath = idx >= 0 ? fullPath[(idx + "/RIOB/".Length)..] : fullPath.TrimStart('/'); + } + + Log.Debug($"PathBase={context.Request.PathBase} | Path={context.Request.Path} | relativePath={relativePath}"); + + // Procedo a calcolare metodo e ID... + string metodo = "/"; + string id = "ALL"; + if (!string.IsNullOrEmpty(relativePath)) + { + // Rimuovo eventuale query string + var pathOnly = relativePath.Split('?')[0]; + + // Splitto per / + var parts = pathOnly.Split('/', StringSplitOptions.RemoveEmptyEntries); + + if (parts.Length > 0) metodo = parts[0]; + if (parts.Length > 1) id = parts[1]; + } + + Log.Debug($"Metodo: {metodo} | machineId: {id}"); + + var (oldW, newW) = _weightProvider.GetWeightsFor(metodo); + var pickNew = DecideByWeights(oldW, newW); + var target = pickNew ? "IOC" : "IO"; + + // Costruisci destination base in base al target + var destBase = pickNew + ? _config["ServerConf:NewApiUrl"] + : _config["ServerConf:OldApiUrl"]; + + if (string.IsNullOrEmpty(destBase)) + { + context.Response.StatusCode = 502; + await context.Response.WriteAsync("Destination not configured"); + return; + } + + if (!destBase.EndsWith("/")) destBase += "/"; + // salva path originale per ripristino + var originalPath = context.Request.Path; + var originalPathBase = context.Request.PathBase; + + try + { + string sKey = $"{target}|{metodo}|{id}"; + + // Registra scelta + _stats.Record(sKey); + + // imposta la Path che vogliamo che YARP appenda al destBase + context.Request.Path = new PathString("/" + relativePath); + // opzionale: se vuoi che PathBase sia vuoto per il backend, impostalo così + context.Request.PathBase = PathString.Empty; + + Log.Debug($"Forwarding to base {destBase} with forwarded path {context.Request.Path} | original PathBase:{originalPathBase} | Path={originalPath})"); + + var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, _forwarderConfig, _transformer, context.RequestAborted); + + sw.Stop(); + _stats.RecordDuration(sKey, sw.Elapsed); + + if (error != ForwarderError.None) + { + var feat = context.GetForwarderErrorFeature(); + Log.Error(feat?.Exception, "Forwarder error to {DestBase}", destBase); + if (!context.Response.HasStarted) + { + context.Response.StatusCode = 502; + await context.Response.WriteAsync($"Forward error: {feat?.Exception?.Message}"); + } + } + } + finally + { + // ripristina i path originali per non rompere la pipeline successiva + context.Request.Path = originalPath; + context.Request.PathBase = originalPathBase; + } + } + + private bool DecideByWeights(int oldW, int newW) + { + bool result = false; + // se entrambi zero -> prefer legacy + var total = oldW + newW; + if (total <= 0) + { + result = false; + } + else + { + var rnd = Random.Shared.NextDouble(); // 0..1 + result = rnd < (double)newW / total; + } + return result; + } + } +} diff --git a/MP-RIOC/Services/RouteStatsManager.cs b/MP-RIOC/Services/RouteStatsManager.cs new file mode 100644 index 00000000..8bb5caed --- /dev/null +++ b/MP-RIOC/Services/RouteStatsManager.cs @@ -0,0 +1,71 @@ +using System.Collections.Concurrent; + +namespace MP.RIOC.Services +{ + public class RouteStats + { + public long Count; + public TimeSpan TotalDuration = TimeSpan.Zero; + public TimeSpan MaxDuration = TimeSpan.Zero; + public TimeSpan MinDuration = TimeSpan.MaxValue; + public ConcurrentDictionary StatusCodes = new(); + } + + public class RouteStatsManager + { + private readonly ConcurrentDictionary _map = new(); + + /// + /// Registrazione del metodo + destinazione + /// + /// + public void Record(string dest_method) + { + var stat = _map.GetOrAdd(dest_method, _ => new RouteStats()); + Interlocked.Increment(ref stat.Count); + } + + /// + /// Registrazione destinazione+metodo + /// + /// chiave dest+metodo x salvataggio statistiche + /// + public void RecordDuration(string dest_method, TimeSpan duration) + { + if (_map.TryGetValue(dest_method, out var stat)) + { + lock (stat) + { + stat.TotalDuration += duration; + + if (stat.MaxDuration < duration) + { + stat.MaxDuration = duration; + } + if (stat.MinDuration > duration) + { + stat.MinDuration = duration; + } + } + } + } + + public void RecordStatusCode(string method, int statusCode) + { + if (_map.TryGetValue(method, out var stat)) + { + stat.StatusCodes.AddOrUpdate(statusCode, 1, (_, v) => v + 1); + } + } + + public IReadOnlyDictionary Snapshot() + { + return _map.ToDictionary(kv => kv.Key, kv => kv.Value); + } + + public void Clear() + { + _map.Clear(); + } + } +} diff --git a/MP-RIOC/WeatherForecast.cs b/MP-RIOC/WeatherForecast.cs new file mode 100644 index 00000000..86a54878 --- /dev/null +++ b/MP-RIOC/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace MP.RIOC +{ + public class WeatherForecast + { + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} diff --git a/MP-RIOC/appsettings.Development.json b/MP-RIOC/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/MP-RIOC/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/MP-RIOC/appsettings.json b/MP-RIOC/appsettings.json new file mode 100644 index 00000000..4583eef3 --- /dev/null +++ b/MP-RIOC/appsettings.json @@ -0,0 +1,79 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Yarp": "Debug", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "NLog": { + "variables": { + "baseFileDir": "${basedir}/logs/", + "layout": "${longdate} | ${uppercase:${level}} | ${logger:shortName=false} | ${message}" + }, + "extensions": [ + { "assembly": "NLog.Extensions.Logging" }, + { "assembly": "NLog.Web.AspNetCore" } + ], + "throwConfigExceptions": true, + "targets": { + "async": true, + "logfile": { + "type": "File", + "fileName": "${basedir}/logs/${shortdate}.log", + "archiveEvery": "Day", + "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", + "archiveNumbering": "DateAndSequence", + "archiveAboveSize": "10240000", + "archiveDateFormat": "HH", + "maxArchiveFiles": "60", + "maxArchiveDays": "30" + }, + "logconsole": { + "type": "ColoredConsole", + "layout": "${longdate} | ${uppercase:${level}} | ${logger:shortName=true} | ${message}" + } + }, + "rules": [ + { + "logger": "*", + "minLevel": "Trace", + "writeTo": "logconsole" + }, + { + "logger": "*", + "minLevel": "Info", + "writeTo": "logfile" + } + ] + }, + "CodApp": "MP.RIOC", + "RouteMan": { + "MetricCalcIntervalSeconds": 10, + "MetricFlushIntervalSeconds": 60, + "DefaultWeightOld": 100, + "DefaultWeightNew": 0, + "DeleteExpiredMetrics": true + }, + "ServerConf": { + "RoutePath": "/api/IOB", + "HttpVersion": "1.1", + "HttpVersionPolicy": "RequestVersionExact", + "OldApiUrl": "https://iis01.egalware.com/MP/IO/IOB/", + "NewApiUrl": "https://iis01.egalware.com/MP/IOC/api/IOB/", + "BaseUrlIoc": "/MP/RIOC/", + "MpIoNS": "MoonPro:SQL2016DEV:MoonPro", + "RedisBaseKey": "MP-IOC", + "RedisWeight": true, + "SafePages": "Index", + "redisLongTimeCache": 60, + "redisShortTimeCache": 30, + "useFactory": true + }, + "ConnectionStrings": { + "MP.Utils": "Server=SQL2016DEV;Database=MoonPro_Utils; User ID=sa;Password=keyhammer16; integrated security=False; App=MP.IOC;", + "Redis": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false", + "RedisAdmin": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true" + } +} diff --git a/MP-RIOC/compilerconfig.json b/MP-RIOC/compilerconfig.json new file mode 100644 index 00000000..445819db --- /dev/null +++ b/MP-RIOC/compilerconfig.json @@ -0,0 +1,6 @@ +[ + { + "outputFile": "Components/Layout/MainLayout.razor.css", + "inputFile": "Components/Layout/MainLayout.razor.less" + } +] \ No newline at end of file diff --git a/MP-RIOC/compilerconfig.json.defaults b/MP-RIOC/compilerconfig.json.defaults new file mode 100644 index 00000000..a5a10c08 --- /dev/null +++ b/MP-RIOC/compilerconfig.json.defaults @@ -0,0 +1,59 @@ +{ + "compilers": { + "less": { + "autoPrefix": "", + "cssComb": "none", + "ieCompat": true, + "math": null, + "strictMath": false, + "strictUnits": false, + "relativeUrls": true, + "rootPath": "", + "sourceMapRoot": "", + "sourceMapBasePath": "", + "sourceMap": false + }, + "sass": { + "autoPrefix": "", + "loadPaths": "", + "style": "expanded", + "relativeUrls": true, + "sourceMap": false + }, + "stylus": { + "sourceMap": false + }, + "babel": { + "sourceMap": false + }, + "coffeescript": { + "bare": false, + "runtimeMode": "node", + "sourceMap": false + }, + "handlebars": { + "root": "", + "noBOM": false, + "name": "", + "namespace": "", + "knownHelpersOnly": false, + "forcePartial": false, + "knownHelpers": [], + "commonjs": "", + "amd": false, + "sourceMap": false + } + }, + "minifiers": { + "css": { + "enabled": true, + "termSemicolons": true, + "gzip": false + }, + "javascript": { + "enabled": true, + "termSemicolons": true, + "gzip": false + } + } +} \ No newline at end of file diff --git a/MP-RIOC/post-build.ps1 b/MP-RIOC/post-build.ps1 new file mode 100644 index 00000000..446eede8 --- /dev/null +++ b/MP-RIOC/post-build.ps1 @@ -0,0 +1,32 @@ +param([string]$ProjectDir, [string]$ProjectPath); + +$FileMajMin = "..\MajMin.vers" +$FileVers = "Resources\VersNum.txt" +$FileManIn = "Resources\manifest-original.xml" +$FileManOut = "Resources\manifest.xml" +$FileCLogIn = "Resources\ChangeLog-original.html" +$FileCLogOut = "Resources\ChangeLog.html" +$MajMin = Get-Content $FileMajMin +$currentDate = get-date -format yyMM; +$currentTime = get-date -format dHH; +$find = "(.|\n)*?"; +$currRelNum = $MajMin + $currentDate +"." + $currentTime +$replace = "" + $MajMin + $currentDate +"." + $currentTime + ""; +$csproj = Get-Content $ProjectPath +$csprojUpdated = $csproj -replace $find, $replace + +Set-Content -Path $ProjectPath -Value $csprojUpdated +Set-Content -Path $FileVers -Value $currRelNum + +# replace x manifest +$manData = Get-Content $FileManIn +$manData = $manData -replace "1.0.0.0", $currRelNum +$manData = $manData -replace "{{DIRNAME}}", "MP-IOC" +$manData = $manData -replace "{{BRANCHNAME}}", "stable/LAST" +$manData = $manData -replace "{{PACKNAME}}", "MP.IOC" +Set-Content -Path $FileManOut -Value $manData + +# replace x ChangeLog +$clogData = Get-Content $FileCLogIn +$clogData = $clogData -replace "{{CURRENT-REL}}", $currRelNum +Set-Content -Path $FileCLogOut -Value $clogData diff --git a/MP.IOC/Program.cs b/MP.IOC/Program.cs index c0ccbfda..62c3e23e 100644 --- a/MP.IOC/Program.cs +++ b/MP.IOC/Program.cs @@ -35,7 +35,18 @@ logger.Info("Setup REDIS OK"); // YARP base config -builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); +builder.Services.AddReverseProxy() + .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) + .ConfigureHttpClient((context, handler) => + { + // Se sei in sviluppo o sul server con problemi di certificato + handler.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => + { + // ATTENZIONE: In produzione filtra per hostname o usa cautela + return true; + }; + }); + builder.Services.AddHttpForwarder(); // HttpMessageInvoker (SocketsHttpHandler) diff --git a/MP.IOC/appsettings.json b/MP.IOC/appsettings.json index d7c5727c..0600459a 100644 --- a/MP.IOC/appsettings.json +++ b/MP.IOC/appsettings.json @@ -2,6 +2,7 @@ "Logging": { "LogLevel": { "Default": "Information", + "Yarp": "Debug", "Microsoft.AspNetCore": "Warning", "Microsoft.EntityFrameworkCore.Database.Command": "Warning", "Microsoft.EntityFrameworkCore.Infrastructure": "Warning", @@ -64,9 +65,17 @@ ], "Clusters": { "cluster-old": { + "HttpRequest": { + "Version": "1.1", + "VersionPolicy": "RequestVersionExact" + }, "Destinations": { "old1": { "Address": "https://iis01.egalware.com/MP/IO/IOB/" } } }, "cluster-new": { + "HttpRequest": { + "Version": "1.1", + "VersionPolicy": "RequestVersionExact" + }, "Destinations": { "new1": { "Address": "https://iis01.egalware.com/MP/IOC/api/IOB/" } } } } From 46f8377acbb2436ab2c847a674bed7aa946dd537 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 08:23:35 +0200 Subject: [PATCH 02/13] Completato setup ottimizzato per RIOC --- MP-RIOC/Pages/Index.cshtml | 101 +++++++++++++++++++++++++- MP-RIOC/Program.cs | 5 +- MP-RIOC/Services/RouteStatsManager.cs | 1 + 3 files changed, 104 insertions(+), 3 deletions(-) diff --git a/MP-RIOC/Pages/Index.cshtml b/MP-RIOC/Pages/Index.cshtml index 48b156b7..44c0cf28 100644 --- a/MP-RIOC/Pages/Index.cshtml +++ b/MP-RIOC/Pages/Index.cshtml @@ -1,6 +1,9 @@ -@page +@* @page +@using MP.RIOC.Services +@inject RouteStatsManager StatsManager @{ Layout = null; + var metrics = StatsManager.Snapshot(); } @@ -22,4 +25,100 @@ Versione Router: @System.Reflection.Assembly.GetExecutingAssembly().GetName().Version + *@ + +@page +@using MP.RIOC.Services +@inject RouteStatsManager StatsManager +@{ + Layout = null; + var metrics = StatsManager.Snapshot(); +} + + + + MP.RIOC Dashboard + + + +
+
+
+

MAPO MES

+
Live Dashboard
+
+
+

MP-RIOC

+

Router I/O Controller

+
+
+
+
+ Stato: ● Online +
+
+ Versione: @System.Reflection.Assembly.GetExecutingAssembly().GetName().Version +
+
+ + + + + + + + + + @foreach (var stat in metrics) + { + + + + + + } + @if (!metrics.Any()) + { + + + + } + +
Target | Metodo | IDChiamateLatenza Media
+ @if(stat.Key.StartsWith("IOC")) { NEW } + else { OLD } + @stat.Key + @stat.Value.Count@(stat.Value.AvgDuration)ms
Nessun dato raccolto al momento.
+ +
+ Ultimo aggiornamento: @DateTime.Now.ToString("HH:mm:ss") +

+
+ \ No newline at end of file diff --git a/MP-RIOC/Program.cs b/MP-RIOC/Program.cs index acbc5ca6..573ef479 100644 --- a/MP-RIOC/Program.cs +++ b/MP-RIOC/Program.cs @@ -112,12 +112,13 @@ app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments(routePath, StringComparis // 6. Definizione degli Endpoints locali app.MapRazorPages(); -app.MapGet("/router-status", () => Results.Ok(new +app.MapGet("/router-status", (RouteStatsManager stats) => Results.Ok(new { Status = "Online", Version = assemblyVersion, Mode = weightOnRedis ? "Redis" : "InMemory", - Time = DateTime.Now + Time = DateTime.Now, + Metrics = stats.Snapshot() })); // 7. Fallback "intelligente" diff --git a/MP-RIOC/Services/RouteStatsManager.cs b/MP-RIOC/Services/RouteStatsManager.cs index 8bb5caed..9274826a 100644 --- a/MP-RIOC/Services/RouteStatsManager.cs +++ b/MP-RIOC/Services/RouteStatsManager.cs @@ -9,6 +9,7 @@ namespace MP.RIOC.Services public TimeSpan MaxDuration = TimeSpan.Zero; public TimeSpan MinDuration = TimeSpan.MaxValue; public ConcurrentDictionary StatusCodes = new(); + public TimeSpan AvgDuration => TotalDuration / (Count > 0 ? Count : 1); } public class RouteStatsManager From 0209dbfc4c291f4cc51d04143840bd7737233597 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 08:58:56 +0200 Subject: [PATCH 03/13] Aggiunta publish profiles --- .../Properties/PublishProfiles/IIS01.pubxml | 25 +++++++++++++++++++ .../Properties/PublishProfiles/IIS03.pubxml | 25 +++++++++++++++++++ .../Properties/PublishProfiles/IIS04.pubxml | 25 +++++++++++++++++++ .../PublishProfiles/IISProfile.pubxml | 25 +++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 MP-RIOC/Properties/PublishProfiles/IIS01.pubxml create mode 100644 MP-RIOC/Properties/PublishProfiles/IIS03.pubxml create mode 100644 MP-RIOC/Properties/PublishProfiles/IIS04.pubxml create mode 100644 MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml new file mode 100644 index 00000000..d91b9388 --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml @@ -0,0 +1,25 @@ + + + + + MSDeploy + true + Release + Any CPU + https://iis01.egalware.com/MP/RIOC/ + false + b9188473-f4ae-4f9f-be2d-70edaace0db9 + false + https://iis01.egalware.com:8172/MsDeploy.axd + Default Web Site/MP/RIOC + + false + WMSVC + true + true + jenkins + <_SavePWD>true + <_TargetId>IISWebDeploy + net8.0 + + \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml new file mode 100644 index 00000000..d91b9388 --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml @@ -0,0 +1,25 @@ + + + + + MSDeploy + true + Release + Any CPU + https://iis01.egalware.com/MP/RIOC/ + false + b9188473-f4ae-4f9f-be2d-70edaace0db9 + false + https://iis01.egalware.com:8172/MsDeploy.axd + Default Web Site/MP/RIOC + + false + WMSVC + true + true + jenkins + <_SavePWD>true + <_TargetId>IISWebDeploy + net8.0 + + \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml new file mode 100644 index 00000000..d91b9388 --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml @@ -0,0 +1,25 @@ + + + + + MSDeploy + true + Release + Any CPU + https://iis01.egalware.com/MP/RIOC/ + false + b9188473-f4ae-4f9f-be2d-70edaace0db9 + false + https://iis01.egalware.com:8172/MsDeploy.axd + Default Web Site/MP/RIOC + + false + WMSVC + true + true + jenkins + <_SavePWD>true + <_TargetId>IISWebDeploy + net8.0 + + \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml new file mode 100644 index 00000000..d91b9388 --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml @@ -0,0 +1,25 @@ + + + + + MSDeploy + true + Release + Any CPU + https://iis01.egalware.com/MP/RIOC/ + false + b9188473-f4ae-4f9f-be2d-70edaace0db9 + false + https://iis01.egalware.com:8172/MsDeploy.axd + Default Web Site/MP/RIOC + + false + WMSVC + true + true + jenkins + <_SavePWD>true + <_TargetId>IISWebDeploy + net8.0 + + \ No newline at end of file From 9242bcf7e22b87529c0354cc0b5a8139c8183ed2 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 08:59:24 +0200 Subject: [PATCH 04/13] pulizia classi non necessarie services --- MP-RIOC/Data/MpDataService.cs | 5006 --------------------------------- MP-RIOC/WeatherForecast.cs | 13 - 2 files changed, 5019 deletions(-) delete mode 100644 MP-RIOC/Data/MpDataService.cs delete mode 100644 MP-RIOC/WeatherForecast.cs diff --git a/MP-RIOC/Data/MpDataService.cs b/MP-RIOC/Data/MpDataService.cs deleted file mode 100644 index 12eecbd8..00000000 --- a/MP-RIOC/Data/MpDataService.cs +++ /dev/null @@ -1,5006 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using MP.Core.Conf; -using MP.Core.DTO; -using MP.Core.Objects; -using MP.Data; -using MP.Data.Controllers; -using MP.Data.DbModels; -using MP.Data.DbModels.Anag; -using MP.Data.MgModels; -using MP.Data.Services.IOC; -using MP.Data.Services.Mtc; -using Newtonsoft.Json; -using NLog; -using StackExchange.Redis; -using System.Data; -using System.Diagnostics; -using System.Globalization; -using static MP.Core.Objects.Enums; - -namespace MP.RIOC.Data -{ - public class MpDataService : IDisposable - { - #region Public Constructors - - public MpDataService( - IConfiguration configuration, - ILogger logger, - IServiceScopeFactory scopeFactory, - IMtcSetupService mtcServ) - { - _logger = logger; - _logger.LogInformation("Starting MpDataService INIT"); - _configuration = configuration; - _scopeFactory = scopeFactory; - - // setup compoenti REDIS - redisConn = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("Redis")); - redisConnAdmin = ConnectionMultiplexer.Connect(_configuration.GetConnectionString("RedisAdmin")); - redisDb = redisConn.GetDatabase(); - BroadastMsgPipe = new MessagePipe(redisConn, Constants.BROADCAST_M_PIPE); - // leggo cache (lungo periodo e corto periodo) - int.TryParse(_configuration.GetValue("ServerConf:redisLongTimeCache"), out redisLongTimeCache); - int.TryParse(_configuration.GetValue("ServerConf:redisShortTimeCache"), out redisShortTimeCache); - Log.Info($"Redis INIT | redisLongTimeCache: {redisLongTimeCache} min | redisShortTimeCache {redisShortTimeCache} min"); - - // gestione scope factory - useFactory = _configuration.GetValue("ServerConf:useFactory"); - // conf base x servizi condivisi IO/IOC - MpIoNS = _configuration.GetValue("ServerConf:MpIoNS") ?? "MP"; - Log.Info($"MpDataService | useFactory {useFactory} | MpIoNS {MpIoNS}"); - - // conf DB - string connStr = _configuration.GetConnectionString("MP.Data") ?? ""; - if (string.IsNullOrEmpty(connStr)) - { - Log.Error("DbController: ConnString empty!"); - } - else - { - SpecDbController = new MpSpecController(configuration); - IocDbController = new MpIocController(configuration); - Log.Info("DbControllers INIT OK"); - } - - MtcService = mtcServ; - - // conf mongo... - connStr = _configuration.GetConnectionString("mdbConnString") ?? ""; - if (string.IsNullOrEmpty(connStr)) - { - Log.Error("MongoController: ConnString empty!"); - } - else - { - mongoController = new MpMongoController(configuration); - Log.Info("MongoController INIT OK"); - } - - } - - #endregion Public Constructors - - #region Public Properties - - public static MpIocController IocDbController { get; set; } = null!; - public static MpMongoController mongoController { get; set; } = null!; - public static MpSpecController SpecDbController { get; set; } = null!; - public MessagePipe BroadastMsgPipe { get; set; } = null!; - - /// - /// Dizionario dei tag configurati per IOB - /// - public Dictionary> currTagConf { get; set; } = new Dictionary>(); - - #endregion Public Properties - - #region Public Methods - - /// - /// Recupera eventuali azioni richieste - /// - /// - public async Task ActionGetReq() - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - DisplayAction? result = null; - // cerco in redis... - RedisValue rawData = await redisDb.StringGetAsync(Utils.redisActionReq); - if (!string.IsNullOrEmpty($"{rawData}")) - { - result = JsonConvert.DeserializeObject($"{rawData}"); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ActionGetReq Read from REDIS: {ts.TotalMilliseconds}ms"); - } - if (result == null) - { - result = new DisplayAction(); - } - return result; - } - - /// - /// Salva richiesta azione - /// - /// - /// - public bool ActionSetReq(DisplayAction? act2save) - { - bool fatto = false; - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - // cerco in redis... - string rawData = JsonConvert.SerializeObject(act2save); - // invio broadcast + salvo in redis - BroadastMsgPipe.saveAndSendMessage(Utils.redisActionReq, rawData); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ActionSetReq REDIS send to broadcast + Write cache: {ts.TotalMilliseconds}ms"); - return fatto; - } - - /// - /// Verifica se sia da reinviare un tName alla macchina dall'elenco di quelli salvati (in - /// modalità upsert) se non scaduti - /// - /// - /// - /// - /// - public bool AddCheckTask4Machine(string idxMacchina, taskType taskKey, string taskVal) - { - bool answ = false; - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - Log.Info($"addCheckTask4Machine idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); - try - { - Dictionary savedTask = mSavedTaskMacchina(idxMacchina); - // cerco valOut saved - string savedVal = savedTask[taskKey.ToString()]; - // se ho un valOut != "" --> rimetto in coda di invio... - if (!string.IsNullOrEmpty(savedVal) && (savedVal != taskVal)) - { - // leggo tName attuali... - Dictionary currTask = mTaskMacchina(idxMacchina); - // rimetto in tName da eseguire... - currTask[taskKey.ToString()] = savedVal; - RedisSetHashDict(currHash, currTask); - answ = true; - Log.Info($"re-issued task4machine: idxMacchina: {idxMacchina} | taskKey: {taskKey} | savedVal: {savedVal}"); - } - } - catch (Exception exc) - { - Log.Error($"Eccezione in AddCheckTask4Machine{Environment.NewLine}{exc}"); - } - - return answ; - } - - /// - /// Aggiunge un PARAMETRO OPZIONALE all'elenco di quelli salvati (in modalità upsert) - /// - /// - /// - /// - /// - public bool AddOptPar4Machine(string idxMacchina, string taskKey, string taskVal) - { - bool answ = false; - RedisKey currHash = Utils.RedKeyOptPar(idxMacchina, MpIoNS); - try - { - // leggo tName attuali... - var currVal = mOptParMacchina(idxMacchina); - // verifico se esista o se vada aggiunto... - if (currVal.ContainsKey(taskKey)) - { - currVal[taskKey] = taskVal; - } - else - { - currVal.Add(taskKey, taskVal); - } - RedisSetHashDict(currHash, currVal); - answ = true; - } - catch (Exception exc) - { - Log.Error($"Eccezione in addOptPar4Machine{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// Aggiunge un tName all'elenco di quelli salvati (in modalità upsert) - /// - /// - /// - /// - /// - public bool AddTask4Machine(string idxMacchina, Enums.taskType taskKey, string taskVal) - { - bool answ = false; - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - var currSavedParHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina, MpIoNS); - Dictionary currTask = new Dictionary(); - Dictionary savedTask = new Dictionary(); - try - { - // leggo tName attuali... - currTask = mTaskMacchina(idxMacchina); - if (currTask.ContainsKey($"{taskKey}")) - { - currTask[$"{taskKey}"] = taskVal; - } - else - { - currTask.Add($"{taskKey}", taskVal); - } - RedisSetHashDict(currHash, currTask); - answ = true; - Log.Info($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {taskKey} | taskVal: {taskVal}"); - } - catch { } - // verifico in base al tipo di tName se fare backup... - switch (taskKey) - { - case Enums.taskType.setArt: - case Enums.taskType.setComm: - case Enums.taskType.setPzComm: - case Enums.taskType.setProg: - // leggo tName SALVATI attuali... - savedTask = mSavedTaskMacchina(idxMacchina); - savedTask[taskKey.ToString()] = taskVal; - RedisSetHashDict(currSavedParHash, savedTask); - answ = true; - break; - - case Enums.taskType.endProd: - // salvo un DICT vuoto x resettare - savedTask = new Dictionary(); - RedisSetHashDict(currSavedParHash, savedTask); - answ = true; - break; - - default: - break; - } - return answ; - } - - /// - /// Aggiunge un set di task x macchina all'elenco di quelli salvati (in modalità upsert) - /// - /// - /// - /// - /// - public async Task AddTask4MacListAsync(string idxMacchina, Dictionary taskDict) - { - bool answ = false; - bool needSaveParams = false; - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - var currSavedParHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina, MpIoNS); - Dictionary currTask = new Dictionary(); - Dictionary savedTask = new Dictionary(); - - // leggo valori attuali... - currTask = await mTaskMacchinaAsync(idxMacchina); - // verifico processing dei ricevuti - foreach (var item in taskDict) - { - if (currTask.ContainsKey($"{item.Key}")) - { - currTask[$"{item.Key}"] = item.Value; - } - else - { - currTask.Add($"{item.Key}", item.Value); - } - Log.Trace($"Task ADD | hash: {currHash} | idxMacchina: {idxMacchina} | taskKey: {item.Key} | taskVal: {item.Value}"); - - // verifico in base al tipo di tName se fare backup... - switch (item.Key) - { - case Enums.taskType.setArt: - case Enums.taskType.setComm: - case Enums.taskType.setPzComm: - case Enums.taskType.setProg: - // leggo tName SALVATI attuali... - savedTask = mSavedTaskMacchina(idxMacchina); - savedTask[item.Key.ToString()] = item.Value; - needSaveParams = true; - break; - - case Enums.taskType.endProd: - // salvo un DICT vuoto x resettare - savedTask = new Dictionary(); - needSaveParams = true; - break; - - default: - break; - } - } - // salvo! - await RedisSetHashDictAsync(currHash, currTask); - answ = true; - - if (needSaveParams) - { - await RedisSetHashDictAsync(currSavedParHash, savedTask); - } - return answ; - } - - /// - /// Insert record allarme - /// - /// Data evento - /// Idx macchina - /// area memoria - /// indice memoria - /// valOut status - /// valOut decodificato - /// - public async Task AlarmInsertAsync(DateTime dtRif, string idxMacchina, string memAddress, int memIndex, int statusVal, string valDecoded) - { - // aggiorno record sul DB - bool answ = await IocDbController.AlarmLogInsertAsync(dtRif, idxMacchina, memAddress, memIndex, statusVal, valDecoded); - - return answ; - } - - public async Task> AnagStatiComm() - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - List? result = new List(); - // cerco in redis... - RedisValue rawData = await redisDb.StringGetAsync(Utils.redisStatoCom); - if (!string.IsNullOrEmpty($"{rawData}")) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"AnagStatiComm Read from REDIS: {ts.TotalMilliseconds}ms"); - } - else - { - result = await Task.FromResult(SpecDbController.AnagStatiComm()); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(Utils.redisStatoCom, rawData, getRandTOut(redisLongTimeCache)); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"AnagStatiComm Read from DB: {ts.TotalMilliseconds}ms"); - } - if (result == null) - { - result = new List(); - } - return result; - } - - /// - /// Restituisce l'anagrafica STATI per intero - /// - /// - public async Task> AnagStatiGetAllAsync() - { - List dbResult = new List(); - // cerco in redis... - var currKey = Utils.redisAnagStati; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - dbResult = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - dbResult = await IocDbController.AnagStatiGetAllAsync(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(dbResult); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - return dbResult; - } - - public async Task> AnagTipoArtLV() - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string source = "DB"; - List? result = new List(); - // cerco in redis... - RedisValue rawData = await redisDb.StringGetAsync(Utils.redisTipoArt); - if (!string.IsNullOrEmpty($"{rawData}")) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - source = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.AnagTipoArtLV()); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(Utils.redisTipoArt, rawData, getRandTOut(redisLongTimeCache)); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"AnagTipoArtLV Read from {source}: {ts.TotalMilliseconds}ms"); - if (result == null) - { - result = new List(); - } - return result; - } - - /// - /// Elenco Codice articolo con dati dossier gestiti - /// - /// - public async Task> ArticleWithDossier() - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = Utils.redisArtByDossier; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.ArticleWithDossier()); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ArticleWithDossier | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Eliminazione record selezionato - /// - /// - /// - public async Task ArticoliDeleteRecord(AnagArticoliModel currRec) - { - bool fatto = await SpecDbController.ArticoliDeleteRecord(currRec); - await resetCacheArticoli(); - return fatto; - } - - /// - /// Elenco ultimi articoli data amcchina - /// - /// - /// - public async Task> ArticoliGetLastByMaccAsync(string idxMacc) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisArtList}:Last:{idxMacc}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await IocDbController.ArticoliGetLastByMaccAsync(idxMacc); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ArticoliGetLastByMaccAsync | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Restitusice elenco articoli cercati - /// - /// - /// - /// - public async Task> ArticoliGetSearch(int numRecord, string azienda, string searchVal) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string sKey = string.IsNullOrEmpty(searchVal) ? "***" : searchVal; - string currKey = $"{Utils.redisArtList}:{azienda}:{sKey}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.ArticoliGetSearch(numRecord, azienda, searchVal)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ArticoliGetSearch | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Aggiornamento record selezionato - /// - /// - /// - public async Task ArticoliUpdateRecord(AnagArticoliModel currRec) - { - bool fatto = await SpecDbController.ArticoliUpdateRecord(currRec); - await resetCacheArticoli(); - return fatto; - } - - /// - /// Verifica se sia possiubile cancellare articolo dato suo CodArt cercando su redis o su - /// tab veto da DB - /// - /// - /// - public bool ArticoloDelEnabled(object CodArt) - { - bool answ = false; - string codArticolo = $"{CodArt}"; - int cacheCheckArtUsato = 1; - int.TryParse(_configuration.GetValue("ServerConf:cacheCheckArtUsato"), out cacheCheckArtUsato); - TimeSpan TTLCache = getRandTOut(cacheCheckArtUsato); - // cerco in cache redis... - string redKeyArtUsed = $"{Utils.redKeyArtUsed}:{codArticolo}"; - string redKeyTabCheckArt = Utils.redKeyTabCheckArt; - var rawData = redisDb.StringGet(redKeyArtUsed); - if (!string.IsNullOrEmpty(rawData)) - { - bool.TryParse(rawData, out answ); - } - else - { - // controllo non sia stato mai prodotto sennò non posso cancellare... - try - { - // cerco in cache se ci sia la tabella con gli articoli impiegati... - var rawTable = redisDb.StringGet(redKeyTabCheckArt); - List? artList = new List(); - if (!string.IsNullOrEmpty(rawTable)) - { - artList = JsonConvert.DeserializeObject>($"{rawTable}"); - } - // rileggo... - if (artList == null || artList.Count == 0) - { - artList = new List(); - var tabArticoli = SpecDbController.ArticoliGetUsed(); - var codList = tabArticoli.Select(x => x.CodArticolo); - foreach (string cod in codList) - { - artList.Add(cod); - } - // SE fosse vuoto aggiungo comunque il cado "ND"... - if (artList.Count == 0) - { - artList.Add("ND"); - } - // salvo - rawTable = JsonConvert.SerializeObject(artList); - redisDb.StringSet(redKeyTabCheckArt, rawTable, TTLCache); - } - // cerco nella tabella: se ci fosse --> disabilitato delete - bool usato = false; - if (artList != null && artList.Count > 0) - { - usato = artList.Contains(codArticolo); - } - answ = !usato; - redisDb.StringSet(redKeyArtUsed, $"{answ}", TTLCache); - } - catch - { } - } - return answ; - } - - /// - /// Effettua split ODL - /// - /// macchina - /// effettuare conferma qty - /// imposta la qty prox ODL da ODL che si chiude - /// matricola operatore - /// Round Step quantità prox ODL (corrente come riferimento) - /// Cod ext da associare all'ODL - /// - public async Task AutoStartOdlAsync(string idxMacchina, bool doConfirm, bool qtyFromLast, int matrOpr, int roundStep = 100, string keyRichiesta = "") - { - string answ = "KO"; - DateTime adesso = DateTime.Now; - - // verifico NON CI SIA un veto a NUOVI split... - string redKey = $"{Utils.redisVetoSplitOdl}:{idxMacchina}"; - var rawData = await redisDb.StringGetAsync(redKey); - if (rawData.HasValue) - { - Log.Info($"VETO ATTIVO | Richiesto forceSplitOdl per impianto {idxMacchina} | impossibile procedere"); - } - else - { - // registro VETO x altri split... 5 minuti - await redisDb.StringSetAsync(redKey, $"Inizio SPLIT-ODL {adesso}", TimeSpan.FromMinutes(5)); - // setup dati da config - bool confRett = false; - int modoConfProd = -1; - bool slaveConfirmPzProd = false; - var configData = await ConfigGetAllAsync(); - if (configData != null) - { - var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); - if (currRec != null) - { - bool.TryParse(currRec.Valore, out confRett); - } - currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); - if (currRec != null) - { - int.TryParse(currRec.Valore, out modoConfProd); - } - currRec = configData.FirstOrDefault(x => x.Chiave == "SlaveConfirmPzProd"); - if (currRec != null) - { - bool.TryParse(currRec.Valore, out slaveConfirmPzProd); - } - } - // calcolo la qta da gestire - int qtyConf = 0; - int qtyNew = 0; - int qtyScarto = 0; - if (doConfirm) - { - try - { - var rigaProd = await IocDbController.PezziProdMacchinaAsync(idxMacchina); - var statoProd = await IocDbController.StatoProdMacchinaAsync(idxMacchina, adesso); - qtyConf = rigaProd.pezziNonConfermati; - qtyScarto = statoProd.Pz2RecScarto; - // calcolo nuovi pezzi da confermare - if (qtyFromLast) - { - roundStep = roundStep == 0 ? 1 : roundStep; - double ratio = (double)qtyConf / roundStep; - qtyNew = (int)Math.Round(Math.Ceiling(ratio) * roundStep, 0); - } - } - catch (Exception exc) - { - Log.Error($"AutoStartOdlAsync | Errore recupero pezzi da confermare | idxMacchina {idxMacchina}{Environment.NewLine}{exc}"); - } - } - - // chiamo metodo redis/db... - try - { - // recupero ODL corrente - var currData = await IocDbController.OdlCurrByMaccAsync(idxMacchina); - if (currData != null && currData.IdxOdl > 0) - { - // preparo var x master/slave - bool isMachMaster = await IobIsMasterAsync(idxMacchina); - List slaveList = new(); - if (isMachMaster) - { - List allSlaveList = await IocDbController.Macchine2SlaveAsync(); - slaveList = allSlaveList.Where(x => x.IdxMacchina == idxMacchina).ToList(); - } - - // registro un evento di inizio attrezzaggio (idxTipoEv = 2) - int idxEvento = 2; - Log.Info($"Invio evento ODL-SPLIT per macchina {idxMacchina} | ev: {idxEvento} | art: {currData.CodArticolo} | qty conf: {qtyConf} | sty sca: {qtyScarto} | new qty: {qtyNew}"); - - // creo evento - EventListModel newRecEv = new EventListModel() - { - CodArticolo = currData.CodArticolo, - IdxMacchina = idxMacchina, - IdxTipo = idxEvento, - InizioStato = adesso, - MatrOpr = matrOpr, - pallet = "", - Value = "ODL-SPLIT" - }; - inputComandoMapo resCmd = await scriviRigaEventoBarcodeAsync(newRecEv); - - // era 100, messo 50... - int wTime = 50; - - // eventuale conferma produzione - if (doConfirm) - { - await Task.Delay(wTime); - adesso = DateTime.Now; - // chiamo conferma produzione... - try - { - string chiamata = confRett ? "confermaProdMacchinaFull" : "confermaProdMacchina"; - Log.Info($"Chiamata a {chiamata} con parametri {currData.IdxMacchina} |{matrOpr} | {DateTime.Now.AddDays(-10)} | {DateTime.Now} | {qtyConf} | 0 | {qtyScarto} | {DateTime.Now} | false"); - string idxMacchinaSel = currData.IdxMacchina; - bool fatto = false; - if (confRett) - { - // confermo al netto dei pezzi lasciati... - fatto = await IocDbController.ConfermaProdMacchinaFullAsync(idxMacchinaSel, modoConfProd, qtyConf, 0, qtyScarto, adesso, matrOpr); - if (slaveConfirmPzProd) - { - foreach (var machine in slaveList) - { - await IocDbController.ConfermaProdMacchinaFullAsync(machine.IdxMacchinaSlave, modoConfProd, qtyConf, 0, qtyScarto, adesso, matrOpr); - } - } - } - else - { - fatto = await IocDbController.ConfermaProdMacchinaAsync(idxMacchinaSel, modoConfProd, qtyConf, qtyScarto, adesso, matrOpr); - if (slaveConfirmPzProd) - { - foreach (var machine in slaveList) - { - await IocDbController.ConfermaProdMacchinaAsync(idxMacchinaSel, modoConfProd, qtyConf, qtyScarto, adesso, matrOpr); - } - } - } - if (!fatto) - { - Log.Error($"ERRORE in chiamata {chiamata}"); - } - } - catch (Exception exc) - { - Log.Error($"Eccezione in ConfermaProduzione{Environment.NewLine}{exc}"); - } - } - // 2025.02.19 portato da 100 a 1000 ms attesa x evitare che ODL sia avviato PRIMA della conferma - // attendo 1000 msec x chiudere ODL - await Task.Delay(1000); - // chiamo splitOdl - await IocDbController.AutoStartOdlAsync(currData.IdxOdl, 0, idxMacchina, currData.TCRichAttr, currData.PzPallet, $"Nuovo ODL da forceSplitOdl.autoStart", true, qtyNew, keyRichiesta); - - // elimino eventuale ODL precedente... - string currKey = $"{Utils.redisOdlCurrByMac}:{idxMacchina}"; - await redisDb.KeyDeleteAsync(currKey); - - // ricalcola ODL macchina - var newOdl = await GetCurrOdlAsync(idxMacchina); - // attendo 1000 msec - await Task.Delay(1000); - - adesso = DateTime.Now; - // registro fine ODL (idxTipoEv = 1) - idxEvento = 1; - Log.Info($"Invio evento FINE ODL-SPLIT | idx: {idxMacchina} | ev: {idxEvento} | art: {currData.CodArticolo}"); - // costruisco nuovo evento - newRecEv = new EventListModel() - { - CodArticolo = currData.CodArticolo, - IdxMacchina = idxMacchina, - IdxTipo = idxEvento, - InizioStato = adesso, - MatrOpr = matrOpr, - pallet = "", - Value = "ODL-START" - }; - resCmd = await scriviRigaEventoBarcodeAsync(newRecEv); - // invio eventi setup a macchina.... - string setArtVal = $"{currData.CodArticolo}"; - string setPzCommVal = $"{qtyNew}"; - string setCommVal = $"ODL{newOdl}"; - if (!string.IsNullOrEmpty(keyRichiesta)) - { - setCommVal = $"{keyRichiesta} ODL{newOdl}"; - } - try - { - // invio task caricamento dati ODL - Dictionary updDict = new Dictionary(); - updDict.Add(taskType.setArt, setArtVal); - updDict.Add(taskType.setComm, setCommVal); - updDict.Add(taskType.setPzComm, setPzCommVal); -#if false - addTask4Machine(idxMacchina, taskType.setArt, setArtVal); - addTask4Machine(idxMacchina, taskType.setComm, setCommVal); - addTask4Machine(idxMacchina, taskType.setPzComm, setPzCommVal); - - updateMachineParameter(idxMacchina, "setArt", setArtVal); - updateMachineParameter(idxMacchina, "setComm", setCommVal); - updateMachineParameter(idxMacchina, "setPzComm", setPzCommVal); -#endif - // recupero set attuale parametri - var currMachPar = await MachineParamListAsync(idxMacchina); - List list2upd = new(); - // aggiorno i valori interessati tra quelli nel dizionario - foreach (var item in updDict) - { - var cRec = currMachPar.FirstOrDefault(x => x.uid == $"{item.Key}"); - if (cRec != null) - { - list2upd.Add(cRec); - } - } - // aggiungo task di setParameters x la commessa... - updDict.Add(taskType.setParameter, setCommVal); - // salvo - await AddTask4MacListAsync(idxMacchina, updDict); - await MachineParamUpsertAsync(idxMacchina, list2upd); - } - catch - { } - // chiamo refresh MSE - await IocDbController.RecalcMseAsync(idxMacchina, 0); - // resetto stato macchina... - var kStatoMacc = $"{Utils.redisStatoMacch}:{idxMacchina}"; - await redisDb.KeyDeleteAsync(kStatoMacc); - answ = "OK"; - Log.Info($"Effettuato reset e ricalcoli x split ODL per macchina {idxMacchina}"); - // se è una master richiamo fix x child... - if (isMachMaster) - { - Dictionary updDict = new Dictionary(); - string ts = ""; - string outData = ""; - await IocDbController.OdlFixMachineSlaveAsync(idxMacchina, 30, 1); - foreach (var machine in slaveList) - { - // invio chiusura attrezzaggio - ts = string.Format("{0:yyMMdd}T{0:HHmmss.fff}Z", DateTime.Now); - outData = $"TS:{ts}|MATR:{matrOpr}|ODL:{newOdl}"; - - updDict.Clear(); - updDict.Add(taskType.setArt, setArtVal); - updDict.Add(taskType.setComm, setCommVal); - updDict.Add(taskType.setPzComm, setPzCommVal); - -#if false - addTask4Machine(machine.IdxMacchinaSlave, taskType.fixStopSetup, outData); - addTask4Machine(machine.IdxMacchinaSlave, taskType.forceResetPzCount, outData); - // invio task caricamento dati ODL - addTask4Machine(machine.IdxMacchinaSlave, taskType.setArt, setArtVal); - addTask4Machine(machine.IdxMacchinaSlave, taskType.setComm, setCommVal); - addTask4Machine(machine.IdxMacchinaSlave, taskType.setPzComm, setPzCommVal); - - updateMachineParameter(machine.IdxMacchinaSlave, "setArt", setArtVal); - updateMachineParameter(machine.IdxMacchinaSlave, "setComm", setCommVal); - updateMachineParameter(machine.IdxMacchinaSlave, "setPzComm", setPzCommVal); -#endif - - // recupero set attuale parametri - var currMachPar = await MachineParamListAsync(machine.IdxMacchinaSlave); - List list2upd = new(); - // aggiorno i valori interessati tra quelli nel dizionario - foreach (var item in updDict) - { - var cRec = currMachPar.FirstOrDefault(x => x.uid == $"{item.Key}"); - if (cRec != null) - { - list2upd.Add(cRec); - } - } - // aggiungo task di setParameters x la commessa... - updDict.Add(taskType.fixStopSetup, outData); - updDict.Add(taskType.forceResetPzCount, outData); - updDict.Add(taskType.setParameter, setCommVal); - // salvo - await AddTask4MacListAsync(machine.IdxMacchinaSlave, updDict); - await MachineParamUpsertAsync(machine.IdxMacchinaSlave, list2upd); - } - } - } - } - catch (Exception exc) - { - Log.Error($"Eccezione in forceSplitOdl{Environment.NewLine}{exc}"); - } - } - return answ; - } - - public string CalcRecipe(RecipeModel currRecipe) - { - return mongoController.CalcRecipe(currRecipe); - } - - /// - /// controlla se da il segnale di "microstato" deriva un evento da generare - modalità OFFLINE - /// - /// idx macchina - /// valOut ingresso - /// data-ora evento (server) - /// sequenza dati inviati - /// dati macchina in cache (opzionale, se null fa lookup) - /// - public async Task CheckMicroStatoAsync(string idxMacchina, string valore, DateTime dtEve, string contatore, Dictionary? datiMaccCache = null) - { - // recupero SE IMPIEGATO REDIS i valori del Dictionary della macchina... - Dictionary datiMacc = datiMaccCache ?? await mDatiMacchineAsync(idxMacchina); - - // processing - inputComandoMapo answ = new inputComandoMapo(); - - // SE no trovassi verifico se esista la macchina altrimenti la creo... REDIS compliant - if (!datiMacc.ContainsKey(idxMacchina)) - { - await verificaIdxMacchinaAsync(idxMacchina); - } - - // continuo processing... - string CodArticolo = datiMacc["CodArticolo"]; - if (string.IsNullOrEmpty(CodArticolo)) - { - var allDatiMacch = await IocDbController.DatiMacchineGetAllAsync(); - var recMacc = allDatiMacch.FirstOrDefault(x => x.IdxMacchina == idxMacchina); - if (recMacc != null) - { - CodArticolo = recMacc.CodArticoloA; - } - } - // preparo gestione val ingresso - int? valINT = 0; - int idxTipoEv = 0; - int next_idxMS = 0; - try - { - valINT = int.Parse(valore, NumberStyles.HexNumber); - int idxFamIn = Convert.ToInt32(datiMacc["IdxFamIn"]); - int idxMicroStato = Convert.ToInt32(datiMacc["IdxMicroStato"]); - int valIOB = Convert.ToInt32(valINT); - next_idxMS = idxMicroStato; - // recupero singolo valOut (stringa) x chiave - string todoSMI = await ValoreSmiAsync(idxFamIn, idxMicroStato, valIOB); - // solo se ho trovato un risultato nella tab SMI della famiglia macchina... - if (!string.IsNullOrEmpty(todoSMI) && todoSMI.Contains("_")) - { - // splitto e salvo valori OUT... - string[] valori = todoSMI.Split('_'); - idxTipoEv = Convert.ToInt32(valori[0]); - next_idxMS = Convert.ToInt32(valori[1]); - } - } - catch (Exception exc) - { - Log.Error($"[ChkMiSt_5a] - - Eccezione in recupero riga Trans ingressi | idxMacchina {idxMacchina} | valINT {valINT}:{Environment.NewLine}{exc}", Environment.NewLine, exc, valINT, idxMacchina); - } - - // effettuo update vari SU DB!!! - MicroStatoMacchinaModel newRecMsm = new MicroStatoMacchinaModel() - { - IdxMacchina = idxMacchina, - IdxMicroStato = next_idxMS, - InizioStato = dtEve, - Value = valore - }; - if (idxTipoEv > 0) - { - // preparo record - string valEsteso = string.Format("[{0}] {1}", contatore.PadLeft(3, '0'), valore); - // creo evento - EventListModel newRecEv = new EventListModel() - { - CodArticolo = CodArticolo, - IdxMacchina = idxMacchina, - IdxTipo = idxTipoEv, - InizioStato = dtEve, - MatrOpr = 0, - pallet = "-", - Value = valEsteso - }; - // salva e processa evento + microstato - answ = await scriviRigaEventoAsync(newRecMsm, newRecEv); - } - else - { - if (useFactory) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var iocService = scope.ServiceProvider.GetRequiredService(); - // solo microstato - await iocService.MicroStatoMacchinaUpsertAsync(newRecMsm); - } - else - { - // solo microstato - await IocDbController.MicroStatoMacchinaUpsertAsync(newRecMsm); - } - } - return answ; - } - - public async Task> ConfigGetAllAsync() - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - List? result = new List(); - // cerco in redis... - RedisValue rawData = await redisDb.StringGetAsync(Utils.redisConfKey); - if (!string.IsNullOrEmpty($"{rawData}")) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ConfigGetAllAsync Read from REDIS: {ts.TotalMilliseconds}ms"); - } - else - { - result = await IocDbController.ConfigGetAllAsync(); - //result = await Task.FromResult(SpecDbController.ConfigGetAllAsync()); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(Utils.redisConfKey, rawData, getRandTOut(redisLongTimeCache)); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ConfigGetAllAsync Read from DB: {ts.TotalMilliseconds}ms"); - } - if (result == null) - { - result = new List(); - } - return result; - } - - /// - /// Reset dati cache config - /// - /// - public async Task ConfigResetCache() - { - await redisDb.StringSetAsync(Utils.redisConfKey, ""); - } - - /// - /// Update chiave config - /// - /// - public async Task ConfigUpdate(ConfigModel updRec) - { - return await Task.FromResult(SpecDbController.ConfigUpdate(updRec)); - } - - /// - /// Elenco completo valori DatiMacchine - /// - /// - public async Task> DatiMacchineGetAll() - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisBaseAddr}:TabDatiMacchine:ALL"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.DatiMacchineGetAll()); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"DatiMacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - public async Task> DecNumArtGetFiltAsync(string codArt = "") - { - List result = new(); - string tag = string.IsNullOrEmpty(codArt) ? "ALL" : codArt; - string currKey = $"{Utils.redisDecNumArtKey}:{tag}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - result = await IocDbController.DecNumArtGetFiltAsync(codArt); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - return result; - } - - /// - /// Dispose del connettore ai dati - /// - public void Dispose() - { - // Clear database controller - SpecDbController.Dispose(); - mongoController.Dispose(); - redisConn.Dispose(); - } - - /// - /// Restituisce l'elenco delle date dei dossier x una macchina (se presenti) Impiegata anche - /// cache redis - /// - /// - /// - public async Task> DossierLastByMachAsync(string idxMacchina) - { - List result = new List(); - - var currKey = $"{Utils.redisDossByMacLast}:{idxMacchina}"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - result = await IocDbController.DossGetLastByMaccAsync(idxMacchina); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - return result; - } - - /// - /// Eliminazione di un dossier - /// - /// record dossier da eliminare - /// - public async Task DossiersDeleteRecord(DossierModel selRecord) - { - bool result = false; - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - result = await SpecDbController.DossiersDeleteRecord(selRecord); - // elimino cache redis... - RedisValue pattern = new RedisValue($"{Utils.redisDossByMac}:*"); - bool answ = await RedisFlushPatternAsync(pattern); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"DossiersDeleteRecord | IdxMacchina {selRecord.IdxMacchina} | DtRif {selRecord.DtRif} | IdxODL {selRecord.IdxODL} | {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Elenco ultimi n record DOssiers (che contengono ad esempio "salvataggi" di FLuxLog) dato - /// macchina (ordinato x data registrazione) - /// - /// * = tutte, altrimenti solo x una data macchina - /// Data minima per estrazione records - /// Data Massima per estrazione records - /// Num Max records da recuperare - /// - public async Task> DossiersGetLastFilt(string IdxMacchina, string CodArticolo, DateTime DtStart, DateTime DtEnd, int MaxRec) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisDossByMac}:{IdxMacchina}:{CodArticolo}:{DtStart:yyyyMMddHHmm}:{DtEnd:yyyyMMddHHmm}:{MaxRec}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await SpecDbController.DossiersGetLastFiltAsync(IdxMacchina, CodArticolo, DtStart, DtEnd, MaxRec); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"DossiersGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Inserimento nuovo record dossier - /// - /// - /// - public async Task DossiersInsert(DossierModel currDoss) - { - // aggiorno record sul DB - bool answ = await SpecDbController.DossiersInsert(currDoss); - - return answ; - } - - /// - /// Effettua salvataggio snapshot parametri (con stored) + svuota eventuale cache redis - /// - /// macchina - /// NUm massimo secondi per recuperare dati correnti - /// DataOra riferimento x cui prendere valori antecedenti - /// - public async Task DossiersTakeParamsSnapshotLast(string IdxMacchina, DateTime dtMin, DateTime dtMax) - { - bool answ = false; - await Task.Delay(1); - Log.Info($"Richiesta snapshot per macchina {IdxMacchina} | periodo {dtMin} --> {dtMax}"); - // chiamo stored x salvare parametri - SpecDbController.DossiersTakeParamsSnapshotLast(IdxMacchina, dtMin, dtMax); - // elimino cache redis... - RedisValue pattern = new RedisValue($"{Utils.redisDossByMac}:*"); - answ = await RedisFlushPatternAsync(pattern); - Log.Info($"Svuotata cache dossier | {pattern}"); - return answ; - } - - /// - /// Update valOut dossier - /// - /// - /// - public async Task DossiersUpdateValore(DossierModel currDoss) - { - // aggiorno record sul DB - bool answ = await SpecDbController.DossiersUpdateValore(currDoss); - - return answ; - } - - /// - /// Restitusice elenco aziende - /// - /// - public Task> ElencoAziende() - { - return Task.FromResult(SpecDbController.AnagGruppiAziende()); - } - - /// - /// Restitusice elenco fasi - /// - /// - public Task> ElencoGruppiFase() - { - List result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisAnagGruppi}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - var rawResult = JsonConvert.DeserializeObject>($"{rawData}"); - if (rawResult != null) - { - result = rawResult; - } - readType = "REDIS"; - } - else - { - result = SpecDbController.AnagGruppiFase(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache / 5)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ElencoGruppiFase | Read from {readType}: {ts.TotalMilliseconds}ms"); - return Task.FromResult(result); - } - - public Task> ElencoLink() - { - return Task.FromResult(SpecDbController.ElencoLink()); - } - - /// - /// Aggiunta record EventList - /// - /// - /// - public async Task EvListInsert(EventListModel newRec) - { - return await SpecDbController.EvListInsert(newRec); - } - - /// - /// Imposta in redis la scadenza della pagina x il reload - /// - /// - /// - public DateTime ExpiryReloadParamGet() - { - DateTime dtRif = DateTime.Now; - string currKey = $"{Utils.redisParamPageExp}"; - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - dtRif = JsonConvert.DeserializeObject($"{rawData}"); - } - return dtRif; - } - - /// - /// Imposta in redis la scadenza della pagina x il reload - /// - /// - /// - public bool ExpiryReloadParamSet(DateTime expTime) - { - bool fatto = false; - string currKey = $"{Utils.redisParamPageExp}"; - string rawData = JsonConvert.SerializeObject(expTime); - fatto = redisDb.StringSet(currKey, rawData); - return fatto; - } - - /// - /// Task completo sistemazione dossier quotidiani mancanti - /// - /// - /// - public async Task FixDailyDossierAsync(string idxMacc) - { - string answ = ""; - // verifico se si possa processare, ovvero tab ConfFlux x macchina sia valorizzata... - var confDataMach = await ConfFluxMach(idxMacc); - if (confDataMach.Count > 0) - { - // determino ultima data da processare (inizio oggi, a mezzanotte) - DateTime dtTo = DateTime.Today; - DateTime dtFrom = dtTo; - // determino data di partenza, prima da dossier esistenti - var listaDoss = await DossierLastByMachAsync(idxMacc); - if (listaDoss.Count > 0) - { - // primo giorno DOPO ultima registrazione - dtFrom = listaDoss - .Select(x => x.DtRif) - .OrderByDescending(x => x) - .FirstOrDefault() - .AddDays(1); - } - else - { - // ...o da fluxLog acquisiti... - var listaFL = await FluxLogFirstByMachAsync(idxMacc); - if (listaFL.Count > 0) - { - // giorno successivo a prima registrazione - dtFrom = listaFL - .OrderBy(x => x) - .FirstOrDefault() - .AddDays(1); - } - } - string caller = $"takeFlogSnapshot({idxMacc})"; - DateTime dtStart = dtFrom.Date; - DateTime dtEnd = dtFrom; - // max 10 dossier alla volta (se non configurato diversamente) - int maxAdd = 1; - string confVal = await tryGetConfigAsync("IO_numDossMaxCreate"); - if (!string.IsNullOrEmpty(confVal)) - { - int.TryParse(confVal, out maxAdd); - } - - if (dtStart < dtTo) - { - // verifico di avere almeno 1 dossier da produrre ciclo fino ad esaurire le - // date da processare - while (dtStart < dtTo && maxAdd > 0) - { - // sistemo end - dtEnd = dtStart.AddDays(1); - // effettuo chiamata registrazione snapshot! - answ = await FluxLogSaveSnapshotAsync(idxMacc, dtStart, dtEnd, caller); - // incremento START... - dtStart = dtEnd; - // riduco il numero di chiamate ammesse x singolo task - maxAdd--; - } - // reset cache dossier... - await DossierLastByMachResetAsync(idxMacc); - answ = "OK"; - } - else - { - Log.Warn("FixDailyDossierAsync | NO more to add"); - } - } - else - { - Log.Warn("FixDailyDossierAsync | NO ConfFluxData"); - } - return answ; - } - - public async Task FlushRedisCache() - { - await Task.Delay(1); - RedisValue pattern = Utils.RedValue("*"); - bool answ = await RedisFlushPatternAsync(pattern); - // rileggo vocabolario.,.. - ObjVocabolario = VocabolarioGetAll(); - return answ; - } - - public async Task FlushRedisKey(string redKey) - { - await Task.Delay(1); - RedisValue pattern = Utils.RedValue(redKey); - bool answ = await RedisFlushPatternAsync(pattern); - return answ; - } - - public List FluxLogDtoGetByFlux(string Valore) - { - List answ = new List(); - DossierFluxLogDTO? result = JsonConvert.DeserializeObject(Valore); - if (result != null) - { - if (result.ODL != null) - { - answ = result - .ODL - .OrderBy(x => x.CodFlux) - .ToList(); - // inizializzo SE necessario - foreach (var item in answ) - { - item.ValoreEdit = String.IsNullOrEmpty(item.ValoreEdit) ? item.Valore : item.ValoreEdit; - } - } - } - return answ; - } - - /// - /// Elenco ultimi n record flux log dato macchina e flusso (ordinato x data registrazione) - /// - /// Data massima x eventi - /// Data minima x eventi - /// * = tutte, altrimenti solo x una data macchina - /// *=tutti, altrimenti solo selezionato - /// numero massimo record da restituire - /// - public async Task> FluxLogGetLastFilt(DateTime DtMax, DateTime DtMin, string IdxMacchina, string CodFlux, int MaxRec, double redisCacheSec) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisFluxLogFilt}:{IdxMacchina}:{CodFlux}:{MaxRec}:{DtMax:yyyyMMddHHmm}:{DtMin:yyyyMMddHHmm}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.FluxLogGetLastFilt(DtMax, DtMin, IdxMacchina, CodFlux, MaxRec)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - if (string.IsNullOrEmpty(canCacheParametri)) - { - canCacheParametri = await tryGetConfigAsync("SPEC_ParametriEnableRedisCache"); - } - if (canCacheParametri != "false") - { - redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisCacheSec)); - } - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"FluxLogGetLastFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Restituisce il valOut dell'ODL corrente (ODL deve esserci per gestione contapezzi, senza - /// ODL NO invio/gestione ODL) - /// - /// - /// - public async Task GetCurrOdlAsync(string idxMacchina) - { - string result = ""; - string currKey = $"{Utils.redisOdlCurrByMac}:{idxMacchina}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = $"{rawData}"; - } - else - { - result = await GetCurrOdlByProdAsync(idxMacchina); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - return result; - } - - /// - /// Restituisce il valOut dell'ODL corrente (ODL deve esserci per gestione contapezzi, senza - /// ODL NO invio/gestione ODL) - /// - /// - /// indica se forzare lettura da db (true) o meno - /// - public async Task GetCurrOdlAsync(string idxMacchina, bool forceDb) - { - string answ = ""; - // se ho forceDB leggo dai dati prod... - if (forceDb) - { - var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now, true); - if (datiProd != null) - { - answ = datiProd.IdxOdl.ToString(); - } - } - else - { - answ = await GetCurrOdlAsync(idxMacchina); - } - return answ; - } - - /// - /// Restituisce il valOut dell'ultimo ODL - /// - /// - /// - public async Task GetLastOdlAsync(string idxMacchina) - { - ODLExpModel result = new ODLExpModel(); - string currKey = $"{Utils.redisOdlLastByMac}:{idxMacchina}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject($"{rawData}") ?? new(); - } - else - { - result = await IocDbController.OdlLastByMaccAsync(idxMacchina); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - return result; - } - - /// - /// Effettua calcolo data-ora di riferimento per il server a partire da - /// - /// - /// - /// - public DateTime GetSrvDtEvent(string dtEve, string dtCurr) - { - DateTime dataOraEvento = DateTime.Now; - // 2017.09.14 trimmo eventualmente lo zero finale dalle date SE supera i millisecondi... - dtEve = dtEve.Length > 17 ? dtEve.Substring(0, 17) : dtEve; - dtCurr = dtCurr.Length > 17 ? dtCurr.Substring(0, 17) : dtCurr; - DateTime dtEvento, dtCorrente; - // controllo: se ho valori dt x evento e orario DIVERSI per acquisitore IOB calcolo - // dataOraEvento corretto - if (dtEve != dtCurr) - { - Int64 delta = 0; - try - { - // se ho meno decimali x evento rispetto dtCorrente... - if (dtEve.Length < dtCurr.Length) - { - dtEve = dtEve.PadRight(dtCurr.Length, '0'); - } - delta = Convert.ToInt64(dtCurr) - Convert.ToInt64(dtEve); - // se meno di 60'000 ms ... - if (delta < 59999) - { - dataOraEvento = dataOraEvento.AddMilliseconds(-delta); - } - else - { - // in questo caso elimino i MS dalle stringhe e converto i datetime.... - CultureInfo provider = CultureInfo.InvariantCulture; - string format = "yyyyMMddHHmmssfff"; - dtEvento = DateTime.ParseExact(dtEve, format, provider); - dtCorrente = DateTime.ParseExact(dtCurr, format, provider); - TimeSpan deltaTS = dtCorrente.Subtract(dtEvento); - dataOraEvento = dataOraEvento.Add(-deltaTS); - } - } - catch (Exception exc) - { - Log.Error($"getSrvDtEvent | Errore calcolo ora corrente da IOB remoto | dtEve: {dtEve} | dtCurr: {dtCurr}{Environment.NewLine}" + - $"{exc}"); - } - } - - return dataOraEvento; - } - - /// - /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato - /// - /// - /// - public async Task> GetTask2ExeMacchinaAsync(string idxMacchina) - { - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - return await RedisGetHashDictAsync(currHash); - } - - /// - /// Init ricetta - /// - /// - /// - /// - /// - public RecipeModel InitRecipe(string confPath, int idxPODL, Dictionary CalcArgs) - { - return mongoController.InitRecipe(confPath, idxPODL, CalcArgs); - } - - /// - /// Restituisce il valOut booleano se la macchina sia abilitata all'input - /// - /// - /// - public async Task IobInsEnabAsync(string idxMacchina) - { - var key = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); - - string? val = await redisDb.HashGetAsync(key, "insEnabled"); - - if (val == null) - { - var data = await ResetDatiMacchinaAsync(idxMacchina); - data.TryGetValue("insEnabled", out val); - } - - return val != null && (val == "1" || val.ToLower() == "true"); - } - - /// - /// Restituisce il valOut booleano se la macchina sia master - /// - /// - /// - public async Task IobIsMasterAsync(string idxMacchina) - { - var key = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); - - // 1. Tentativo ottimizzato: leggiamo solo il campo che ci serve - // Supponendo che tu usi StackExchange.Redis direttamente o un wrapper - string? val = await redisDb.HashGetAsync(key, "Master"); - - // 2. Se non c'è in cache, carichiamo/resettiamo tutto - if (val == null) - { - //var data = ResetDatiMacchina(idxMacchina); - var data = await ResetDatiMacchinaAsync(idxMacchina); - data.TryGetValue("Master", out val); - } - - // 3. Parsing sicuro - return val != null && (val == "1" || val.ToLower() == "true"); - } - - /// - /// Restituisce il valOut booleano se la macchina sia abilitata all'inserimento COMPLETO nel - /// Signal Log - /// - /// - /// - public async Task IobSLogEnabAsync(string idxMacchina) - { - bool answ = false; - // ORA recupero da memoria redis... - try - { - var currHash = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); - RedisValue rawData = await redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled"); - // se è vuoto... leggo da DB e popolo! - if (!rawData.HasValue) - { - await ResetDatiMacchinaAsync(idxMacchina); - // riprovo - rawData = await redisDb.HashGetAsync(currHash, (RedisValue)"sLogEnabled"); - } - - // provo conversione - bool.TryParse($"{rawData}", out answ); - } - catch (Exception exc) - { - Log.Error($"Errore IobSLogEnabAsync | idxMacchina {idxMacchina}:{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// - /// idxMacc odl da cercare - /// - public async Task> ListGiacenze(int IdxOdl) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisGiacenzaList}:{IdxOdl}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.ListGiacenze(IdxOdl)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ListGiacenze | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - public async Task> ListValuesFilt(string tabName, string fieldName) - { - List resultList = new List(); - string tag = ""; - tag += string.IsNullOrEmpty(tabName) ? "" : $"{tabName}:"; - tag += string.IsNullOrEmpty(fieldName) ? "" : $"{fieldName}:"; - var currKey = $"{Utils.redisConfFlux}:{tag}"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - resultList = await IocDbController.ListValuesFiltAsync(tabName, fieldName); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(resultList); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (resultList == null) - { - resultList = new(); - } - return resultList; - } - - /// - /// Elenco completo valori Macchine 2 Slave - /// - /// - public async Task> Macchine2SlaveGetAllAsync() - { - List? result = new List(); - string currKey = $"{Utils.redisBaseAddr}:M2STab"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - } - else - { - if (useFactory) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var mtcService = scope.ServiceProvider.GetRequiredService(); - result = await mtcService.Macchine2SlaveAsync(); - } - else - { - result = await IocDbController.Macchine2SlaveAsync(); - } - - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); - } - if (result == null) - { - result = new List(); - } - return result; - } - - /// - /// Elenco di tutte le macchine gestite - /// - /// - /// - public async Task> MacchineGetFilt(string codGruppo) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string keyGrp = codGruppo != "*" ? codGruppo : "ALL"; - string currKey = $"{Utils.redisMacList}:{keyGrp}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.MacchineGetFilt(codGruppo)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"MacchineGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Verifica se la macchina abbia un codice PATH ricette associato - /// - /// - /// - public async Task MacchineRecipeArchive(string idxMacchina) - { - string? result = ""; - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisMacRecipePath}:{idxMacchina}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject($"{rawData}"); - readType = "REDIS"; - } - else - { - //recupero elenco macchine... - var machineList = await MacchineGetFilt("*"); - var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); - result = currMach != null ? currMach.RecipeArchivePath : null; - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"MacchineRecipeArchive | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result ?? ""; - } - - /// - /// Verifica se la macchina abbia un codice CONF ricetta associato - /// - /// - /// - public async Task MacchineRecipeConf(string idxMacchina) - { - string? result = ""; - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisMacRecipeConf}:{idxMacchina}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject($"{rawData}"); - readType = "REDIS"; - } - else - { - //recupero elenco macchine... - var machineList = await MacchineGetFilt("*"); - var currMach = machineList.Where(x => x.IdxMacchina == idxMacchina).FirstOrDefault(); - result = currMach != null ? currMach.RecipePath : null; - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"MacchineRecipeConf | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result ?? ""; - } - - /// - /// Elenco idxMacc Macchine che abbiano dati FLuxLog, nel periodo indicato - /// - /// - /// - /// - public async Task> MacchineWithFlux(DateTime dtStart, DateTime dtEnd) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisMacByFlux}:{dtStart:yyyyMMddHHmm}:{dtEnd:yyyyMMddHHmm}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await SpecDbController.MacchineWithFlux(dtStart, dtEnd); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"MacchineWithFlux | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) - /// - /// - /// - public List MachineParamList(string idxMacchina) - { - // setup parametri costanti - string source = "NA"; - Stopwatch sw = new Stopwatch(); - sw.Start(); - List? result = new List(); - // cerco in _redisConn... - var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue && rawData.Length() > 2) - { - var rawVal = JsonConvert.DeserializeObject>($"{rawData}"); - // ordino! - result = rawVal - .OrderBy(x => x.displOrdinal) - .ThenBy(x => x.description) - .ToList(); - source = "REDIS"; - } - if (result == null) - { - result = new List(); - source = "NONE"; - } - sw.Stop(); - Log.Debug($"MachineParamList | {source} | {sw.Elapsed.TotalMilliseconds}ms"); - return result; - } - - /// - /// Lista parametri correnti (ObjItemDTO) della macchina (ex getCurrObjItems) - /// - /// - /// - public async Task> MachineParamListAsync(string idxMacchina) - { - // setup parametri costanti - string source = "NA"; - Stopwatch sw = new Stopwatch(); - sw.Start(); - List? result = new List(); - // cerco in _redisConn... - var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue && rawData.Length() > 2) - { - var rawVal = JsonConvert.DeserializeObject>($"{rawData}"); - // ordino! - result = rawVal - .OrderBy(x => x.displOrdinal) - .ThenBy(x => x.description) - .ToList(); - source = "REDIS"; - } - if (result == null) - { - result = new List(); - source = "NONE"; - } - sw.Stop(); - Log.Debug($"MachineParamListAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); - return result; - } - - /// - /// Lista parametri correnti che necessitano di write della macchina (ex getCurrObjItems) - /// - /// - /// - public async Task> MachineParamListPendingWriteAsync(string idxMacchina) - { - List? result = new List(); - // recupero tutti i parametri - var allData = await MachineParamListAsync(idxMacchina); - result = allData.Where(x => x.writable && !string.IsNullOrEmpty(x.reqValue)).ToList(); - return result; - } - - /// - /// Esegue aggiornamento MachineParamList (ex CurrObjItems) Async - /// - /// - /// - /// - public async Task MachineParamListSetAsync(string idxMacchina, List currData) - { - string serVal = JsonConvert.SerializeObject(currData); - var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); - bool fatto = await redisDb.StringSetAsync(currKey, serVal); - return fatto; - } - - /// - /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) - /// - /// - /// - /// - public async Task MachineParamUpsertAsync(string idxMacchina, List innovations) - { - bool answ = false; - if (innovations != null) - { - Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); - // leggo i valori attuali... - List actValues = await MachineParamListAsync(idxMacchina); - // per ogni valOut passatomi faccio insert o update rispetto elenco valori correnti - // in REDIS - foreach (var item in actValues) - { - // cerco nelle innovazioni SE CI SIA il valOut... - var trovato = innovations.Find(obj => obj.uid == item.uid); - // se non trovato nelle innovazioni... - if (trovato == null) - { - // lo ri-aggiungo x non perderlo - innovations.Add(item); - // in base a value (value == null) uso debug/info - if (string.IsNullOrEmpty(item.value)) - { - Log.Debug($"idxMacchina: {idxMacchina} | innovations | add | item.uid: {item.uid} | item.value: {item.value}"); - } - else - { - Log.Info($"idxMacchina: {idxMacchina} | innovations | add | item.uid: {item.uid} | item.value: {item.value}"); - } - } - // altrimenti aggiorno campo (non trasmesso) name e tengo il resto... - else - { - trovato.name = item.name; - Log.Debug($"idxMacchina: {idxMacchina} | innovations | update | item.uid: {item.uid} | item.value: {item.value} --> {trovato.value} "); - } - } - // serializzo e salvo - answ = await MachineParamListSetAsync(idxMacchina, innovations); - } - return answ; - } - - /// - /// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato - /// - /// - /// - public async Task> mDatiMacchineAsync(string idxMacchina) - { - // hard coded dimensione vettore DatiMacchine - Dictionary answ = new Dictionary(); - // ORA recupero da memoria redis... - try - { - var currHash = Utils.RedKeyDatiMacc(idxMacchina, MpIoNS); - answ = await RedisGetHashDictAsync(currHash); - // se è vuoto... leggo da DB e popolo! - if (answ.Count == 0) - { - answ = await ResetDatiMacchinaAsync(idxMacchina); - } - } - catch (Exception exc) - { - Log.Error($"Errore in compilazione dati Macchine x Redis - idxMacchina {idxMacchina}:{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato - /// - /// - /// - public Dictionary mOptParMacchina(string idxMacchina) - { - // hard coded dimensione vettore DatiMacchine - Dictionary answ = new Dictionary(); - // ORA recupero da memoria redis... - RedisKey currHash = Utils.RedKeyOptPar(idxMacchina, MpIoNS); - answ = RedisGetHashDict(currHash); - return answ; - } - - /// - /// Restitusice elenco KVP dei TASK SALVATI (da passare a IOB-WIN) per l'impianto indicato - /// - /// - /// - public Dictionary mSavedTaskMacchina(string idxMacchina) - { - // hard coded dimensione vettore DatiMacchine - Dictionary answ = new Dictionary(); - // ORA recupero da memoria redis... - try - { - RedisKey currHash = Utils.RedKeySavedTask2ExeMacc(idxMacchina); - answ = RedisGetHashDict(currHash); - } - catch (Exception exc) - { - Log.Info($"Errore in recupero dati SAVED TASK x Redis mSavedTaskMacchina | idxMacchina {idxMacchina}{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// Elenco da tabella MappaStatoExplModel - /// - /// - public async Task> MseGetAllAsync(bool forceDb = false) - { - Stopwatch sw = new Stopwatch(); - string source = "DB"; - sw.Start(); - List? result = new List(); - // cerco in _redisConn... - RedisValue rawData = await redisDb.StringGetAsync(Constants.redisMseKey); - if (rawData.HasValue && !forceDb) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - source = "REDIS"; - } - else - { - result = await IocDbController.MseGetAllAsync(maxAge); - // serializzp e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(Constants.redisMseKey, rawData, getRandTOut(redisShortTimeCache / 2)); - } - if (result == null) - { - result = new List(); - } - sw.Stop(); - Log.Debug($"MseGetAllAsync | {source} | {sw.Elapsed.TotalMilliseconds}ms"); - return result; - } - - /// - /// Restitusice elenco KVP (async) per evitare blocchi quando chiamato da metodi asincroni - /// - /// - /// - public async Task[]> mTabMSMIAsync(string idxMacchina) - { - KeyValuePair[] answ = new KeyValuePair[1]; - answ[0] = new KeyValuePair("0", "0"); - try - { - var currHash = Utils.RedKeyMsmi(idxMacchina); - answ = await RedisGetHashAsync(currHash); - if (answ == null || answ.Length == 0) - { - answ = await resetMSMIAsync(idxMacchina); - } - } - catch (Exception exc) - { - Log.Error($"Errore in compilazione Tabella Multi State Machine Ingressi x Redis:{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato - /// - /// - /// - public Dictionary mTaskMacchina(string idxMacchina) - { - // hard coded dimensione vettore DatiMacchine - Dictionary answ = new Dictionary(); - // ORA recupero da memoria redis... - try - { - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - answ = RedisGetHashDict(currHash); - } - catch (Exception exc) - { - Log.Error(string.Format("Errore in mTaskMacchina | idxMacchina {2}:{0}{1}", Environment.NewLine, exc, idxMacchina)); - } - return answ; - } - - /// - /// Restitusice elenco KVP dei TASK (da passare a IOB-WIN) per l'impianto indicato - /// - /// - /// - public async Task> mTaskMacchinaAsync(string idxMacchina) - { - // hard coded dimensione vettore DatiMacchine - Dictionary answ = new Dictionary(); - // ORA recupero da memoria redis... - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - answ = await RedisGetHashDictAsync(currHash); - return answ; - } - - /// - /// Generazione autoOdl - /// - /// - /// - /// - /// - /// - public async Task OdlAutoDayGenAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo) - { - var result = await IocDbController.OdlAutoDayGenAsync(idxMacchina, dataInizio, dataFine, codArticolo); - return result; - } - - /// - /// Generazione autoOdl - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task OdlAutoDayGenFullAsync(string idxMacchina, DateTime dataInizio, DateTime dataFine, string codArticolo, int? pzPODL, int? pzPallet, string? keyRichiesta, int? tcAssegnato, string? codGruppo, bool flgCreaPODL, bool flgCheckTC) - { - var result = await IocDbController.OdlAutoDayGenFullAsync(idxMacchina, dataInizio, dataFine, codArticolo, pzPODL, pzPallet, keyRichiesta, tcAssegnato, codGruppo, flgCreaPODL, flgCheckTC); - return result; - } - - /// - /// Elenco ODL dato batch selezionato - /// - /// Batch richiesto - /// - public async Task> OdlByBatch(string BatchSel) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = Utils.redisOdlByBatch; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.OdlByBatch(BatchSel)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"OdlByBatch | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// ODL da chiave - /// - /// - /// - public ODLExpModel OdlByKey(int IdxOdl) - { - ODLExpModel? result = new ODLExpModel(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - result = SpecDbController.OdlByKey(IdxOdl); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"OdlByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Effettua chiusura dell'ODL indicato, andand - /// - /// idx odl da chiudere - /// idx macchina - /// matricola operatore - /// indica se confermare i pezzi priam di chiudere ODL - public async Task ODLClose(int idxOdl, string idxMacchina, int matrOpr, bool confPezzi) - { - bool fatto = false; - await Task.Delay(1); - // recupero dati x conf modalità conferma - var configData = await ConfigGetAllAsync(); - if (configData != null) - { - bool confRett = false; - var currRec = configData.FirstOrDefault(x => x.Chiave == "confRett"); - if (currRec != null) - { - bool.TryParse(currRec.Valore, out confRett); - } - int modoConfProd = 0; - currRec = configData.FirstOrDefault(x => x.Chiave == "modoConfProd"); - if (currRec != null) - { - int.TryParse(currRec.Valore, out modoConfProd); - } - // chiamo metodo conferma! - fatto = await SpecDbController.ODLClose(idxOdl, idxMacchina, matrOpr, confPezzi, confRett, modoConfProd); - } - - return fatto; - } - - public async Task OdlCurrByMaccAsync(string IdxMacchina) - { - ODLExpModel result = new(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisOdlList}:Current:{IdxMacchina}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject($"{rawData}") ?? new(); - readType = "REDIS"; - } - else - { - result = await IocDbController.OdlCurrByMaccAsync(IdxMacchina); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); - } - if (result == null) - { - result = new(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Trace($"OdlCurrByMaccAsync | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Record ODL da chaive - /// - /// - public async Task OdlGetByKey(int IdxOdl) - { - await Task.Delay(1); - var dbResult = await SpecDbController.OdlGetByKey(IdxOdl); - return dbResult; - } - - /// - /// ODL correnti (tutti) - /// - /// - /// - public List OdlGetCurrent() - { - List? dbResult = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisOdlCurrByMac}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - try - { - dbResult = JsonConvert.DeserializeObject>($"{rawData}"); - } - catch - { } - readType = "REDIS"; - } - else - { - dbResult = SpecDbController.OdlGetCurrent().Select(x => x.IdxMacchina).Distinct().ToList(); - rawData = JsonConvert.SerializeObject(dbResult); - redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(3)); - } - if (dbResult == null) - { - dbResult = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"OdlGetCurrent | Read from {readType}: {ts.TotalMilliseconds}ms"); - - return dbResult; - } - - /// - /// elenco TUTTI gli ODL - /// - /// - /// - public List OdlListAll() - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - result = SpecDbController.OdlListAll(); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"OdlListAll | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Elenco ODL filtrati x stato, articolo, KeyRich (che contiene stato) - /// - /// Stato ODL: true=in corso/completato - /// Cod articolo - /// KeyRich (parziale) da cercare (es cod stato x yacht) - /// Reparto selezionato - /// Macchina selezionata - /// Data inizio - /// Data fine - /// - public async Task> OdlListGetFilt(bool inCorso, string codArt, string keyRichPart, string Reparto, string IdxMacchina, DateTime startDate, DateTime endDate) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisOdlList}:{inCorso}:{codArt}:{keyRichPart}:{Reparto}:{IdxMacchina}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.ListODLFilt(inCorso, codArt, keyRichPart, Reparto, IdxMacchina, startDate, endDate)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"OdlListGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Elenco di tutti i parametri filtrati x macchina - /// - /// * = tutte, altrimenti solo x una data macchina - /// - public async Task> ParametriGetFilt(string IdxMacchina) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisFluxByMac}:{IdxMacchina}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.ParametriGetFilt(IdxMacchina)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"ParametriGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Eliminazione record selezionato - /// - /// - /// - public async Task POdlDeleteRecord(PODLExpModel currRec) - { - var dbResult = await SpecDbController.PODLDeleteRecord(currRec); - // elimino cache redis... - await POdlFlushCache(); - await Task.Delay(1); - return dbResult; - } - - /// - /// Avvio fase setup per il record selezionato - /// - /// - /// - public async Task POdlDoSetup(PODLExpModel currRec) - { - var dbResult = await SpecDbController.PODL_startSetup(currRec, 0, 1, 1, "", DateTime.Now); - // elimino cache redis... - await POdlFlushCache(); - await Task.Delay(1); - return dbResult; - } - - /// - /// Recupero PODL da chiave - /// - /// - /// - public async Task POdlGetByKey(int idxPODL) - { - PODLModel result = new PODLModel(); - if (idxPODL != 0) - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisPOdlByPOdl}:{idxPODL}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - var rawResult = JsonConvert.DeserializeObject($"{rawData}"); - if (rawResult != null) - { - result = rawResult; - readType = "REDIS"; - } - } - else - { - result = await SpecDbController.PODL_getByKey(idxPODL); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new PODLModel(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Trace($"POdlGetByKey | Read from {readType}: {ts.TotalMilliseconds}ms"); - } - else - { - Log.Debug("Errore IdxPODL = 0"); - } - return result; - } - - public async Task> POdlGetByMaccArtAsync(string idxMacchina, string codArticolo, string codGruppo, bool onlyFree) - { - List result = new List(); - - var currKey = $"{Utils.redisPOdlByMaccArt}:{idxMacchina}"; - if (!string.IsNullOrEmpty(codArticolo)) - { - currKey += $":A{codArticolo}"; - } - if (!string.IsNullOrEmpty(codGruppo)) - { - currKey += $":G{codGruppo}"; - } - currKey += onlyFree ? $":FREE" : ":ALL"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - result = await IocDbController.POdlGetByMaccArtAsync(idxMacchina, codArticolo, codGruppo, onlyFree); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - return result; - } - - /// - /// Recupero PODL da IdxODL - /// - /// - /// - public PODLModel POdlGetByOdl(int idxODL) - { - PODLModel result = new PODLModel(); - if (idxODL != 0) - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisPOdlByOdl}:{idxODL}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - var rawResult = JsonConvert.DeserializeObject($"{rawData}"); - if (rawResult != null) - { - result = rawResult; - } - readType = "REDIS"; - } - else - { - result = SpecDbController.PODL_getByOdl(idxODL); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new PODLModel(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Trace($"POdlGetByOdl | Read from {readType}: {ts.TotalMilliseconds}ms"); - } - else - { - Log.Debug("Errore IdxODL = 0"); - } - return result; - } - - /// - /// Elenco PODL non avviati filtrati x articolo, KeyRich (che contiene stato) - /// - /// Solo lanciati (1) o ancora disponibili (0) - /// KeyRich (parziale) da cercare (es cod stato x yacht) - /// Macchina - /// Gruppo - /// Data inizio - /// Data fine - /// - public async Task> POdlListGetFilt(bool lanciato, string keyRichPart, string idxMacchina, string codGruppo, DateTime startDate, DateTime endDate) - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisPOdlList}:{codGruppo}:{idxMacchina}:{keyRichPart}:{lanciato}:{startDate:yyyyMMdd_HHmmss}:{endDate:yyyyMMdd_HHmmss}"; - // cerco in redis dato valOut sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = await Task.FromResult(SpecDbController.ListPODLFilt(lanciato, keyRichPart, idxMacchina, codGruppo, startDate, endDate)); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, TimeSpan.FromSeconds(redisShortTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"POdlListGetFilt | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Aggiornamento record selezionato - /// - /// - /// - public async Task POdlUpdateRecord(PODLModel currRec) - { - var dbResult = await SpecDbController.PODLUpdateRecord(currRec); - // elimino cache redis... - await POdlFlushCache(); - return dbResult; - } - - /// - /// Processa registrazione FL da IOB - /// - /// - /// - /// - /// - /// - /// - /// - /// - public async Task ProcessFluxLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, bool disabKA) - { - // se non vietato... - if (!disabKA) - { - // scrivo keep alive!!! (se necessario, altrimenti è in cache...) - await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); - } - - string answ = ""; - DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); - // inizio processing vero e proprio INPUT... - - if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(valore)) - { - string errore = "processFluxLog | Errore: parametri macchina/valOut vuoti"; - Log.Error(errore); - answ = errore; - } - else - { - FluxLogModel newRec = new FluxLogModel() - { - IdxMacchina = idxMacchina, - dtEvento = dataOraEvento, - CodFlux = flux, - Valore = valore, - Cnt = contatore - }; - await IocDbController.FluxLogInsertAsync(newRec); - // 2022.06.06 salvo su redis il valOut ULTIMO del flux x recupero rapido ultimo valOut - var currKey = Utils.RedKeyLastFLog(idxMacchina, flux, MpIoNS); - // 10 min cache max... - await redisDb.StringSetAsync(currKey, valore, TimeSpan.FromMinutes(10)); - // registro in risposta che è andato tutto bene... - answ = "OK"; - } - - return answ; - } - - /// - /// Validazione preliminare valori input - /// - /// - /// - /// - /// - /// - private bool ValidateinputParams(string idxMacchina, string valore, string dtEve, string dtCurr) - { - bool isValid = false; - // preparo stringa valori correnti - string currVals = $"idxMacchina: {idxMacchina} | valOut: {valore} | dtEve: {dtEve} | dtCurr:{dtCurr}"; - if (dtEve == null || dtCurr == null) - { - Log.Warn($"procInput: null found | {currVals}"); - } - else if (dtEve.Length < 17 || dtCurr.Length < 17) - { - Log.Info($"procInput: invalid data | {currVals}"); - } - else if (string.IsNullOrEmpty(idxMacchina)) - { - Log.Info($"procInput: missing IdxMacchina | {currVals}"); - } - else if (string.IsNullOrEmpty(valore)) - { - Log.Info($"procInput: missing valOut | {currVals}"); - } - else - { - isValid = true; - } - return isValid; - } - - /// - /// Calcola dataora evento da info dt ricevute - /// - /// - /// - /// - private DateTime ParseEventTime(string dtEve, string dtCurr) - { - DateTime dataOraEvento = DateTime.Now; - - // fix formato dataora in ingresso - string stdEve = dtFormStd(dtEve); - string stdCurr = dtFormStd(dtCurr); - - // 2. Se le stringhe normalizzate coincidono, skip dei calcoli - if (stdEve != stdCurr) - { - // 3. Parsing sicuro - if (DateTime.TryParseExact(stdEve, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtEvento) && - DateTime.TryParseExact(stdCurr, dtFormat, ciProvider, DateTimeStyles.None, out DateTime dtCorrente)) - { - TimeSpan diff = dtCorrente - dtEvento; - double ms = Math.Abs(diff.TotalMilliseconds); - - // 4. Classificazione delta con switch expression (più leggibile e performante) - string deltaClass = ms switch - { - <= 10 => "0-10 ms", - <= 100 => "10-100 ms", - <= 1000 => "100-1000 ms", - <= 10000 => "1-10 sec", - _ => "> 10 sec" - }; - - Log.Debug($"Correzione delta {deltaClass}"); - - // 5. Correzione data/ora server: sottrao lo scarto calcolato - dataOraEvento = dataOraEvento.Subtract(diff); - } - else - { - Log.Error($"Errore parsing date: {stdEve} | {stdCurr}"); - } - } - return dataOraEvento; - } - - /// - /// Processa input da IOB eventualmente registrando i segnali inviati - /// - /// - /// - /// - /// - /// - /// - public async Task ProcessInputAsync(string idxMacchina, string valore, string dtEve, string dtCurr, string contatore) - { - string answ = ""; - if (ValidateinputParams(idxMacchina, valore, dtEve, dtCurr)) - { - DateTime dataOraEvento = ParseEventTime(dtEve, dtCurr); - - // se abilitato registro evento sul DB - if (await IobSLogEnabAsync(idxMacchina)) - { - int cntVal = 0; - int.TryParse(contatore, out cntVal); - await saveSigLogAsync(idxMacchina, valore, dataOraEvento, cntVal); - } - // continuo col resto - try - { - // scrivo keep alive!!! (se necessario, altrimenti è in cache...) - await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); - // Cache dati macchina per evitare lookup ridondanti - Dictionary datiMacc = await mDatiMacchineAsync(idxMacchina); - // verifico se sia una macchina MULTI.... - if (isMulti(idxMacchina, datiMacc)) - { - // inizio preprocessing - string newVal = ""; - // processo OGNI macchina a stati dell'impianto... (KEY:IdxMacchina / IdxMacchina#qualcosa, Val = IdxFamIn) - foreach (var item in await mTabMSMIAsync(idxMacchina)) - { - newVal = preProcInput(item.Key, valore, datiMacc); - // ora processo e salvo il valOut del microstato... - // INTERNAMENTE gestisce i casi DB/REDIS secondo necessità - await CheckMicroStatoAsync(item.Key, newVal, dataOraEvento, contatore, datiMacc); - } - } - else - { - // ora processo e salvo il valOut del microstato... INTERNAMENTE - // gestisce i casi DB/REDIS secondo necessità - await CheckMicroStatoAsync(idxMacchina, valore, dataOraEvento, contatore, datiMacc); - } - // forzo RESET dati macchina... - await ResetDatiMacchinaAsync(idxMacchina); - // registro in risposta che è andato tutto bene... - answ = "OK"; - } - catch (Exception exc) - { - string errore = $"Errore: {Environment.NewLine}{exc}"; - Log.Error(errore); - answ = errore; - } - } - return answ; - } - - /// - /// Processa registrazione UserLog da IOB - /// - /// Macchina - /// Flusso: DI/RC/RC - /// valOut = note/valString - /// data evento - /// data corrente - /// contatore invio - /// Matricola Operatore - /// label = causale scarto / tagCode - /// valNum = esitoOk (0/1) / Quantità di scarto associata - /// - public async Task ProcessUserLogAsync(string idxMacchina, string flux, string valore, string dtEve, string dtCurr, int contatore, int matrOpr, string label, int valNum) - { - // scrivo keep alive!!! (se necessario, altrimenti è in cache...) - await ScriviKeepAliveAsync(idxMacchina, DateTime.Now); - // 2017.09.14 trimmo eventualmente lo zero finale dalle date SE supera i millisecondi... - dtEve = dtEve.Length > 17 ? dtEve.Substring(0, 17) : dtEve; - dtCurr = dtCurr.Length > 17 ? dtCurr.Substring(0, 17) : dtCurr; - - string answ = ""; - DateTime dataOraEvento = GetSrvDtEvent(dtEve, dtCurr); - - // inizio processing vero e proprio INPUT... - if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(flux)) - { - string errore = "processFluxLog | Errore: parametri macchina/flux vuoti"; - Log.Error(errore); - answ = errore; - } - else - { - // in base al flusso decido dove e cosa scrivere... - switch (flux) - { - case "DI": - RegistroDichiarazioniModel recDich = new RegistroDichiarazioniModel() - { - IdxMacchina = idxMacchina, - DtRec = dataOraEvento, - MatrOpr = matrOpr, - ValString = valore, - TagCode = label - }; - await IocDbController.RegDichiarInsertAsync(recDich); - break; - - case "RC": - bool esitoOk = valNum != 0; - await IocDbController.RegControlliInsertAsync(idxMacchina, matrOpr, esitoOk, valore, dataOraEvento); - break; - - case "RS": - RegistroScartiModel recSca = new RegistroScartiModel() - { - IdxMacchina = idxMacchina, - DataOra = dataOraEvento, - Causale = label, - Qta = valNum, - Note = valore, - MatrOpr = matrOpr - }; - await IocDbController.RegScartiInsertAsync(recSca); - break; - - default: - break; - } - // registro in risposta che è andato tutto bene... - answ = "OK"; - } - return answ; - } - - /// - /// Restituisce il contapezzi salvato per la macchina - /// - /// - /// - public async Task pzCounter(string idxMacchina) - { - int answ = -1; - try - { - var currKey = Utils.RedKeyPzCount(idxMacchina, MpIoNS); - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - int.TryParse(rawData, out answ); - } - else - { - answ = await PzCounterTcAsync(idxMacchina); - // salvo in _redisConn... - await redisDb.StringSetAsync(currKey, answ.ToString(), TimeSpan.FromSeconds(1)); - } - } - catch (Exception exc) - { - Log.Error($"Eccezione in pzCounter{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// Restituisce il contapezzi come CONTEGGIO da TCRilevati per la macchina - ASYNC - /// - /// - /// - public async Task PzCounterTcAsync(string idxMacchina) - { - int answ = -1; - DateTime dataRif = DateTime.Now; - var datiProd = await StatoProdMacchinaAsync(idxMacchina, dataRif); - if (datiProd != null) - { - answ = datiProd.PzTotODL; - } - return answ; - } - - /// - /// Ricerca ricetta su MongoDB dato PODL - /// - /// - /// - public async Task RecipeGetByPODL(int idxPODL) - { - RecipeModel? result = null; - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "MongoDB"; - result = await mongoController.RecipeGetByPODL(idxPODL); - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"RecipeGetByPODL | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } - - /// - /// Salva ricetta su MongoDB - /// - /// - /// - public async Task RecipeSetByPODL(RecipeModel currRecord) - { - bool answ = false; - answ = await mongoController.RecipeSetByPODL(currRecord); - if (answ) - { - await POdlFlushCache(); - } - return answ; - } - - /// - /// Effettua conteggio chaivi REDIS dato pattern ricerca - /// - /// - /// - public int RedisCountKey(string keyPattern) - { - int num = 0; - keyPattern = (string.IsNullOrEmpty(keyPattern) ? "**" : keyPattern); - try - { - var listEndpoints = redisConnAdmin.GetEndPoints(); - foreach (var endPoint in listEndpoints) - { - var server = redisConnAdmin.GetServer(endPoint); - foreach (RedisKey item in server.Keys(pattern: keyPattern, database: redisDb.Database, pageSize: 250, cursor: 0L)) - { - num++; - } - } - } - catch (Exception arg) - { - Log.Error($"Eccezione in RedisCountKey{Environment.NewLine}{arg}"); - } - - return num; - } - - /// - /// Esegue eliminazione memoria redis keyVal - /// - /// - /// - public bool RedisDelKey(string keyVal) - { - bool answ = redisDb.KeyDelete((RedisKey)keyVal); -#if false - var listEndpoints = redisConnAdmin.GetEndPoints(); - foreach (var endPoint in listEndpoints) - { - var server = redisConnAdmin.GetServer(endPoint); - if (server != null) - { - redisDb.KeyDelete((RedisKey)keyVal); - answ = true; - } - } -#endif - return answ; - } - - /// - /// Esegue eliminazione memoria redis keyVal - /// - /// - /// - public async Task RedisDelKeyAsync(RedisKey keyVal) - { - return await redisDb.KeyDeleteAsync(keyVal); - } - - /// - /// Esegue flush memoria redis dato keyVal - /// - /// - /// - public bool RedisFlushPattern(string pattern) - { - bool answ = false; - var listEndpoints = redisConnAdmin.GetEndPoints(); - foreach (var endPoint in listEndpoints) - { - var server = redisConnAdmin.GetServer(endPoint); - if (server != null) - { - var keyList = server.Keys(redisDb.Database, pattern); - foreach (var item in keyList) - { - redisDb.KeyDelete(item); - } - answ = true; - } - } - return answ; - } - - /// - /// Esegue flush memoria redis dato keyVal, async - /// - /// - /// - public async Task RedisFlushPatternAsync(RedisValue pattern) - { - Log.Debug($"Richiesta flush pattern: {pattern}"); - - // 1. Target ONLY master (le replica sono in read-only) - var master = redisConnAdmin.GetEndPoints() - .Where(ep => redisConnAdmin.GetServer(ep).IsConnected && !redisConnAdmin.GetServer(ep).IsReplica) - .FirstOrDefault(); - - if (master == null) - { - Log.Warn($"Nessun master Redis raggiungibile per il pattern {pattern}"); - return false; - } - - // 2. Flush intero DB se richiesto - if (pattern.ToString() == "*") - { - Log.Debug($"Full DB reset da pattern {pattern}"); - if (master != null) - { - redisConnAdmin.GetServer(master).FlushDatabase(redisDb.Database); - Log.Info($"Flush database {redisDb.Database} completato"); - } - return true; - } - // altrimenti faccio ciclo! - var server = redisConnAdmin.GetServer(master); - var db = redisConnAdmin.GetDatabase(redisDb.Database); - const int batchSize = 500; - var batch = new List(batchSize); - int deletedCount = 0; - - try - { - // KeysAsync usa SCAN automaticamente quando i risultati sono grandi - await foreach (var key in server.KeysAsync( - database: redisDb.Database, - pattern: pattern.ToString(), - pageSize: batchSize)) - { - batch.Add(key); - if (batch.Count >= batchSize) - { - // Esecuzione batch in parallelo controllato - await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); - batch.Clear(); - deletedCount += batchSize; - } - } - - // Restanti - if (batch.Count > 0) - { - await Task.WhenAll(batch.Select(k => db.KeyDeleteAsync(k))); - deletedCount += batch.Count; - } - - Log.Info("Flush pattern {Pattern}: eliminate {Count} chiavi", pattern, deletedCount); - return true; - } - catch (RedisConnectionException ex) - { - Log.Error(ex, "Connessione Redis persa durante il flush di {pattern}", pattern); - return false; - } - catch (Exception ex) - { - Log.Error(ex, "Errore imprevisto nel flush pattern {pattern}", pattern); - throw; - } - } - - public KeyValuePair[] RedisGetHash(RedisKey redKey) - { - HashEntry[] rawData = redisDb.HashGetAll(redKey); - var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair($"{x.Name}", $"{x.Value}")).ToArray(); - return result; - } - - public async Task[]> RedisGetHashAsync(RedisKey redKey) - { - HashEntry[] rawData = await redisDb.HashGetAllAsync(redKey); - var result = rawData.Where(x => !x.Name.IsNull).Select(x => new KeyValuePair($"{x.Name}", $"{x.Value}")).ToArray(); - return result; - } - - public Dictionary RedisGetHashDict(RedisKey hashKey) - { - HashEntry[] rawData = redisDb.HashGetAll(hashKey); - var result = rawData.Where(x => !x.Name.IsNull).ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); - return result; - } - - public async Task> RedisGetHashDictAsync(RedisKey hashKey) - { - HashEntry[] rawData = await redisDb.HashGetAllAsync(hashKey); - var result = rawData - .Where(x => !x.Name.IsNull) - .ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); - return result; - } - - public async Task RedisGetHashFieldAsync(RedisKey key, string hashField) - { - return await redisDb.HashGetAsync(key, hashField); - } - - public bool RedisKeyPresent(RedisKey key) - { - return redisDb.KeyExists(key); - } - - public async Task RedisKeyPresentAsync(RedisKey key) - { - return await redisDb.KeyExistsAsync(key); - } - - public void RedisSetHash(RedisKey redKey, KeyValuePair[] valori, double expireSeconds = -1.0) - { - HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); - redisDb.HashSet(redKey, redHash); - if (expireSeconds > 0.0) - { - redisDb.KeyExpire(redKey, DateTime.Now.AddSeconds(expireSeconds)); - } - } - - public async Task RedisSetHashAsync(RedisKey redKey, KeyValuePair[] valori, double expireSeconds = -1.0) - { - HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); - await redisDb.HashSetAsync(redKey, redHash); - if (expireSeconds > 0.0) - { - await redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds)); - } - } - - public void RedisSetHashDict(RedisKey redKey, Dictionary valori, double expireSeconds = -1.0) - { - HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); - redisDb.HashSet(redKey, redHash); - if (expireSeconds > 0.0) - { - redisDb.KeyExpire(redKey, DateTime.Now.AddSeconds(expireSeconds)); - } - } - - public async Task RedisSetHashDictAsync(RedisKey redKey, Dictionary valori, double expireSeconds = -1.0) - { - HashEntry[] redHash = valori.Select(x => new HashEntry(x.Key, x.Value)).ToArray(); - await redisDb.HashSetAsync(redKey, redHash); - if (expireSeconds > 0.0) - { - await redisDb.KeyExpireAsync(redKey, DateTime.Now.AddSeconds(expireSeconds)); - } - } - - /// - /// Inserisce record RRL + fa pulizia vecchi record - /// - /// - public async Task RemRebootLogAddAsync(RemoteRebootLogModel newRec) - { - // verifica preliminare ultima esecuzione (max 1 ogni 60 min...) - DateTime adesso = DateTime.Now; - bool doClean = false; - if (adesso.Subtract(lastCleanupRRL).TotalMinutes > 60) - { - lastCleanupRRL = adesso; - doClean = true; - } - - string confVal = await tryGetConfigAsync("IO_NumReboot2Keep"); - int num2keep = int.TryParse(confVal, out int n) ? n : 5; - // insert del record + pulizia - bool fatto = await IocDbController.RemRebootLogAddAndCleanAsync(newRec, doClean, num2keep); - if (fatto) - { - // svuota cache - var currKey = $"{Utils.redisRemRebLog}:*"; - await RedisFlushPatternAsync(currKey); - } - return fatto; - } - - /// - /// Recupera tutti i record di RemoteRebootLog - /// - /// - public async Task> RemRebootLogGetAllAsync() - { - // setup parametri costanti - string source = "DB"; - Stopwatch sw = new Stopwatch(); - sw.Start(); - List result = new List(); - // cerco in _redisConn... - var currKey = $"{Utils.redisRemRebLog}:ALL"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - //if (!string.IsNullOrEmpty($"{rawData}")) - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - source = "REDIS"; - } - else - { - result = await IocDbController.RemRebootLogGetAllAsync(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); - } - if (result == null) - { - result = new List(); - } - sw.Stop(); - Log.Debug($"RemRebootLogGetAll | {source} | {sw.Elapsed.TotalMilliseconds}ms"); - return result; - } - - /// - /// Recupera ultimo record x ogni IdxMacchina x avere ultimo attivo - /// - /// - public async Task> RemRebootLogGetLast() - { - // setup parametri costanti - string source = "DB"; - Stopwatch sw = new Stopwatch(); - sw.Start(); - List result = new List(); - // cerco in _redisConn... - var currKey = $"{Utils.redisRemRebLog}:LAST"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - source = "REDIS"; - } - else - { - result = await IocDbController.RemRebootLogGetLastAsync(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(30)); - } - if (result == null) - { - result = new List(); - } - sw.Stop(); - Log.Debug($"RemRebootLogGetLast | {source} | {sw.Elapsed.TotalMilliseconds}ms"); - return result; - } - - /// - /// Elimina da elenco KVP il TASK per l'impianto indicato - /// - /// - /// - /// - public async Task> RemTask2ExeMacchinaAsync(string idxMacchina, taskType tName) - { - // hard coded dimensione vettore DatiMacchine - Dictionary answ = new Dictionary(); - // ORA recupero da memoria redis... - try - { - var currHash = Utils.RedKeyTask2ExeMacc(idxMacchina, MpIoNS); - answ = await RedisGetHashDictAsync(currHash); - - answ.Remove($"{tName}"); - // riscrivo! - await RedisDelKeyAsync(currHash); - await RedisSetHashDictAsync(currHash, answ); - Log.Info($"Task REM - idxMacchina: {idxMacchina} | taskKey: {tName.ToString()}"); - } - catch (Exception exc) - { - Log.Info(string.Format("Errore in RemTask2ExeMacchinaAsync | idxMacchina {2}:{0}{1}", Environment.NewLine, exc, idxMacchina)); - } - return answ; - } - - /// - /// Resetta (rileggendo) i dati della State Machine multi ingressi nel formato - /// currKey: IdxMacchina - /// value: IdxFamigliaIngresso - /// - /// - /// - public async Task[]> resetMSMIAsync(string idxMacchina) - { - var currHash = Utils.RedKeyMsmi(idxMacchina); - // recupero records - var tabMSMI = await IocDbController.VMSFDGetMultiByMaccAsync(idxMacchina); - - KeyValuePair[] answ = new KeyValuePair[tabMSMI.Count]; - // salvo tutti i valori StateMachineIngressi... - int i = 0; - foreach (var item in tabMSMI) - { - answ[i] = new KeyValuePair(item.IdxMacchina, item.IdxFamigliaIngresso.ToString()); - i++; - } - // verifico il timeout (default 60 sec...) - var sTOutSmi = await tryGetConfigAsync("TmOut.MSMI"); - int tOut = 60; - int.TryParse(sTOutSmi, out tOut); - tOut = tOut <= 60 ? 60 : tOut; - // salvo in redis! - await RedisSetHashAsync(currHash, answ, tOut); - return answ; - } - - /// - /// Processa registrazione EVENTO CONTEGGIO PEZZI x una data macchina IOB - /// - /// Macchina - /// Pezzi da registrare - /// - public async Task saveCaricoPezzi(string idxMacchina, string qty) - { - // default: 0, non registrato x cautela... - string answ = "0"; - // controllo per proseguire - if (string.IsNullOrEmpty(idxMacchina) || string.IsNullOrEmpty(qty)) - { - string errore = $"Errore: mancano parametri macchina/incremento: idxMacchina {idxMacchina} | qty {qty}"; - Log.Error(errore); - answ = errore; - } - else - { - int numPzIncr = -1; - int.TryParse(qty, out numPzIncr); - // se il conteggio è >= 0 SALVO evento... - if (numPzIncr >= 0) - { - // recupero info tra cui ODL corrente - Dictionary datiMacc = await mDatiMacchineAsync(idxMacchina); - // registro evento 120 --> contapezzi in blocco !!!HARD CODED!!! !!!FIXME!!! - int idxEvento = 120; - DateTime adesso = DateTime.Now; - string codArticolo = "ND"; - if (datiMacc.ContainsKey("CodArticolo")) - { - codArticolo = datiMacc["CodArticolo"]; - } - - // creo evento - EventListModel newRecEv = new EventListModel() - { - CodArticolo = codArticolo, - IdxMacchina = idxMacchina, - IdxTipo = idxEvento, - InizioStato = adesso, - MatrOpr = 0, - pallet = "-", - Value = qty - }; - // salva e processa - var resp = await scriviRigaEventoAsync(newRecEv); - // registro in risposta che è andato tutto bene... ovvero la qty richiesta... - answ = qty; - } - } - return answ; - } - - /// - /// Processa registrazione EVENTO CONTEGGIO PEZZI x una data macchina IOB - /// - /// Macchina - /// Pezzi da registrare - /// DataOra evento - /// DataOra corrente - /// - public async Task SaveCaricoPezziAsync(string idxMacchina, string qty, string dtEve = "", string dtCurr = "") - { - // default: 0, non registrato x cautela... - string answ = "0"; - // Verifica se evento realtime oppure ho data specificata x processing @dtEve - DateTime adesso = DateTime.Now; - DateTime dtEvent = adesso; - bool rtimeProc = string.IsNullOrEmpty(dtEve); - if (!rtimeProc) - { - dtEvent = GetSrvDtEvent(dtEve, dtCurr); - } - // controllo per proseguire - if (!string.IsNullOrEmpty(idxMacchina) && !string.IsNullOrEmpty(qty)) - { - int numPzIncr = -1; - int.TryParse(qty, out numPzIncr); - // se il conteggio è >= 0 SALVO evento... - if (numPzIncr >= 0) - { - var listOdl = await IocDbController.OdlListByMaccPeriodoAsync(idxMacchina, dtEvent, dtEvent.AddSeconds(1)); - if (listOdl != null && listOdl.Count > 0) - { - string codArticolo = listOdl.FirstOrDefault()?.CodArticolo ?? "ND"; - // registro evento 120 --> contapezzi in blocco !!!HARD CODED!!! !!!FIXME!!! - int idxEvento = 120; - // creo evento - EventListModel newRecEv = new EventListModel() - { - CodArticolo = codArticolo, - IdxMacchina = idxMacchina, - IdxTipo = idxEvento, - InizioStato = dtEvent, - MatrOpr = 0, - pallet = "-", - Value = qty - }; - // salva e processa - var resp = await scriviRigaEventoAsync(newRecEv); - // registro in risposta che è andato tutto bene... ovvero la qty richiesta... - answ = qty; - } - } - } - else - { - string errore = $"Errore: mancano parametri macchina/incremento: idxMacchina {idxMacchina} | qty {qty}"; - Log.Error(errore); - answ = errore; - } - return answ; - } - - /// - /// Processa registrazione di un counter x una data macchina IOB - /// - /// - /// contapezzi - /// - public async Task SaveCounterAsync(string idxMacchina, string counter) - { - string answ = "0"; - // inizio processing vero e proprio INPUT... - if (string.IsNullOrEmpty(idxMacchina)) - { - string errore = "Errore: parametro macchina vuoto"; - Log.Info(errore); - answ = errore; - } - else - { - if (string.IsNullOrEmpty(counter)) - { - string errore = "Errore: parametro counter vuoto"; - Log.Error(errore); - answ = errore; - } - else - { - int newCounter = -1; - int.TryParse(counter, out newCounter); - // se il conteggio è >= 0 SALVO come nuovo conteggio... - if (newCounter >= 0) - { - var currKey = Utils.RedKeyPzCount(idxMacchina, MpIoNS); - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - // salvo per + tempo... - await redisDb.StringSetAsync(currKey, answ.ToString()); - answ = counter; - } - else - { - int currCount = await PzCounterTcAsync(idxMacchina); - answ = currCount.ToString(); - // salvo per meno tempo... - await redisDb.StringSetAsync(currKey, answ); - } - } - } - } - return answ; - } - - public async Task SaveDataItemsAsync(string id, List dataList) - { - bool answ = false; - if (mongoController != null) - { - answ = mongoController.SaveMachineDataItems(id, dataList); - } - else - { - // modalità con SqlSb (no mongo) - if (useFactory) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var mtcService = scope.ServiceProvider.GetRequiredService(); - answ = await mtcService.ReplaceMachineDataAsync(id, dataList); - } - else - { - answ = await MtcService.ReplaceMachineDataAsync(id, dataList); - } - } - - return answ; - } - - public async Task SaveMachine2Iob(string idxMacchina, string serData) - { - bool fatto = false; - var currKey = Utils.RedKeyMach2Iob(idxMacchina); - // se != null --> salvo! - if (!string.IsNullOrEmpty(serData)) - { - fatto = await redisDb.StringSetAsync(currKey, serData); - } - return fatto; - } - - public async Task SaveMachineIobConf(string idxMacchina, Dictionary currDict) - { - bool fatto = false; - var currKey = Utils.RedKeyMachIobConf(idxMacchina); - // se != null --> salvo! - if (currDict != null) - { - await RedisSetHashDictAsync(currKey, currDict); - fatto = true; - } - return fatto; - } - - /// - /// salva il segnale di "microstato" (segnale) ASYNC - /// - /// idx macchina - /// valOut ingresso - /// data-ora evento (server) - /// contatore sequenza dati inviati - /// - public async Task saveSigLogAsync(string idxMacchina, string valore, DateTime dtEve, int contatore) - { - SignalLogModel newRec = new SignalLogModel() - { - IdxMacchina = idxMacchina, - DtCurr = DateTime.Now, - DtEve = dtEve, - Contatore = contatore, - Valore = valore - }; - return await IocDbController.SignalLogInsertAsync(newRec); - } - - /// - /// scrive un evento di keepalive sulla tabella - /// - /// - /// - /// - public async Task ScriviKeepAliveAsync(string IdxMacchina, DateTime oraMacchina) - { - // cerco se ho keep alive in redis, - var currKey = Utils.RedKeyHash($"KeepAlive:{IdxMacchina}"); - bool keyPresent = await RedisKeyPresentAsync(currKey); - // se NON presente salvo in REDIS con TTL 10 sec e sul DB... - if (!keyPresent) - { - DateTime adesso = DateTime.Now; - await redisDb.StringSetAsync(currKey, adesso.ToString("s"), TimeSpan.FromSeconds(30)); - // effettuo scrittura sul DB - await IocDbController.KeepAliveUpsertAsync(IdxMacchina, DateTime.Now, oraMacchina); - } - } - - /// - /// Salvataggio YAML completo di configurazione dell'IOB - /// - /// - /// - /// - public async Task SetIobConfYamlAsync(string idxMacchina, string iobConfFull) - { - bool answ = false; - // se ho un area memoria valida... - if (!string.IsNullOrEmpty(iobConfFull)) - { - // salvo! - var currKey = Utils.RedKeyIobConfYaml(idxMacchina, MpIoNS); - answ = await redisDb.StringSetAsync(currKey, iobConfFull); - } - return answ; - } - - public async Task SetIobMemMap(string idxMacchina, PlcMemMapDto currMap) - { - bool answ = false; - // se ho un area memoria valida... - if (currMap != null) - { - // salvo! - var currKey = Utils.RedKeyIobMemMap(idxMacchina, MpIoNS); - string serVal = JsonConvert.SerializeObject(currMap); - answ = await redisDb.StringSetAsync(currKey, serVal); - } - return answ; - } - - /// - /// Restitusice elenco KVP dei campi della State Machine ingressi nel formato - /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) - /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE - /// - /// - /// - public async Task[]> StateMachInByKeyAsync(int idxFamIn) - { - // hard coded dimensione vettore DatiMacchine - KeyValuePair[] answ = new KeyValuePair[1]; - // iniziualizzo con un valOut... 0/0 - answ[0] = new KeyValuePair("0", "0"); - // ORA recupero da memoria redis... - try - { - var currHash = Utils.GetHashSMI(idxFamIn); - answ = await RedisGetHashAsync(currHash); - // se è vuoto... leggo da DB e popolo! - if (answ.Length == 0) - { - answ = await resetSMIAsync(idxFamIn); - } - } - catch (Exception exc) - { - Log.Error($"Errore in compilazione State Machine Ingressi x Redis:{Environment.NewLine}{exc}"); - } - return answ; - } - - /// - /// Statistiche ODL calcolate (da stored stp_STAT_ODL) - /// - /// - public Task> StatOdl(int IdxOdl) - { - return SpecDbController.OdlStart(IdxOdl); - } - - /// - /// restituisce il valOut da REDIS associato al tag richeisto - /// - /// Chiave in cui cercare il valOut - /// - public string TagConfGetKey(string redKey) - { - string outVal = ""; - // cerco in REDIS la conf x l'IOB - var rawData = redisDb.StringGet(redKey); - if (!string.IsNullOrEmpty(rawData)) - { - outVal = $"{rawData}"; - } - return outVal; - } - - /// - /// Elenco setup dei tag conf correnti - /// - /// - public Task>> TagsGetAll() - { - return Task.FromResult(currTagConf); - } - - /// - /// Esegue traduzione dato vocabolario da Lingua + Lemma - /// - /// - /// - /// - public string Traduci(string lemma, string lingua) - { - string answ = $"[{lemma}]"; - // verifico se ho qualcosa nell'obj vocabolario... - if (ObjVocabolario == null || ObjVocabolario.Count == 0) - { - // inizializzo il vocabolario... - ObjVocabolario = VocabolarioGetAll(); - } - var record = ObjVocabolario.Where(x => x.Lingua == lingua && x.Lemma == lemma).FirstOrDefault(); - if (record != null) - { - answ = record.Traduzione; - } - return answ; - } - - public async Task updateDossierValue(DossierModel currDoss, FluxLogDTO editFL) - { - bool answ = false; - // recupero intero set valori dossier deserializzando... - var fluxLogList = FluxLogDtoGetByFlux(currDoss.Valore); - await Task.Delay(1); - - // se tutto ok - if (fluxLogList != null) - { - // da provare...!!!! - - // elimino vecchio record - var currRec = fluxLogList.FirstOrDefault(x => x.CodFlux == editFL.CodFlux && x.dtEvento == editFL.dtEvento); - if (currRec != null) - { - fluxLogList.Remove(currRec); - // aggiungo nuovo - fluxLogList.Add(editFL); - } - - // serializzo nuovamente valOut - DossierFluxLogDTO? result = new DossierFluxLogDTO(); - var ODLflux = result.ODL.ToList(); - foreach (var item in fluxLogList) - { - ODLflux.Add(item); - } - - DossierFluxLogDTO updatedResult = new DossierFluxLogDTO() { ODL = ODLflux }; - - string rawVal = JsonConvert.SerializeObject(updatedResult); - currDoss.Valore = rawVal; - // aggiorno record sul DB - await SpecDbController.DossiersUpdateValore(currDoss); - } - - return answ; - } - - /// - /// Effettua UPSERT elenco parametri correnti x IOB (se c'è UPDATE, se manca ADD) - /// - /// - /// - /// - public async Task UpsertCurrObjItemsAsync(string idxMacchina, List innovations) - { - bool answ = false; - if (innovations != null) - { - Log.Info($"upsertCurrObjItems | idxMacchina: {idxMacchina} | {innovations.Count} innovations"); - // leggo i valori attuali... - List actValues = MachineParamList(idxMacchina); - // per ogni valOut passatomi faccio insert o update rispetto elenco valori correnti in REDIS - foreach (var item in actValues) - { - // cerco nelle innovazioni SE CI SIA il valOut... - var trovato = innovations.Find(obj => obj.uid == item.uid); - // se non trovato nelle innovazioni... - if (trovato == null) - { - // lo ri-aggiungo x non perderlo - innovations.Add(item); - Log.Trace($"innovations | add | item.uid: {item.uid} | item.value: {item.value}"); - } - else - // altrimenti aggiorno campo (non trasmesso) name e tengo il resto... - { - trovato.name = item.name; - Log.Info($"innovations | update | item.uid: {item.uid} | item.value: {item.value} --> {trovato.value} "); - } - } - // serializzo e salvo - string serVal = JsonConvert.SerializeObject(innovations); - - var currKey = Utils.RedKeyCurrObjItems(idxMacchina, MpIoNS); - RedisValue rawData = redisDb.StringSet(currKey, serVal); - } - return answ; - } - - /// - /// Restituisce il valore SPECIFICATO per la state machine ingressi - /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) - /// - /// - /// - /// - /// - public async Task ValoreSmiAsync(int idxFamIn, int idxMicroStato, int valoreIn) - { - string valOut = ""; - var currHash = Utils.GetHashSMI(idxFamIn); - string field = $"{idxMicroStato}_{valoreIn}"; - var searchVal = await RedisGetHashFieldAsync(currHash, field); - if (!searchVal.HasValue) - { - // ricarico tabella (salvando in redis x ricerca successiva)! - var valori = await StateMachInByKeyAsync(idxFamIn); - if (valori.Any(x => x.Key == field)) - { - searchVal = valori.FirstOrDefault(x => x.Key == field).Value; - } - } - valOut = $"{searchVal}"; - return valOut; - } - - /// - /// Elenco completo tabella Vocabolario - /// - /// - public List VocabolarioGetAll() - { - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - List? result = new List(); - string source = "REDIS"; - // cerco in redis... - RedisValue rawData = redisDb.StringGet(Utils.redisVocabolario); - if (!string.IsNullOrEmpty($"{rawData}")) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - } - else - { - result = SpecDbController.VocabolarioGetAll(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(Utils.redisVocabolario, rawData, getRandTOut(redisLongTimeCache / 5)); - source = "DB"; - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"VocabolarioGetAll Read from {source}: {ts.TotalMilliseconds}ms"); - if (result == null) - { - result = new List(); - } - return result; - } - - #endregion Public Methods - - #region Internal Methods - - /// - /// Recupera ArtNum dato CodArt (per impianti che accettano solo INT in scrittura) - /// - /// - /// CodArt richiesto, se vuoto restituisce TUTTI i valori in tabella di decodifica - /// - /// Dizionario contenente le righe delle codifiche attive Articolo/Numero - internal async Task> GetArtNumAsync(string codArt) - { - Dictionary answ = new Dictionary(); - var currData = await DecNumArtGetFiltAsync(codArt); - foreach (var item in currData) - { - answ.Add(item.CodArticolo, item.NumART); - } - return answ; - } - - #endregion Internal Methods - - #region Protected Fields - - protected Random rand = new Random(); - - #endregion Protected Fields - - #region Protected Properties - - protected string canCacheParametri { get; set; } = ""; - - #endregion Protected Properties - - #region Protected Methods - - /// - /// Restituisce un timeout dai minuti richiesti + tempo random 1..60 sec - /// - /// - /// - protected TimeSpan getRandTOut(double stdMinutes) - { - double rndValue = stdMinutes + (double)rand.Next(1, 60) / 60; - return TimeSpan.FromMinutes(rndValue); - } - - #endregion Protected Methods - - #region Private Fields - - private static IConfiguration _configuration = null!; - private static ILogger _logger = null!; - private static Logger Log = LogManager.GetCurrentClassLogger(); - private static IMtcSetupService MtcService = null!; -#if false - /// - /// Elenco completo valori Macchine 2 Slave - /// - /// - public List Macchine2SlaveGetAll() - { - List? result = new List(); - Stopwatch stopWatch = new Stopwatch(); - stopWatch.Start(); - string readType = "DB"; - string currKey = $"{Utils.redisBaseAddr}:M2STab"; - // cerco in redis dato valore sel macchina... - RedisValue rawData = redisDb.StringGet(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}"); - readType = "REDIS"; - } - else - { - result = IocDbController.Macchine2Slave(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - redisDb.StringSet(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (result == null) - { - result = new List(); - } - stopWatch.Stop(); - TimeSpan ts = stopWatch.Elapsed; - Log.Debug($"Macchine2SlaveGetAll | Read from {readType}: {ts.TotalMilliseconds}ms"); - return result; - } -#endif - private readonly IServiceScopeFactory _scopeFactory; - - /// - /// Provider CultureInfo x parse valori (es dataora) - /// - private CultureInfo ciProvider = CultureInfo.InvariantCulture; - - /// - /// Formato dataora standard x parsing - /// - private string dtFormat = "yyyyMMddHHmmssfff"; - - /// - /// Ultima esecuzione pulizia RRL x evitgare congestioni - /// - private DateTime lastCleanupRRL = DateTime.Now.AddHours(-1); - - /// - /// MS max age x dato MSE - /// - private int maxAge = 2000; - - private string MpIoNS = ""; - - /// - /// Oggetto vocabolario x uso continuo traduzione - /// - private List ObjVocabolario = new List(); - - /// - /// Oggetto per connessione a REDIS - /// - private ConnectionMultiplexer redisConn = null!; - - /// - /// Oggetto per connessione a REDIS modalità admin (ex flux dati) - /// - private ConnectionMultiplexer redisConnAdmin = null!; - - /// - /// Oggetto DB redis da impiegare x chiamate R/W - /// - private IDatabase redisDb = null!; - - private int redisLongTimeCache = 5; - - private int redisShortTimeCache = 2; - - /// - /// Generatore random classe - /// - private Random rnd = new Random(); - - private bool useFactory = false; - - #endregion Private Fields - - #region Private Methods - - /// - /// Verifica se sia necessario inserire un cambio di stato impianto (DiarioDi Bordo) in modalità batch - /// - /// - /// - /// - /// - /// - /// - /// - /// - private async Task CheckCambiaStatoBatchAsync(tipoInputEvento tipoInput, string IdxMacchina, DateTime InizioStato, int IdxTipo, string CodArt, string Value, int MatrOpr, string pallet) - { - await IocDbController.CheckCambiaStatoBatchAsync(tipoInput, IdxMacchina, InizioStato, IdxTipo, CodArt, Value, MatrOpr, pallet); -#if false - List listTransit = new List(); - TransizioneStatiModel? rigaTrans = null; - switch (tipoInput) - { - case tipoInputEvento.barcode: - // effettuo cambio stato INDIPENDENTEMENTE da stato precedente - listTransit = await IocDbController.SMES_getUserForcedAsync(IdxMacchina, IdxTipo); - - if (listTransit.Count > 0) - { - rigaTrans = listTransit.FirstOrDefault(); - // solo se cambia stato... - if (rigaTrans != null && rigaTrans.IdxStato != rigaTrans.next_IdxStato) - { - await IocDbController.DDB_InsStatoBatchAsync(IdxMacchina, InizioStato, rigaTrans.next_IdxStato, CodArt, Value, MatrOpr, pallet); - // aggiorno MSE - await IocDbController.RecalcMseAsync(IdxMacchina, 0); - } - } - else - { - Log.Debug($"Non trovata riga per: BARCODE | IdxMacchina: {IdxMacchina} | IdxTipo: {IdxTipo} | CodArt: {CodArt} | Value: {Value} | MatrOpr: {MatrOpr} | pallet: {pallet}"); - } - break; - - case tipoInputEvento.hw: - // verifico se ci sia necessità di cambio stato - listTransit = await IocDbController.SMES_getHwTransitionsAsync(IdxMacchina, IdxTipo); - if (listTransit.Count > 0) - { - rigaTrans = listTransit.FirstOrDefault(); - if (rigaTrans != null && rigaTrans.IdxStato != rigaTrans.next_IdxStato) - { - await IocDbController.DDB_InsStatoBatchAsync(IdxMacchina, InizioStato, rigaTrans.next_IdxStato, CodArt, Value, MatrOpr, pallet); - // aggiorno MSE - await IocDbController.RecalcMseAsync(IdxMacchina, 0); - } - } - else - { - Log.Debug($"Non trovata riga per: HW | IdxMacchina: {IdxMacchina} | IdxTipo: {IdxTipo} | CodArt: {CodArt} | Value: {Value} | MatrOpr: {MatrOpr} | pallet: {pallet}"); - } - break; - - default: - break; - } -#endif - } - - /// - /// Restituisce l'elenco codici flusso (da confFlux) x una macchina (se presenti) Impiegata - /// anche cache redis - /// - /// - /// - private async Task> ConfFluxMach(string idxMacchina) - { - List resultList = new List(); - string tag = string.IsNullOrEmpty(idxMacchina) ? "ALL" : idxMacchina; - var currKey = $"{Utils.redisConfFlux}:{tag}"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - var dbData = await IocDbController.ConfFluxFiltAsync(idxMacchina); - resultList = dbData - .Select(x => x.CodFlux) - .ToList(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(resultList); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (resultList == null) - { - resultList = new(); - } - return resultList; - } - - /// - /// Svuota la cache redis x l'elenco delle righe di confFlux x una macchina (se presenti) - /// - /// - /// - private async Task DossierLastByMachResetAsync(string idxMacchina) - { - bool answ = false; - var currKey = $"{Utils.redisDossByMacLast}:{idxMacchina}"; - await redisDb.KeyDeleteAsync(currKey); - return answ; - } - - /// - /// Helper standardizzazione valOut dataora ricevuto da IOB remoti - /// - /// - /// - private string dtFormStd(string? s) - { - return s?.Length > 17 ? s[..17] : s?.PadRight(17, '0') ?? string.Empty; - } - - /// - /// Restituisce l'elenco delle data-ora di confFlux x una macchina (se presenti) Impiegata - /// anche cache redis - /// - /// - /// num record da recuperare - /// - private async Task> FluxLogFirstByMachAsync(string idxMacchina, int numMax = 10) - { - List resultList = new List(); - - var currKey = $"{Utils.redisFluxByMacFirst}:{idxMacchina}"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - resultList = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - var dbData = await IocDbController.FluxLogFirstByMaccAsync(idxMacchina, numMax); - resultList = dbData - .Select(x => x.dtEvento) - .ToList(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(resultList); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache)); - } - if (resultList == null) - { - resultList = new List(); - } - return resultList; - } - - /// - /// Effettua vera chiamata x salvataggio snapshot dati FluxLog - /// - /// - /// - /// - /// - private async Task FluxLogSaveSnapshotAsync(string id, DateTime dtStart, DateTime dtEnd, string caller) - { - string answ = ""; - DateTime dataOraEvento = DateTime.Now; - Log.Debug($"{caller} | Richiesta snapshot dati FluxLog macchina: id: {id} | periodo: {dtStart} - {dtEnd}"); - try - { - bool fatto = await IocDbController.FluxLogTakeSnapshotLastAsync(id, dtStart, dtEnd); - answ = fatto ? "OK" : "KO"; - } - catch (Exception exc) - { - Log.Error($"Errore in {caller}{Environment.NewLine}{exc}"); - answ = "NO"; - } - return answ; - } - - /// - /// Recupero info ODL corrente da dati prod macchina - /// - /// - /// - private async Task GetCurrOdlByProdAsync(string idxMacchina) - { - string answ = ""; - // recupero stato... - var datiProd = await StatoProdMacchinaAsync(idxMacchina, DateTime.Now); - if (datiProd != null) - { - answ = datiProd.IdxOdl.ToString(); - } - // ultimo controllo su idxOdl... - answ = answ == "" ? "0" : answ; - // restituisco! - return answ; - } - - /// - /// Restituisce il valOut booleano se la macchina sia di tipo MULTI (con più state machine x INGRESSI) - /// usando dati macchina in cache per evitare lookup ridondanti - /// - /// - /// - /// - private bool isMulti(string idxMacchina, Dictionary datiMacc) - { - bool answ = false; - try - { - answ = Convert.ToBoolean(datiMacc.ContainsKey("Multi") && datiMacc["Multi"] == "1"); - } - catch (Exception exc) - { - Log.Error($"Eccezione in isMulti{Environment.NewLine}{exc}"); - } - return answ; - } - - private async Task> ListMasterAsync() - { - HashSet result = new(); - string currKey = $"{Utils.redisBaseAddr}:ListMaster"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - var fullList = await Macchine2SlaveGetAllAsync(); - result = fullList.Select(x => x.IdxMacchina).ToHashSet(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); - } - return result; - } - - private async Task> ListSlaveAsync() - { - HashSet result = new(); - string currKey = $"{Utils.redisBaseAddr}:ListSlave"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue) - { - result = JsonConvert.DeserializeObject>($"{rawData}") ?? new(); - } - else - { - var fullList = await Macchine2SlaveGetAllAsync(); - result = fullList.Select(x => x.IdxMacchinaSlave).Distinct().ToHashSet(); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, getRandTOut(redisLongTimeCache * 10)); - } - return result; - } - - private async Task POdlFlushCache() - { - bool answ = false; - RedisValue pattern = new RedisValue($"{Utils.redisXdlData}:*"); - answ = await RedisFlushPatternAsync(pattern); - pattern = new RedisValue($"{Utils.redisPOdlByOdl}:*"); - answ = await RedisFlushPatternAsync(pattern); - pattern = new RedisValue($"{Utils.redisPOdlByPOdl}:*"); - answ = await RedisFlushPatternAsync(pattern); - pattern = new RedisValue($"{Utils.redisPOdlList}:*"); - answ = await RedisFlushPatternAsync(pattern); - - return answ; - } - - /// - /// Calcola l'effettivo valOut da passare alla macchina a stati INGRESSI usando dati macchina in cache - /// - /// - /// - /// - /// - private string preProcInput(string idxMacchina, string valore, Dictionary datiMacc) - { - string newVal = ""; - try - { - // variabili - int valINT = 0; - int BitFilt = 0; - int BSR = 0; - bool ExplodeBit = false; - int NumBit = 0; - int newValInt = 0; - // recupero parametri dalla cache... - int.TryParse(datiMacc.ContainsKey("BitFilt") ? datiMacc["BitFilt"] : "0", out BitFilt); - int.TryParse(datiMacc.ContainsKey("BSR") ? datiMacc["BSR"] : "0", out BSR); - Boolean.TryParse(datiMacc.ContainsKey("ExplodeBit") ? datiMacc["ExplodeBit"] : "false", out ExplodeBit); - // non usato (x ora) - int.TryParse(datiMacc.ContainsKey("NumBit") ? datiMacc["NumBit"] : "0", out NumBit); - - // recupero valOut - valINT = int.Parse(valore, NumberStyles.HexNumber); - // filtro - newValInt = MP.Core.Utils.bMaskInt(valINT, BitFilt); - // effettuo eventuale BitShiftRight - if (BSR > 0) - { - newValInt = newValInt >> BSR; - } - // effettuo eventuale esplosione in BIT esclusivi - if (ExplodeBit) - { - newValInt = Convert.ToInt32(1 << newValInt); - } - // riconverto a STRING HEX!!! - newVal = newValInt.ToString("X"); - } - catch - { - newVal = valore; - } - - return newVal; - } - - private async Task resetCacheArticoli() - { - RedisValue pattern = new RedisValue($"{Utils.redisArtByDossier}:*"); - await RedisFlushPatternAsync(pattern); - pattern = new RedisValue($"{Utils.redisArtList}:*"); - await RedisFlushPatternAsync(pattern); - } - - /// - /// Restitusice elenco KVP dei campi DatiMacchine + StatoMacchine per l'impianto indicato - /// - /// - /// - private async Task> ResetDatiMacchinaAsync(string idxMacc) - { - Dictionary result = new Dictionary(); - VMSFDModel? dbResult = null; - if (useFactory) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var mtcService = scope.ServiceProvider.GetRequiredService(); - dbResult = await mtcService.VMSFDGetByMaccAsync(idxMacc); - } - else - { - dbResult = await IocDbController.VMSFDGetByMaccAsync(idxMacc); - } - - if (dbResult == null) return new Dictionary(); - - double numSecCache = redisLongTimeCache; - // converto in formato dizionario... - if (dbResult != null) - { - // salvo 1:1 i valori... STATO - result.Add("IdxMicroStato", $"{dbResult.IdxMicroStato}"); - result.Add("IdxStato", $"{dbResult.IdxStato}"); - result.Add("CodArticolo", $"{dbResult.CodArticolo}"); - result.Add("insEnabled", $"{dbResult.InsEnabled}"); - result.Add("sLogEnabled", $"{dbResult.SLogEnabled}"); - result.Add("pallet", $"{dbResult.Pallet}"); - result.Add("CodArticolo_A", $"{dbResult.CodArticoloA}"); - result.Add("CodArticolo_B", $"{dbResult.CodArticoloB}"); - result.Add("TempoCicloBase", $"{dbResult.TempoCicloBase}"); - result.Add("PzPalletProd", $"{dbResult.PzPalletProd}"); - result.Add("MatrOpr", $"{dbResult.MatrOpr}"); - result.Add("lastVal", $"{dbResult.LastVal}"); - result.Add("TCBase", $"{dbResult.TempoCicloBase}"); - - //...e SETUP - result.Add("CodMacc", $"{dbResult.Codmacchina}"); - result.Add("IdxFamIn", $"{dbResult.IdxFamigliaIngresso}"); - result.Add("Multi", $"{dbResult.Multi}"); - result.Add("BitFilt", $"{dbResult.BitFilt}"); - result.Add("MaxVal", $"{dbResult.MaxVal}"); - result.Add("BSR", $"{dbResult.Bsr}"); - result.Add("ExplodeBit", $"{dbResult.ExplodeBit}"); - result.Add("NumBit", $"{dbResult.NumBit}"); - result.Add("IdxFamMacc", $"{dbResult.IdxFamiglia}"); - result.Add("simplePallet", $"{dbResult.SimplePallet}"); - result.Add("palletChange", $"{dbResult.PalletChange}"); - // durata cache in secondi dal valOut insEnabled... - //double numSecCache = ((result["insEnabled"].ToLower() == "true") ? redisShortTimeCache : redisLongTimeCache); - numSecCache = dbResult.InsEnabled ? redisShortTimeCache : redisLongTimeCache; - } - else - { - - - } - // dati master/slave - string isMaster = (await ListMasterAsync()).Contains(idxMacc) ? "1" : "0"; - string isSlave = (await ListSlaveAsync()).Contains(idxMacc) ? "1" : "0"; - result.Add("Master", isMaster); - result.Add("Slave", isSlave); - - // Processing redis in transazoine... - var redKey = Utils.RedKeyDatiMacc(idxMacc, MpIoNS); - - // variazione con redis transaction... - var transaction = redisDb.CreateTransaction(); - // 1. Eliminiamo la chiave (rimuove i vecchi campi) - _ = transaction.KeyDeleteAsync(redKey); - // 2. Prepariamo gli HashEntry per il batch (più efficiente di un Dictionary) - HashEntry[] entries = result.Select(kvp => new HashEntry(kvp.Key, kvp.Value)).ToArray(); - // 3. Inseriamo i nuovi valori - _ = transaction.HashSetAsync(redKey, entries); - // 4. Impostiamo l'expiration in un unico colpo - _ = transaction.KeyExpireAsync(redKey, TimeSpan.FromSeconds(numSecCache)); - // Eseguiamo tutto in un unico viaggio verso Redis - bool success = await transaction.ExecuteAsync(); - - return result; - } - - /// - /// Resetta (rileggendo) i dati della State Machine ingressi nel formato - /// currKey: cState_nVal (current MICRO-STATE + "_" + new Value) - /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) - /// - /// - /// - private async Task[]> resetSMIAsync(int idxFamIn) - { - var currHash = Utils.GetHashSMI(idxFamIn); - // leggo da DB... - var tabSMI = await IocDbController.StateMachineIngressiAsync(idxFamIn); - - KeyValuePair[] answ = new KeyValuePair[tabSMI.Count]; - // salvo tutti i valori StateMachineIngressi... - int i = 0; - string key = ""; - string val = ""; - foreach (var item in tabSMI) - { - key = string.Format("{0}_{1}", item.IdxMicroStato, item.ValoreIngresso); - val = string.Format("{0}_{1}", item.IdxTipoEvento, item.NextIdxMicroStato); - answ[i] = new KeyValuePair(key, val); - i++; - } - // verifico il timeout (default 60 sec...) - int tOut = 300; - var sTOutSmi = await tryGetConfigAsync("TmOut.SMI"); - if (!string.IsNullOrEmpty(sTOutSmi)) - { - int.TryParse(sTOutSmi, out tOut); - } - // salvo in redis! - await RedisSetHashAsync(currHash, answ, tOut); - return answ; - } - -#if false - /// - /// Scrive una riga di evento nel db + check cambio stato DiarioDiBordo - /// - /// codice macchina - /// - private inputComandoMapo scriviRigaEvento(EventListModel newRec) - { - bool inserito = false; - try - { - // inserisco evento - inserito = IocDbController.EvListInsert(newRec); - // faccio controllo per eventuale cambio stato da tab transizioni... - CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); - } - catch (Exception exc) - { - Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); - } - // formatto output - inputComandoMapo answ = new inputComandoMapo(); - answ.outValue = inserito.ToString(); - answ.needStatusRefresh = true; - return answ; - } -#endif - - /// - /// Scrive una riga di evento nel db + check cambio stato DiarioDiBordo - /// - /// codice macchina - /// - private async Task scriviRigaEventoAsync(EventListModel newRec) - { - bool inserito = false; - try - { - // inserisco evento - inserito = await IocDbController.EvListInsertAsync(newRec); - // faccio controllo per eventuale cambio stato da tab transizioni... - await CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); - } - catch (Exception exc) - { - Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); - } - // formatto output - inputComandoMapo answ = new inputComandoMapo(); - answ.outValue = inserito.ToString(); - answ.needStatusRefresh = true; - return answ; - } - - /// - /// Scrive una riga evento + una riga microstato insieme, ed effettua verifica necessità cambio stato - /// - /// Record MicroStatoMacchina - /// record EventList - /// - private async Task scriviRigaEventoAsync(MicroStatoMacchinaModel newRecMsm, EventListModel newRecEv) - { - bool inserito = false; - if (useFactory) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var iocService = scope.ServiceProvider.GetRequiredService(); - - // inserisco evento - inserito = await iocService.EvListMicroStatoInsertAsync(newRecMsm, newRecEv); - // faccio controllo per eventuale cambio stato da tab transizioni... - - await iocService.CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet); - } - else - { - - try - { - // inserisco evento - inserito = await IocDbController.EvListMicroStatoInsertAsync(newRecMsm, newRecEv); - // faccio controllo per eventuale cambio stato da tab transizioni... - await CheckCambiaStatoBatchAsync(tipoInputEvento.hw, newRecEv.IdxMacchina, newRecEv.InizioStato ?? DateTime.Now, newRecEv.IdxTipo, newRecEv.CodArticolo, newRecEv.Value, newRecEv.MatrOpr, newRecEv.pallet); - } - catch (Exception exc) - { - Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRecEv.IdxMacchina} | IdxTipo {newRecEv.IdxTipo} | codArticolo {newRecEv.CodArticolo} | Value {newRecEv.Value} | MatrOpr {newRecEv.MatrOpr} | Pallet {newRecEv.pallet} | dTime {newRecEv.InizioStato}{Environment.NewLine}{exc}"); - } - } - // formatto output - inputComandoMapo answ = new inputComandoMapo(); - answ.outValue = inserito.ToString(); - answ.needStatusRefresh = true; - return answ; - } - - - - /// - /// Scrive una riga di evento manuale (barcode) nel db + check cambio stato DiarioDiBordo - /// - /// codice macchina - /// - private async Task scriviRigaEventoBarcodeAsync(EventListModel newRec) - { - bool inserito = false; - try - { - // inserisco evento - inserito = await IocDbController.EvListInsertAsync(newRec); - // faccio controllo per eventuale cambio stato da tab transizioni... - await CheckCambiaStatoBatchAsync(tipoInputEvento.barcode, newRec.IdxMacchina, newRec.InizioStato ?? DateTime.Now, newRec.IdxTipo, newRec.CodArticolo, newRec.Value, newRec.MatrOpr, newRec.pallet); - } - catch (Exception exc) - { - Log.Error($"Errore in scriviRigaEvento | IdxMacchina {newRec.IdxMacchina} | IdxTipo {newRec.IdxTipo} | codArticolo {newRec.CodArticolo} | Value {newRec.Value} | MatrOpr {newRec.MatrOpr} | Pallet {newRec.pallet} | dTime {newRec.InizioStato}{Environment.NewLine}{exc}"); - } - // formatto output - inputComandoMapo answ = new inputComandoMapo(); - answ.outValue = inserito.ToString(); - answ.needStatusRefresh = true; - return answ; - } - - /// - /// Stato prod macchina (completo) - /// - /// - /// - /// - private async Task StatoProdMacchinaAsync(string idxMacchina, DateTime dtReq, bool forceDb = false) - { - // setup parametri costanti - string source = "DB"; - StatoProdModel? result = new StatoProdModel(); - // cerco in _redisConn... - string currKey = $"{Utils.redisStatoProd}:{idxMacchina}:{dtReq:HHmm}"; - RedisValue rawData = await redisDb.StringGetAsync(currKey); - if (rawData.HasValue && !forceDb) - { - result = JsonConvert.DeserializeObject($"{rawData}"); - source = "REDIS"; - } - else - { - result = await IocDbController.StatoProdMacchinaAsync(idxMacchina, dtReq); - // serializzo e salvo... - rawData = JsonConvert.SerializeObject(result); - await redisDb.StringSetAsync(currKey, rawData, TimeSpan.FromSeconds(60)); - } - if (result == null) - { - result = new StatoProdModel(); - } - return result; - } - - /// - /// Restituisce valOut della stringa (SE disponibile) - /// - /// - /// - private async Task tryGetConfigAsync(string keyName) - { - string answ = ""; - // preselezione valori - var configData = await ConfigGetAllAsync(); - var currRec = configData.FirstOrDefault(x => x.Chiave == keyName); - if (currRec != null) - { - answ = currRec.Valore; - } - return answ; - } - -#if false - /// - /// Restituisce il valOut SPECIFICATO per la state machine ingressi - /// value: iTipoEv_nState (IdxTipoEv da trasmettere + New MICRO-STATE) - /// - /// - /// - /// - /// - private string valoreSMI(int idxFamIn, int idxMicroStato, int valoreIn) - { - var currHash = Utils.GetHashSMI(idxFamIn); - string field = string.Format("{0}_{1}", idxMicroStato, valoreIn); - return RedisGetHashFieldAsync(currHash, field); - } -#endif - - /// - /// cerca codice in anagrafica macchine ed eventualmente inserisce nuova macchina - /// - /// - private async Task verificaIdxMacchinaAsync(string IdxMacchina) - { - bool needDB = false; - try - { - // esecuzione in REDIS...cerco status macchina... - if ((await mDatiMacchineAsync(IdxMacchina)).Count == 0) - { - needDB = true; - } - } - catch { } - - if (needDB) - { - // verifico se esiste su DB - var dbRec = await IocDbController.MacchineGetByIdxAsync(IdxMacchina); - if (dbRec == null) - { - MacchineModel newRec = new MacchineModel() - { - IdxMacchina = IdxMacchina, - CodMacchina = "0000", - Nome = IdxMacchina, - Descrizione = "Macchina non codificata", - Note = "-", - locazione = "", - RecipeArchivePath = "", - RecipePath = "" - }; - await IocDbController.MacchineUpsertAsync(newRec); - - // verifico ci sia un microstato macchina... - var recMSM = await IocDbController.MicroStatoMacchinaGetByIdxMaccAsync(IdxMacchina); - if (recMSM.Count == 0) - { - // inserisco nuovo stato... - MicroStatoMacchinaModel msRec = new MicroStatoMacchinaModel() - { - IdxMacchina = IdxMacchina, - IdxMicroStato = 0, - InizioStato = DateTime.Now, - Value = "00" - }; - await IocDbController.MicroStatoMacchinaUpsertAsync(msRec); - } - } - } - } - - #endregion Private Methods - } -} diff --git a/MP-RIOC/WeatherForecast.cs b/MP-RIOC/WeatherForecast.cs deleted file mode 100644 index 86a54878..00000000 --- a/MP-RIOC/WeatherForecast.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace MP.RIOC -{ - public class WeatherForecast - { - public DateOnly Date { get; set; } - - public int TemperatureC { get; set; } - - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - - public string? Summary { get; set; } - } -} From a8eef823ffe3ba009519eef5ed1c75dd15568506 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 08:59:38 +0200 Subject: [PATCH 05/13] Fix index page + update profiles --- MP-RIOC/Pages/Index.cshtml | 33 ++----------------- .../Properties/PublishProfiles/IIS03.pubxml | 4 +-- .../Properties/PublishProfiles/IIS04.pubxml | 4 +-- .../PublishProfiles/IISProfile.pubxml | 25 -------------- 4 files changed, 7 insertions(+), 59 deletions(-) delete mode 100644 MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml diff --git a/MP-RIOC/Pages/Index.cshtml b/MP-RIOC/Pages/Index.cshtml index 44c0cf28..6332b46a 100644 --- a/MP-RIOC/Pages/Index.cshtml +++ b/MP-RIOC/Pages/Index.cshtml @@ -1,38 +1,11 @@ -@* @page -@using MP.RIOC.Services -@inject RouteStatsManager StatsManager -@{ - Layout = null; - var metrics = StatsManager.Snapshot(); -} - - - - MP.RIOC - Router Status - - - -
-

MP.RIOC

- MAPO Server Router -

Stato del Servizio: ONLINE

-
-

Questo server smista il traffico tra MP.IO (API Legacy dotNet 4.7.2) e MP.IOC (nuove API .NET 8)

- Versione Router: @System.Reflection.Assembly.GetExecutingAssembly().GetName().Version -
- - *@ - + @page @using MP.RIOC.Services @inject RouteStatsManager StatsManager @{ Layout = null; - var metrics = StatsManager.Snapshot(); + var rawData = StatsManager.Snapshot(); + var metrics = rawData.OrderByDescending(x => x.Value.Count).Take(10); } diff --git a/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml index d91b9388..e2916a71 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml +++ b/MP-RIOC/Properties/PublishProfiles/IIS03.pubxml @@ -6,11 +6,11 @@ true Release Any CPU - https://iis01.egalware.com/MP/RIOC/ + https://iis03.egalware.com/MP/RIOC/ false b9188473-f4ae-4f9f-be2d-70edaace0db9 false - https://iis01.egalware.com:8172/MsDeploy.axd + https://iis03.egalware.com:8172/MsDeploy.axd Default Web Site/MP/RIOC false diff --git a/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml index d91b9388..0322e090 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml +++ b/MP-RIOC/Properties/PublishProfiles/IIS04.pubxml @@ -6,11 +6,11 @@ true Release Any CPU - https://iis01.egalware.com/MP/RIOC/ + https://iis04.egalware.com/MP/RIOC/ false b9188473-f4ae-4f9f-be2d-70edaace0db9 false - https://iis01.egalware.com:8172/MsDeploy.axd + https://iis04.egalware.com:8172/MsDeploy.axd Default Web Site/MP/RIOC false diff --git a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml deleted file mode 100644 index d91b9388..00000000 --- a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - MSDeploy - true - Release - Any CPU - https://iis01.egalware.com/MP/RIOC/ - false - b9188473-f4ae-4f9f-be2d-70edaace0db9 - false - https://iis01.egalware.com:8172/MsDeploy.axd - Default Web Site/MP/RIOC - - false - WMSVC - true - true - jenkins - <_SavePWD>true - <_TargetId>IISWebDeploy - net8.0 - - \ No newline at end of file From 9b1a5a87722eee9415735a25d3261b715a1e6cd9 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 08:59:52 +0200 Subject: [PATCH 06/13] Aggiunta servizi x gestione chaimate LUA su Redis --- MP-RIOC/MP-RIOC.slnx | 1 + MP-RIOC/MP.RIOC.csproj | 11 +- MP-RIOC/Program.cs | 4 + MP-RIOC/RedisScript/RedisUpdateScript_v5.lua | 33 ++ MP-RIOC/RedisScript/RedisUpdateScript_v6.lua | 33 ++ MP-RIOC/Services/LuaScriptProvider.cs | 42 +++ MP-RIOC/Services/MetricsCalcService.cs | 224 +++++++++++ MP-RIOC/Services/MetricsDbFlushService.cs | 367 +++++++++++++++++++ MP-RIOC/appsettings.json | 1 - MP-RIOC/dotnet-tools.json | 13 + MP.IOC/MP.IOC.csproj | 2 +- MP.IOC/Resources/ChangeLog.html | 2 +- MP.IOC/Resources/VersNum.txt | 2 +- MP.IOC/Resources/manifest.xml | 2 +- 14 files changed, 723 insertions(+), 14 deletions(-) create mode 100644 MP-RIOC/RedisScript/RedisUpdateScript_v5.lua create mode 100644 MP-RIOC/RedisScript/RedisUpdateScript_v6.lua create mode 100644 MP-RIOC/Services/LuaScriptProvider.cs create mode 100644 MP-RIOC/Services/MetricsCalcService.cs create mode 100644 MP-RIOC/Services/MetricsDbFlushService.cs create mode 100644 MP-RIOC/dotnet-tools.json diff --git a/MP-RIOC/MP-RIOC.slnx b/MP-RIOC/MP-RIOC.slnx index 455ea819..c16a708c 100644 --- a/MP-RIOC/MP-RIOC.slnx +++ b/MP-RIOC/MP-RIOC.slnx @@ -1,4 +1,5 @@ + diff --git a/MP-RIOC/MP.RIOC.csproj b/MP-RIOC/MP.RIOC.csproj index 0c63a52d..e2cd4295 100644 --- a/MP-RIOC/MP.RIOC.csproj +++ b/MP-RIOC/MP.RIOC.csproj @@ -4,14 +4,10 @@ net8.0 enable enable - MP_RIOC + MP.RIOC 8.16.2605.808 - - - - @@ -25,10 +21,7 @@ - - - - + diff --git a/MP-RIOC/Program.cs b/MP-RIOC/Program.cs index 573ef479..c532a415 100644 --- a/MP-RIOC/Program.cs +++ b/MP-RIOC/Program.cs @@ -1,4 +1,5 @@ using MP.RIOC.Services; +using MP.RIOC.Services; using NLog; using NLog.Web; using StackExchange.Redis; @@ -54,6 +55,9 @@ builder.Services.AddSingleton(redisMux); // Registrazione dei servizi custom builder.Services.AddSingleton(); builder.Services.AddSingleton(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddSingleton(); logger.Info("Standard service configured"); diff --git a/MP-RIOC/RedisScript/RedisUpdateScript_v5.lua b/MP-RIOC/RedisScript/RedisUpdateScript_v5.lua new file mode 100644 index 00000000..1b4a2477 --- /dev/null +++ b/MP-RIOC/RedisScript/RedisUpdateScript_v5.lua @@ -0,0 +1,33 @@ +-- RedisUpdateScript_v5 +local key = KEYS[1] +local countInc = tonumber(ARGV[1]) or 0 +local totalMsInc = tonumber(ARGV[2]) or 0 +local newMax = tonumber(ARGV[3]) +local newMin = tonumber(ARGV[4]) +local sentinel = tonumber(ARGV[5]) + +-- Incrementi base +redis.call('HINCRBY', key, 'count', countInc) +redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) + +-- MAX +local currentMaxStr = redis.call('HGET', key, 'maxMs') +local currentMax = tonumber(currentMaxStr) + +if newMax ~= nil and newMax < sentinel then + if currentMax == nil or newMax > currentMax then + redis.call('HSET', key, 'maxMs', newMax) + end +end + +-- MIN +local currentMinStr = redis.call('HGET', key, 'minMs') +local currentMin = tonumber(currentMinStr) + +if newMin ~= nil and newMin < sentinel then + if currentMin == nil or newMin < currentMin then + redis.call('HSET', key, 'minMs', newMin) + end +end + +return 1 diff --git a/MP-RIOC/RedisScript/RedisUpdateScript_v6.lua b/MP-RIOC/RedisScript/RedisUpdateScript_v6.lua new file mode 100644 index 00000000..230ed7f9 --- /dev/null +++ b/MP-RIOC/RedisScript/RedisUpdateScript_v6.lua @@ -0,0 +1,33 @@ +-- RedisUpdateScript_v6 +local key = KEYS[1] +local countInc = tonumber(ARGV[1]) or 0 +local totalMsInc = tonumber(ARGV[2]) or 0 +local newMax = tonumber(ARGV[3]) +local newMin = tonumber(ARGV[4]) +local sentinel = tonumber(ARGV[5]) + +-- Incrementi +redis.call('HINCRBY', key, 'count', countInc) +redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) + +-- MAX +local currentMaxStr = redis.call('HGET', key, 'maxMs') +local currentMax = tonumber(currentMaxStr) + +if newMax ~= nil and newMax < sentinel then + if currentMax == nil or newMax > currentMax then + redis.call('HSET', key, 'maxMs', tostring(newMax)) + end +end + +-- MIN +local currentMinStr = redis.call('HGET', key, 'minMs') +local currentMin = tonumber(currentMinStr) + +if newMin ~= nil and newMin < sentinel then + if currentMin == nil or newMin < currentMin then + redis.call('HSET', key, 'minMs', tostring(newMin)) + end +end + +return 1 diff --git a/MP-RIOC/Services/LuaScriptProvider.cs b/MP-RIOC/Services/LuaScriptProvider.cs new file mode 100644 index 00000000..41951353 --- /dev/null +++ b/MP-RIOC/Services/LuaScriptProvider.cs @@ -0,0 +1,42 @@ +using Microsoft.Extensions.Options; +using MP.Core.Conf; + +namespace MP.RIOC.Services +{ + public sealed class LuaScriptProvider + { + private readonly Dictionary _scripts; + + public IReadOnlyDictionary Scripts => _scripts; + + public LuaScriptProvider( + IOptions cfg, + IWebHostEnvironment env) + { + _scripts = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var kv in cfg.Value.Scripts) + { + var name = kv.Key; + var relativePath = kv.Value; + + var fullPath = Path.Combine(env.ContentRootPath, relativePath); + + if (!File.Exists(fullPath)) + throw new FileNotFoundException($"Script Lua non trovato: {fullPath}"); + + var content = File.ReadAllText(fullPath); + + _scripts[name] = content; + } + } + + public string Get(string name) + { + if (!_scripts.TryGetValue(name, out var script)) + throw new KeyNotFoundException($"Script Lua '{name}' non trovato."); + + return script; + } + } +} diff --git a/MP-RIOC/Services/MetricsCalcService.cs b/MP-RIOC/Services/MetricsCalcService.cs new file mode 100644 index 00000000..1accf3a5 --- /dev/null +++ b/MP-RIOC/Services/MetricsCalcService.cs @@ -0,0 +1,224 @@ +using MP.RIOC.Services; +using NLog; +using StackExchange.Redis; +using System.Globalization; + +namespace MP.RIOC.Services +{ + public class MetricsCalcService : BackgroundService + { + #region Public Constructors + + /// + /// Metodo x calcolo metriche/statistiche di esecuzone realtime + /// + /// + /// + /// + public MetricsCalcService(RouteStatsManager stats, + LuaScriptProvider luaProvider, + IConfiguration config, + IConnectionMultiplexer mux) + { + _stats = stats; + _config = config; + _db = mux.GetDatabase(); + _redisBaseKey = _config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; + _updateScript = luaProvider.Get("Update"); + } + + #endregion Public Constructors + + #region Protected Methods + + /// + /// Valore SentinelValue x valore minimo in reids/Lua + /// Usiamo un valore molto grande (es. 10 milioni di secondi = ~115 giorni). + /// E' impossibile che una singola richiesta HTTP duri cos� tanto, quindi usarlo come "SentinelValue" � sicuro. + /// + private const string SentinelValue = "999999999"; + + /// + /// Script update Redis in Lua, recuperato dal provider script. + /// Lo script gestisce l'incremento e la logica condizionale per Min/Max in un'unica operazione atomica. + /// + private readonly string _updateScript; + + + // Classe di supporto per l'aggregazione locale dei valori Daily + private class AggregatedStats + { + public long Count; + public double TotalMs; + public double MaxMs; + public double MinMs = double.MaxValue; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var interval = _config.GetValue("RouteMan:MetricCalcIntervalSeconds", 20); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken); + var snapshot = _stats.Snapshot(); + if (snapshot.Count == 0) continue; + + var adesso = DateTime.Now; + var hourStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, adesso.Hour, 0, 0); + var dayStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, 0, 0, 0); + + if (_db == null) continue; + + var batch = _db.CreateBatch(); + var tasks = new List(); + + // Dizionario per aggregare i valori Daily prima di inviarli a Redis + var dailyAggregates = new Dictionary(); + + foreach (var kv in snapshot) + { + string dest = "IO"; + string method = "NA"; + string machineId = "ALL"; + string rawKey = kv.Key; + if (rawKey.Contains("|")) + { + var splitVal = rawKey.Split("|"); + dest = splitVal[0]; + if (splitVal.Length > 1) + method = splitVal[1]; + if (splitVal.Length > 2) + machineId = splitVal[2]; + } + else + { + method = rawKey; + } + + var stat = kv.Value; + var count = Interlocked.Read(ref stat.Count); + var totalMs = stat.TotalDuration.TotalMilliseconds; + var maxMs = stat.MaxDuration.TotalMilliseconds; + // Se la durata è ancora MaxValue, usiamo la SentinelValue per dire a Redis "non aggiornare" + var minMs = (stat.MinDuration == TimeSpan.MaxValue) + ? double.Parse(SentinelValue) + : stat.MinDuration.TotalMilliseconds; + + // --- LOGICA HOURLY (Per ogni route/method e' una chiave distinta) --- + var hourKey = HourBucketKey(dest, method, hourStart); + var hoursIndex = HoursIndexKey(dest, method); + var hourScore = ToEpochSeconds(hourStart); + + // Usiamo lo script Lua per l'aggiornamento atomico dell'ora + tasks.Add(batch.ScriptEvaluateAsync(_updateScript, + new RedisKey[] { hourKey }, + new RedisValue[] { + count.ToString(CultureInfo.InvariantCulture), + totalMs.ToString(CultureInfo.InvariantCulture), + maxMs.ToString(CultureInfo.InvariantCulture), + minMs.ToString(CultureInfo.InvariantCulture), + SentinelValue + })); + + tasks.Add(batch.SortedSetAddAsync(hoursIndex, hourKey, hourScore)); + + // --- LOGICA DAILY (Aggregazione locale per evitare sovrascritture nel loop) --- + var dayKey = DayBucketKey(dest, machineId, dayStart); + var daysIndex = DaysIndexKey(dest, machineId); + var dayScore = ToEpochSeconds(dayStart); + + if (!dailyAggregates.TryGetValue(dayKey, out var agg)) + { + agg = new AggregatedStats(); + dailyAggregates[dayKey] = agg; + } + + agg.Count += count; + agg.TotalMs += totalMs; + agg.MaxMs = Math.Max(agg.MaxMs, maxMs); + if (minMs != double.MaxValue) + agg.MinMs = Math.Min(agg.MinMs, minMs); + + // Aggiungiamo l'indice al batch + tasks.Add(batch.SortedSetAddAsync(daysIndex, dayKey, dayScore)); + } + + // --- INVIO AGGREGATI DAILY A REDIS --- + foreach (var dayKV in dailyAggregates) + { + var key = dayKV.Key; + var agg = dayKV.Value; + // Se agg.MinMs è inizializzato a double.MaxValue, lo portiamo alla SentinelValue + double finalMin = (agg.MinMs == double.MaxValue || agg.MinMs >= double.Parse(SentinelValue)) + ? double.Parse(SentinelValue) + : agg.MinMs; + + tasks.Add(batch.ScriptEvaluateAsync(_updateScript, + new RedisKey[] { key }, + new RedisValue[] { + agg.Count.ToString(CultureInfo.InvariantCulture), + agg.TotalMs.ToString(CultureInfo.InvariantCulture), + agg.MaxMs.ToString(CultureInfo.InvariantCulture), + finalMin.ToString(CultureInfo.InvariantCulture), + SentinelValue + })); + } + + // Esegui tutto il batch in un unico round-trip + batch.Execute(); + await Task.WhenAll(tasks); + + _stats.Clear(); + } + catch (TaskCanceledException) { } + catch (Exception ex) + { + Log.Error(ex, "Error flushing metrics"); + } + } + } + #endregion Protected Methods + + #region Private Fields + + private static string _redisBaseKey = ""; + private static Logger Log = LogManager.GetCurrentClassLogger(); + private readonly IConfiguration _config; + private readonly IDatabase _db; + private readonly RouteStatsManager _stats; + + #endregion Private Fields + + #region Private Methods + + private static string DayBucketKey(string dest, string machId, DateTime dtRif) + { + return $"{_redisBaseKey}:stats:day:{dest}:{machId}:{dtRif.ToString("yyyyMMdd", CultureInfo.InvariantCulture)}"; + } + + private static string DaysIndexKey(string dest, string machId) + { + return $"{_redisBaseKey}:stats:days:{dest}:{machId}"; + } + + private static string HourBucketKey(string dest, string method, DateTime dtRif) + { + return $"{_redisBaseKey}:stats:hour:{dest}:{method}:{dtRif.ToString("yyyyMMddHH", CultureInfo.InvariantCulture)}"; + } + + private static string HoursIndexKey(string dest, string method) + { + return $"{_redisBaseKey}:stats:hours:{dest}:{method}"; + } + + private static long ToEpochSeconds(DateTime dt) + { + return new DateTimeOffset(dt).ToUnixTimeSeconds(); + } + + #endregion Private Methods + } +} diff --git a/MP-RIOC/Services/MetricsDbFlushService.cs b/MP-RIOC/Services/MetricsDbFlushService.cs new file mode 100644 index 00000000..dbbc9eb4 --- /dev/null +++ b/MP-RIOC/Services/MetricsDbFlushService.cs @@ -0,0 +1,367 @@ +using MP.Data.DbModels.Utils; +using MP.Data.Services.Utils; +using NLog; +using StackExchange.Redis; +using System.Globalization; + +namespace MP.RIOC.Services +{ + public class MetricsDbFlushService : BackgroundService + { + #region Public Constructors + + public MetricsDbFlushService( + IServiceScopeFactory scopeFactory, + IConfiguration config, + IConnectionMultiplexer mux) + { + _scopeFactory = scopeFactory; + _config = config; + _mux = mux; + _db = mux.GetDatabase(); + } + + #endregion Public Constructors + + #region Protected Methods + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var interval = _config.GetValue("RouteMan:MetricFlushIntervalSeconds", 300); + + while (!stoppingToken.IsCancellationRequested) + { + try + { + await ProcessDayLiveMetricsAsync(); + await ProcessHourLiveMetricsAsync(); + await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken); + } + catch (TaskCanceledException) { break; } + catch (Exception ex) + { + Log.Error(ex, "Error flushing metrics to database"); + } + } + } + + #endregion Protected Methods + + #region Private Fields + + private const double SentinelValue = 999999999; + private static readonly Logger Log = LogManager.GetCurrentClassLogger(); + private readonly IConfiguration _config; + private readonly IDatabase _db; + private readonly IConnectionMultiplexer _mux; + private readonly IServiceScopeFactory _scopeFactory; + + #endregion Private Fields + + #region Private Properties + + private string _redisBaseKey => _config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; + + #endregion Private Properties + + #region Private Methods + + /// + /// Processing dati giornalieri (da Redis a DB) + /// + /// + private async Task ProcessDayLiveMetricsAsync() + { + var aggrRecordsToInsert = new List(); + var keysToDelete = new List(); + + bool deleteConfirmed = _config.GetValue("RouteMan:DeleteExpiredMetrics", false); + DateTime now = DateTime.Now; + + // Confini temporali per proteggere i dati in corso + DateTime currentDayStart = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); + + var endpoints = _mux.GetEndPoints(); + + foreach (var endpoint in endpoints) + { + var server = _mux.GetServer(endpoint); + + if (server.IsReplica) + { + continue; + } + + string[] patternsToScan = { + $"{_redisBaseKey}:stats:days:*" + }; + + foreach (var pattern in patternsToScan) + { + // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer + await foreach (var indexKey in server.KeysAsync(pattern: pattern)) + { + if (string.IsNullOrEmpty($"{indexKey}")) continue; + + // CORREZIONE: Utilizzo di SortedSetRangeByRankAsync con range 0 a -1 per prendere tutto il set + var memberKeys = await _db.SortedSetRangeByRankAsync(indexKey, 0, -1); + + foreach (var statKey in memberKeys) + { + var sKey = (RedisKey)$"{statKey}"; + if (!TryParseKeyMetadata(sKey, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType)) + continue; + + // Verifica se la chiave fosse scaduta rispetto all'orario corrente + bool isExpired = isHourType + //? timestamp < currentHourStart + ? false + : timestamp < currentDayStart; + + // Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione + if (isExpired && deleteConfirmed) + { + keysToDelete.Add(sKey); + } + + // Recupero dati dalla Hash + var hashData = await _db.HashGetAllAsync(sKey); + if (hashData.Length == 0) continue; + + var dict = hashData.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); + + if (dict.TryGetValue("count", out var countStr) && + dict.TryGetValue("totalMs", out var totalMsStr)) + { + long count = long.Parse(countStr); + count = long.Parse(countStr); + double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture); + + double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0; + double minMs = dict.ContainsKey("minMs") ? double.Parse(dict["minMs"], CultureInfo.InvariantCulture) : 0; + + // TUA CORREZIONE: Reset sentinella per evitare valori fuori scala nel DB + if (minMs >= SentinelValue) minMs = SentinelValue; + if (maxMs >= SentinelValue) maxMs = SentinelValue; + + if (count <= 0) continue; + + aggrRecordsToInsert.Add(new StatsAggregatedModel + { + Destination = dest, + MachineId = machId, + Hour = timestamp, + RequestCount = count, + AvgDuration = totalMs / count, + MinDuration = minMs, + MaxDuration = maxMs, + NoReply = 0 + }); + } + } + } + } + } + + // --- FASE UPSERT DB --- + if (aggrRecordsToInsert.Count > 0) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var aggrService = scope.ServiceProvider.GetRequiredService(); + await aggrService.UpsertManyAsync(aggrRecordsToInsert, false); + Log.Info($"[DAY] Upserted {aggrRecordsToInsert.Count} records to DB"); + } + + // --- FASE PULIZIA REDIS --- + if (deleteConfirmed && keysToDelete.Count > 0) + { + var batch = _db.CreateBatch(); + int deletedCount = 0; + foreach (var key in keysToDelete) + { + _ = batch.KeyDeleteAsync(key); + deletedCount++; + } + batch.Execute(); + Log.Info($"[CLEANUP DAY] Deleted {deletedCount} expired metric keys from Redis"); + } + } + + /// + /// Processing dati orari (da Redis a DB) + /// + /// + private async Task ProcessHourLiveMetricsAsync() + { + var detailRecordsToInsert = new List(); + var keysToDelete = new List(); + + bool deleteConfirmed = _config.GetValue("RouteMan:DeleteExpiredMetrics", false); + DateTime now = DateTime.Now; + + // Confini temporali per proteggere i dati in corso + DateTime currentHourStart = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0); + DateTime currentDayStart = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); + + var endpoints = _mux.GetEndPoints(); + + foreach (var endpoint in endpoints) + { + var server = _mux.GetServer(endpoint); + + if (server.IsReplica) + { + continue; + } + + string[] patternsToScan = { + $"{_redisBaseKey}:stats:hours:*" + }; + + foreach (var pattern in patternsToScan) + { + // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer + await foreach (var indexKey in server.KeysAsync(pattern: pattern)) + { + if (string.IsNullOrEmpty($"{indexKey}")) continue; + + // CORREZIONE: Utilizzo di SortedSetRangeByRankAsync con range 0 a -1 per prendere tutto il set + var memberKeys = await _db.SortedSetRangeByRankAsync(indexKey, 0, -1); + + foreach (var statKey in memberKeys) + { + var sKey = (RedisKey)$"{statKey}"; + if (!TryParseKeyMetadata(sKey, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType)) + continue; + + // Verifica se la chiave fosse scaduta rispetto all'orario corrente + bool isExpired = isHourType + ? timestamp < currentHourStart + : false; + //: timestamp < currentDayStart; + + // Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione + if (isExpired && deleteConfirmed) + { + keysToDelete.Add(sKey); + } + + // Recupero dati dalla Hash + var hashData = await _db.HashGetAllAsync(sKey); + if (hashData.Length == 0) continue; + + var dict = hashData.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); + + if (dict.TryGetValue("count", out var countStr) && + dict.TryGetValue("totalMs", out var totalMsStr)) + { + long count = long.Parse(countStr); + count = long.Parse(countStr); + double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture); + + double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0; + double minMs = dict.ContainsKey("minMs") ? double.Parse(dict["minMs"], CultureInfo.InvariantCulture) : 0; + + // TUA CORREZIONE: Reset sentinella per evitare valori fuori scala nel DB + if (minMs >= SentinelValue) minMs = SentinelValue; + if (maxMs >= SentinelValue) maxMs = SentinelValue; + + if (count <= 0) continue; + + detailRecordsToInsert.Add(new StatsDetailModel + { + Destination = dest, + Type = method, + Hour = timestamp, + RequestCount = count, + AvgDuration = totalMs / count, + MinDuration = minMs, + MaxDuration = maxMs, + NoReply = 0 + }); + } + } + } + } + } + + // --- FASE UPSERT DB --- + if (detailRecordsToInsert.Count > 0) + { + await using var scope = _scopeFactory.CreateAsyncScope(); + var detailService = scope.ServiceProvider.GetRequiredService(); + await detailService.UpsertManyAsync(detailRecordsToInsert, false); + Log.Info($"[HOUR] Upserted {detailRecordsToInsert.Count} records to DB"); + } + + // --- FASE PULIZIA REDIS --- + if (deleteConfirmed && keysToDelete.Count > 0) + { + var batch = _db.CreateBatch(); + int deletedCount = 0; + foreach (var key in keysToDelete) + { + _ = batch.KeyDeleteAsync(key); + deletedCount++; + } + batch.Execute(); + Log.Info($"[CLEANUP HOUR] Deleted {deletedCount} expired metric keys from Redis"); + } + } + + private bool TryParseKeyMetadata(RedisKey key, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType) + { + dest = "NA"; + method = "NA"; + machId = "ALL"; + timestamp = DateTime.MinValue; + isHourType = true; + try + { + string k = key.ToString(); + string relativeKey = k.Replace($"{_redisBaseKey}:", ""); + var parts = relativeKey.Split(':'); + + if (parts.Length < 4) return false; + + string type = parts[1]; // "hour" o "day" + dest = parts[2]; + + if (type == "hour") + { + isHourType = true; + method = parts[3]; + if (parts.Length >= 5 && DateTime.TryParseExact(parts[4], "yyyyMMddHH", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) + { + timestamp = dt; + return true; + } + } + else if (type == "day") + { + isHourType = false; + method = "DAILY"; + string rawDate = ""; + if (parts.Length >= 5) + { + machId = parts[3]; + rawDate = parts[4]; + } + else + { + rawDate = parts[3]; + } + if (DateTime.TryParseExact(rawDate, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) + { + timestamp = dt; + return true; + } + } + } + catch { } + return false; + } + + #endregion Private Methods + } +} diff --git a/MP-RIOC/appsettings.json b/MP-RIOC/appsettings.json index 4583eef3..0f17cadb 100644 --- a/MP-RIOC/appsettings.json +++ b/MP-RIOC/appsettings.json @@ -72,7 +72,6 @@ "useFactory": true }, "ConnectionStrings": { - "MP.Utils": "Server=SQL2016DEV;Database=MoonPro_Utils; User ID=sa;Password=keyhammer16; integrated security=False; App=MP.IOC;", "Redis": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false", "RedisAdmin": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true" } diff --git a/MP-RIOC/dotnet-tools.json b/MP-RIOC/dotnet-tools.json new file mode 100644 index 00000000..ce0aab73 --- /dev/null +++ b/MP-RIOC/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "10.0.7", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/MP.IOC/MP.IOC.csproj b/MP.IOC/MP.IOC.csproj index d1c88af3..cc421694 100644 --- a/MP.IOC/MP.IOC.csproj +++ b/MP.IOC/MP.IOC.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 8.16.2605.611 + 8.16.2605.808 diff --git a/MP.IOC/Resources/ChangeLog.html b/MP.IOC/Resources/ChangeLog.html index f22519c1..c2c936a0 100644 --- a/MP.IOC/Resources/ChangeLog.html +++ b/MP.IOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-IOC -

Versione: 8.16.2605.611

+

Versione: 8.16.2605.808


Note di rilascio:
  • diff --git a/MP.IOC/Resources/VersNum.txt b/MP.IOC/Resources/VersNum.txt index a09c6432..a37f9c6b 100644 --- a/MP.IOC/Resources/VersNum.txt +++ b/MP.IOC/Resources/VersNum.txt @@ -1 +1 @@ -8.16.2605.611 +8.16.2605.808 diff --git a/MP.IOC/Resources/manifest.xml b/MP.IOC/Resources/manifest.xml index 08c9065b..2f8a10b2 100644 --- a/MP.IOC/Resources/manifest.xml +++ b/MP.IOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 8.16.2605.611 + 8.16.2605.808 https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html false From d9728dc706dd450d4be450426afa44f07cff52f7 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 09:45:23 +0200 Subject: [PATCH 07/13] Test modifica pubxml --- MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user new file mode 100644 index 00000000..00f79f0d --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user @@ -0,0 +1,10 @@ + + + + + False|2026-05-08T07:04:47.5869659Z||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; + + + AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAlmCNMQ0J1UqPM+RugOqtLgAAAAACAAAAAAAQZgAAAAEAACAAAABmDmKQdTOJuzYQ2FFyU+1htQ8H/TQ+IM9D7RchZs6pvgAAAAAOgAAAAAIAACAAAABeb+XK2KUWpsQ2fiYxFKeezXYyZloQPjo9Qkmjbf+FlyAAAAAR+ckV3KTLXMIMyW4f5PBdp6Uxv5tWJ5LldbO4N+tXYUAAAACJwytTC9fJKy3wyHTlSVYRd/OdBUQ8pCweu3wSK3CGvcpgwT+VFYooELXfzgEV8l6P6FrAdGoF0gt9O3yyn1X4 + + \ No newline at end of file From ec3c5e65e681d7c524ebc0c8f7da5c4ef33de932 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 09:45:29 +0200 Subject: [PATCH 08/13] ancora ottimizzazioni deploy --- MP-RIOC/MP.RIOC.csproj | 11 +++- MP-RIOC/Program.cs | 48 +++++++++++++++-- .../Properties/PublishProfiles/IIS01.pubxml | 49 ++++++++++-------- .../PublishProfiles/IIS01.pubxml.user | 2 +- MP-RIOC/Resources/ChangeLog-original.html | 31 +++++++++++ MP-RIOC/Resources/ChangeLog.html | 31 +++++++++++ MP-RIOC/Resources/VersNum.txt | 1 + MP-RIOC/Resources/logoSteamware.png | Bin 0 -> 3402 bytes MP-RIOC/Resources/manifest-original.xml | 7 +++ MP-RIOC/Resources/manifest.xml | 7 +++ MP-RIOC/Services/MetricsCalcService.cs | 5 +- MP-RIOC/Services/MetricsDbFlushService.cs | 2 +- MP-RIOC/appsettings.Production.json | 24 +++++++++ MP-RIOC/appsettings.json | 11 +++- MP.Data/DataServiceCollectionExtensions.cs | 17 ------ MP.IOC/Resources/ChangeLog-original.html | 2 +- MP.IOC/appsettings.json | 3 +- 17 files changed, 201 insertions(+), 50 deletions(-) create mode 100644 MP-RIOC/Resources/ChangeLog-original.html create mode 100644 MP-RIOC/Resources/ChangeLog.html create mode 100644 MP-RIOC/Resources/VersNum.txt create mode 100644 MP-RIOC/Resources/logoSteamware.png create mode 100644 MP-RIOC/Resources/manifest-original.xml create mode 100644 MP-RIOC/Resources/manifest.xml create mode 100644 MP-RIOC/appsettings.Production.json diff --git a/MP-RIOC/MP.RIOC.csproj b/MP-RIOC/MP.RIOC.csproj index e2cd4295..5ee72a40 100644 --- a/MP-RIOC/MP.RIOC.csproj +++ b/MP-RIOC/MP.RIOC.csproj @@ -5,7 +5,7 @@ enable enable MP.RIOC - 8.16.2605.808 + 8.16.2605.809 @@ -28,6 +28,15 @@ + + + Always + + + Always + + + diff --git a/MP-RIOC/Program.cs b/MP-RIOC/Program.cs index c532a415..31e3342f 100644 --- a/MP-RIOC/Program.cs +++ b/MP-RIOC/Program.cs @@ -1,4 +1,7 @@ -using MP.RIOC.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; +using MP.Core.Conf; +using MP.Data; using MP.RIOC.Services; using NLog; using NLog.Web; @@ -23,10 +26,33 @@ logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}"); ConfigurationManager configuration = builder.Configuration; // REDIS setup logger.Info("Config OK"); -string confRedis = configuration.GetConnectionString("Redis"); +string confRedis = configuration.GetConnectionString("Redis") ?? "localhost:6379"; string redisSrvAddr = confRedis.Substring(0, confRedis.IndexOf(":")); logger.Info("Setup REDIS OK"); +builder.Services.Configure( + builder.Configuration.GetSection("RedisScripts")); +logger.Info("RedisScript Provider configured"); + +// Metodi principali x accesso dati +var connStr = builder.Configuration.GetConnectionString("MP.Data") + ?? throw new InvalidOperationException("ConnString 'MP.Data' mancante."); + +//builder.Services.AddMemoryCache(); + +builder.Services.AddDbContextFactory(options => + options.UseSqlServer(connStr) + .EnableSensitiveDataLogging(false) // true solo in Sviluppo + .ConfigureWarnings(w => w.Ignore(CoreEventId.ManyServiceProvidersCreatedWarning))); + +// MP.Data DbContext for Stats repositories +string utilsConnString = builder.Configuration.GetConnectionString("MP.Utils") ?? "Server=localhost;Database=MoonPro_Utils; integrated security=True; MultipleActiveResultSets=True; App=MP.IOC;"; +builder.Services.AddDbContextFactory(options => + options.UseSqlServer(utilsConnString)); + +// MP.Data Services Utils - Statistiche DB +builder.Services.AddIocDataLayer(); + // 1. Configurazione dell'invoker personalizzato (Risolve i tuoi errori) var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler { @@ -36,7 +62,7 @@ var httpClientInvoker = new HttpMessageInvoker(new SocketsHttpHandler UseCookies = false, // Correzione per il tracing: usa il propagatore corrente di sistema ActivityHeadersPropagator = DistributedContextPropagator.Current, - ConnectTimeout = TimeSpan.FromSeconds(15), + ConnectTimeout = TimeSpan.FromSeconds(30), // Gestione certificato (ignora errori per localhost/test) SslOptions = new System.Net.Security.SslClientAuthenticationOptions @@ -82,6 +108,22 @@ builder.Services.AddRazorPages(); var app = builder.Build(); +// Blocco per la migrazione automatica del DB Utils... +using (var scope = app.Services.CreateScope()) +{ + var services = scope.ServiceProvider; + try + { + var context = services.GetRequiredService(); + context.Database.Migrate(); + } + catch (Exception ex) + { + var migrateLogger = services.GetRequiredService>(); + migrateLogger.LogError(ex, "Si � verificato un errore durante l'aggiornamento del database."); + } +} + // 1. Configurazione Base Path string baseUrl = configuration.GetValue("ServerConf:BaseUrlIoc") ?? "/MP/RIOC"; app.UsePathBase(baseUrl); diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml index d91b9388..853c1cd4 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml @@ -1,25 +1,32 @@  - - MSDeploy - true - Release - Any CPU - https://iis01.egalware.com/MP/RIOC/ - false - b9188473-f4ae-4f9f-be2d-70edaace0db9 - false - https://iis01.egalware.com:8172/MsDeploy.axd - Default Web Site/MP/RIOC - - false - WMSVC - true - true - jenkins - <_SavePWD>true - <_TargetId>IISWebDeploy - net8.0 - + + MSDeploy + true + Release + Any CPU + https://iis01.egalware.com/MP/RIOC/ + false + b9188473-f4ae-4f9f-be2d-70edaace0db9 + false + https://iis01.egalware.com:8172/MsDeploy.axd + Default Web Site/MP/RIOC + + false + logs + WMSVC + true + true + jenkins + <_SavePWD>true + <_TargetId>IISWebDeploy + net8.0 + + + + dirPath + \\logs$ + + \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user index 00f79f0d..7b6e593f 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user @@ -2,7 +2,7 @@ - False|2026-05-08T07:04:47.5869659Z||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; + True|2026-05-08T07:44:50.2145461Z||;False|2026-05-08T09:42:53.1130763+02:00||;True|2026-05-08T09:34:45.8167687+02:00||;False|2026-05-08T09:34:21.4017890+02:00||;True|2026-05-08T09:24:04.8527556+02:00||;False|2026-05-08T09:04:47.5869659+02:00||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAlmCNMQ0J1UqPM+RugOqtLgAAAAACAAAAAAAQZgAAAAEAACAAAABmDmKQdTOJuzYQ2FFyU+1htQ8H/TQ+IM9D7RchZs6pvgAAAAAOgAAAAAIAACAAAABeb+XK2KUWpsQ2fiYxFKeezXYyZloQPjo9Qkmjbf+FlyAAAAAR+ckV3KTLXMIMyW4f5PBdp6Uxv5tWJ5LldbO4N+tXYUAAAACJwytTC9fJKy3wyHTlSVYRd/OdBUQ8pCweu3wSK3CGvcpgwT+VFYooELXfzgEV8l6P6FrAdGoF0gt9O3yyn1X4 diff --git a/MP-RIOC/Resources/ChangeLog-original.html b/MP-RIOC/Resources/ChangeLog-original.html new file mode 100644 index 00000000..2ec27a72 --- /dev/null +++ b/MP-RIOC/Resources/ChangeLog-original.html @@ -0,0 +1,31 @@ + + Modulo MP-RIOC +

    Versione: {{CURRENT-REL}}

    +
    Note di rilascio: +
      +
    • + Ultime modifiche: +
        {{LAST-CHANGES}}
      +
    • +
    • + v.8.16.* → +
        +
      • Ottimizzazioni varie x Redis
      • +
      +
    • +
    • + v.8.16.* → +
        +
      • Prima release dotNet 8.0 router
      • +
      +
    • +
    +
    +
    + +
    + +
    + \ No newline at end of file diff --git a/MP-RIOC/Resources/ChangeLog.html b/MP-RIOC/Resources/ChangeLog.html new file mode 100644 index 00000000..5a58a32f --- /dev/null +++ b/MP-RIOC/Resources/ChangeLog.html @@ -0,0 +1,31 @@ + + Modulo MP-RIOC +

    Versione: 8.16.2605.809

    +
    Note di rilascio: +
      +
    • + Ultime modifiche: +
        {{LAST-CHANGES}}
      +
    • +
    • + v.8.16.* → +
        +
      • Ottimizzazioni varie x Redis
      • +
      +
    • +
    • + v.8.16.* → +
        +
      • Prima release dotNet 8.0 router
      • +
      +
    • +
    +
    +
    + +
    + +
    + diff --git a/MP-RIOC/Resources/VersNum.txt b/MP-RIOC/Resources/VersNum.txt new file mode 100644 index 00000000..61a5fcb0 --- /dev/null +++ b/MP-RIOC/Resources/VersNum.txt @@ -0,0 +1 @@ +8.16.2605.809 diff --git a/MP-RIOC/Resources/logoSteamware.png b/MP-RIOC/Resources/logoSteamware.png new file mode 100644 index 0000000000000000000000000000000000000000..0958b50a1ee7f6a934e26cf55e2335d4cea6aa82 GIT binary patch literal 3402 zcmV-Q4Yl%#P)Kpl00004XF*Lt006O% z3;baP00009a7bBm000ia000ia0czHX2><{98FWQhbW?9;ba!ELWdKlNX>N2bPDNB8 zb~7$DE;85dX+r=2497`CK~#8N?VEXYRMi>4lgUh$nd~9N?8#&%lgvKZ7a%JLgb=8< zJ+;y z?+!1~Kq%Ee{3ChaIrqG~eD}UL@B6*)`|g{Wuy0Bd19D`vPgk>EW=B z{upI=m_+%c`RJTNcSmx!oT+rHqRMcI3&>_bIUd#}ol(j`U6)j)3=b3Z<}?0@#w%?H z#_YW32Q(?Y!Ej|*6;;kkq)##e$!CL50nS0aZ)B+OVzHEO83M9f=aaHy{L*~p*BJb= z3YIEgNJ6(98R3HyIED%LNjMf>C*1JAO!fMiKO#~X!rN)hNTWX{LH_1E-X zIXbg97@xsWQBh^AOV-+8RFqe;>b(6{z|ad11Te>h=K`|cc)ygasGgOM{&1aFY5~S= zK)yfDTZW1Jo%2 z6=gF<&*Yd!aZn6GFSSS3qL-2okYmC+B@YnpB7t8GSZM%S8snD}Lks>7AulTh+BaD9 zDD(S)^XmL{vQ`;ZWi$yPuarFNC5+uinH%GiV*%!m)b|3|yd=LIF^;ea6ger~a#Td8 z6iFU7MempFF>sk?yS5Rm+gP-v$9GGl8Zy;3$FXBH&{ z&Y+OHrA7JVP7ziKn{VOeTrocB2G%?|2o?Q-#DJ8fOi^Vt%lIBygSToVq}_}+DgY1z z06YQ+$AId7U@`&AE5Owe>zBqU(}oH9<>^^)K|`P$how5o<%?;;mO_vDm@|3810uX!=sgjb@rQ6#lTPR?EzbO2Q=Ugslnp{IfBf?iASlW##s>=HMm z_nx08jT5gU0+LQ?sxlS@FZCD+_J5MPJl)`hgMRZ-;&(feibi-X1FLr(LCV8}Bi1mtf6&8A47ya>P|MPG8aY{omi9v_fra?E19 zf;sA2a#e;)YL6T@zE>`f^_|_l4GJEr1MhVPkFs~z0VPJVk=?)3GRU>`R0oUC0UU}%9ldiM>yf1^xJ*AP3Vq3Y*Qq*4Ra<42h z7cO+!9Ud_@p{hnx;&i7L8ViR12vC*LDZrF~Hwxubz_m{44oT7aV0cfBRJSE3bY<-UYc_C$0M!9rK;UwO9hJZYm9BJ70 zpB>?J%0^%lzw|6ty3dmp_udy#Y_>daDKH%>GMV#=&4p1Ohcl(r?J*P|lL+nY$!yQtiZu>Wa!lK(^u~Z8eouaYDCcT8qB9p)tFuz9FZv zu0E9e(M}cZDNW63*Up`%nK5m8?4V9lUA^Jb<|(?$vhpk*1iLxk=qPo0jHT|9p=+z3 zRA=BCj24sGQIMPG;B$mLFxMOHb~sI9PGxCX&dk=C7yO!QOv=O;2gIv`%_66woC2)7 zVlMF8^55LIA`+N3b2Iniu@W)((92E?pNrS}Gh-((!27_prOZ>Rt)Dn4#%*^jX6#kQ zb^ymK=*|H|9YBTy<^z@j^ESq|7aC1Ftc8|GSa-ZwlfG*xn<#Gr-9K34MXRM~Dc8_T z-%k48Wd7rByE6}fE2wW|?C(yi?I%;3Cr5D&t3;bMpQx>>NpsrlSD^D6eShPcCUYHk zvZgrqZmxL`^*7MHt*X2tp7Xf5)(+9fdG;{g2Y^jHEUCc!2oK0EF^BeR=-%vcx<>qO z<(QNSFC7to(|TEf>LgGt{qp;&bH!e4UBu1&n42Bo32rGaw1hkhPrspV!o)e;V2QrR zn(7*+;R&=D_&QU*aUb*MRhL&5VW>MW(qRldQOx71p8;U?lWJ~EE}xE?Fe-NpI#(%wROhqX>qj?mwhmzCXyZcx!birzuImX+hvO5cY9Ec8!M zP6Fdq&5h0KHwuHoPOp)j-x`fqS`izN%}V!(!p%$J#{QHWxsUPzPwHcUQzXU+FD{IA z(3F?ANVEwN3Fxi^hSeo*Pa5qc#%t-@NBC-QZJ7}ch+2S10s=Fj%wRo{tF?>y0xHU< zqAmnjS{Oe#p{hCr-Nf37(HQtWpz}_su8C&+Nnvo--_DwCtRtZKoa1p|zmo9{Vw^R4 zN?h(Ft|uS3r&7MhoE7LDQS@*8GE`jKN_0x?Mb>&?;`8Mn1L)iMEOpfTgbtt4Us+b6 zdMhEGjX_PkPfkzoku&TYga21BgCPO(1ozl-i^ z=A2~Rw`dFRCgzGcTPUIYZcK~#u8+`n8#}|@c&R?>*F|+nQOfW|g&f0xVV>b;?kB{h zoeLE={St2aRt&J2oZ~9OTLoUtgi$j%>aB%ERm?jKY)hOrTP`{d#zg+|wV)!5S~0l9 zIgg^ZhvNX(^AN{RX}?Vop=tmRRS#qp)Pt0hoWGOy?bHu*jtfFXJ~oT>LlE)VKHzh0 zq;D0UnmAVth`T7al2WV5Y;mw8qPK@|APxkMD<|&M@ z8LwrnsjSY&Q}qGLRzhhzV=Jf&PqSE@@2f&pT~VQB?Yq%CB@E9wH~&j|;>9CSBvE}{Dwz^d_n(7;c zr?rXe;u^djhg0Nr&vNd?^jWggGpwxLF6LF0S2l$fN6?8vrp9kk&HU@PK gb@ksMRAFKN1+dqwbM3SFTL1t607*qoM6N<$g3&)|djJ3c literal 0 HcmV?d00001 diff --git a/MP-RIOC/Resources/manifest-original.xml b/MP-RIOC/Resources/manifest-original.xml new file mode 100644 index 00000000..f95e0763 --- /dev/null +++ b/MP-RIOC/Resources/manifest-original.xml @@ -0,0 +1,7 @@ + + + 1.0.0.0 + https://nexus.steamware.net/repository/SWS/{{DIRNAME}}/{{BRANCHNAME}}/{{PACKNAME}}.zip + https://nexus.steamware.net/repository/SWS/{{DIRNAME}}/{{BRANCHNAME}}/ChangeLog.html + false + diff --git a/MP-RIOC/Resources/manifest.xml b/MP-RIOC/Resources/manifest.xml new file mode 100644 index 00000000..f973210b --- /dev/null +++ b/MP-RIOC/Resources/manifest.xml @@ -0,0 +1,7 @@ + + + 8.16.2605.809 + https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip + https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html + false + diff --git a/MP-RIOC/Services/MetricsCalcService.cs b/MP-RIOC/Services/MetricsCalcService.cs index 1accf3a5..81c164c4 100644 --- a/MP-RIOC/Services/MetricsCalcService.cs +++ b/MP-RIOC/Services/MetricsCalcService.cs @@ -1,5 +1,4 @@ -using MP.RIOC.Services; -using NLog; +using NLog; using StackExchange.Redis; using System.Globalization; @@ -56,7 +55,7 @@ namespace MP.RIOC.Services protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - var interval = _config.GetValue("RouteMan:MetricCalcIntervalSeconds", 20); + var interval = _config.GetValue("RouteMan:MetricCalcIntervalSeconds", 30); while (!stoppingToken.IsCancellationRequested) { diff --git a/MP-RIOC/Services/MetricsDbFlushService.cs b/MP-RIOC/Services/MetricsDbFlushService.cs index dbbc9eb4..1b831f82 100644 --- a/MP-RIOC/Services/MetricsDbFlushService.cs +++ b/MP-RIOC/Services/MetricsDbFlushService.cs @@ -27,7 +27,7 @@ namespace MP.RIOC.Services protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - var interval = _config.GetValue("RouteMan:MetricFlushIntervalSeconds", 300); + var interval = _config.GetValue("RouteMan:MetricFlushIntervalSeconds", 180); while (!stoppingToken.IsCancellationRequested) { diff --git a/MP-RIOC/appsettings.Production.json b/MP-RIOC/appsettings.Production.json new file mode 100644 index 00000000..9d45229b --- /dev/null +++ b/MP-RIOC/appsettings.Production.json @@ -0,0 +1,24 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "CodApp": "MP.RIOC", + "ConnectionStrings": { + "MP.Data": "Server=localhost\\SQLEXPRESS;Database=MoonPro;User ID=steamware;Password=viadante16; integrated security=False; MultipleActiveResultSets=True; App=MP.RIOC;", + "MP.Utils": "Server=localhost\\SQLEXPRESS;Database=MoonPro_Utils; User ID=steamware;Password=viadante16; integrated security=False; App=MP.RIOC;", + "Redis": "localhost:6379,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false", + "RedisAdmin": "localhost:6379,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true" + }, + "ServerConf": { + "useFactory": false + }, + "RedisScripts": { + "Scripts": { + "Update": "RedisScript/RedisUpdateScript_v5.lua" + } + } +} diff --git a/MP-RIOC/appsettings.json b/MP-RIOC/appsettings.json index 0f17cadb..67137685 100644 --- a/MP-RIOC/appsettings.json +++ b/MP-RIOC/appsettings.json @@ -22,6 +22,8 @@ "logfile": { "type": "File", "fileName": "${basedir}/logs/${shortdate}.log", + "keepFileOpen": false, + "concurrentWrites": true, "archiveEvery": "Day", "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", "archiveNumbering": "DateAndSequence", @@ -50,7 +52,7 @@ }, "CodApp": "MP.RIOC", "RouteMan": { - "MetricCalcIntervalSeconds": 10, + "MetricCalcIntervalSeconds": 20, "MetricFlushIntervalSeconds": 60, "DefaultWeightOld": 100, "DefaultWeightNew": 0, @@ -71,7 +73,14 @@ "redisShortTimeCache": 30, "useFactory": true }, + "RedisScripts": { + "Scripts": { + "Update": "RedisScript/RedisUpdateScript_v6.lua" + } + }, "ConnectionStrings": { + "MP.Data": "Server=SQL2016DEV;Database=MoonPro; User ID=sa;Password=keyhammer16; integrated security=False; App=MP.IOC;", + "MP.Utils": "Server=SQL2016DEV;Database=MoonPro_Utils; User ID=sa;Password=keyhammer16; integrated security=False; App=MP.RIOC;", "Redis": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false", "RedisAdmin": "redis.ufficio:26379,serviceName=devel,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true" } diff --git a/MP.Data/DataServiceCollectionExtensions.cs b/MP.Data/DataServiceCollectionExtensions.cs index 78eb73e2..2ff337b3 100644 --- a/MP.Data/DataServiceCollectionExtensions.cs +++ b/MP.Data/DataServiceCollectionExtensions.cs @@ -14,15 +14,6 @@ namespace MP.Data { public static IServiceCollection AddIocDataLayer(this IServiceCollection services) { - //// DbContextFactory: preferibile in Blazor Server e scenari concorrenti - //services.AddDbContextFactory(options => - // options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))); - - //// servizi preliminari - ////services.TryAddSingleton(redisConn); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - // Repository Singleton services.TryAddSingleton(); @@ -40,14 +31,6 @@ namespace MP.Data services.TryAddScoped(); services.TryAddScoped(); - //// aggiunta servizi finali Singleton... - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - //services.TryAddSingleton(); - - return services; } } diff --git a/MP.IOC/Resources/ChangeLog-original.html b/MP.IOC/Resources/ChangeLog-original.html index fa79ade6..cf07d7e9 100644 --- a/MP.IOC/Resources/ChangeLog-original.html +++ b/MP.IOC/Resources/ChangeLog-original.html @@ -25,7 +25,7 @@
\ No newline at end of file diff --git a/MP.IOC/appsettings.json b/MP.IOC/appsettings.json index 0600459a..e24667d4 100644 --- a/MP.IOC/appsettings.json +++ b/MP.IOC/appsettings.json @@ -2,7 +2,6 @@ "Logging": { "LogLevel": { "Default": "Information", - "Yarp": "Debug", "Microsoft.AspNetCore": "Warning", "Microsoft.EntityFrameworkCore.Database.Command": "Warning", "Microsoft.EntityFrameworkCore.Infrastructure": "Warning", @@ -27,6 +26,8 @@ "logfile": { "type": "File", "fileName": "${basedir}/logs/${shortdate}.log", + "keepFileOpen": false, + "concurrentWrites": true, "archiveEvery": "Day", "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", "archiveNumbering": "DateAndSequence", From 46e97f586b02347c3d50d472093e820b7afc47c7 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 09:49:34 +0200 Subject: [PATCH 09/13] Aggiunto placeholder file, update pubxml --- MP-RIOC/MP.RIOC.csproj | 3 +++ MP-RIOC/Properties/PublishProfiles/IIS01.pubxml | 7 ------- MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user | 2 +- MP-RIOC/logs/.placeholder.file | 0 4 files changed, 4 insertions(+), 8 deletions(-) create mode 100644 MP-RIOC/logs/.placeholder.file diff --git a/MP-RIOC/MP.RIOC.csproj b/MP-RIOC/MP.RIOC.csproj index 5ee72a40..1375b5dc 100644 --- a/MP-RIOC/MP.RIOC.csproj +++ b/MP-RIOC/MP.RIOC.csproj @@ -29,6 +29,9 @@ + + Always + Always diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml index 853c1cd4..c7fdf4fd 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml @@ -14,7 +14,6 @@ Default Web Site/MP/RIOC false - logs WMSVC true true @@ -23,10 +22,4 @@ <_TargetId>IISWebDeploy net8.0 - - - dirPath - \\logs$ - - \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user index 7b6e593f..04d8f2f6 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user @@ -2,7 +2,7 @@ - True|2026-05-08T07:44:50.2145461Z||;False|2026-05-08T09:42:53.1130763+02:00||;True|2026-05-08T09:34:45.8167687+02:00||;False|2026-05-08T09:34:21.4017890+02:00||;True|2026-05-08T09:24:04.8527556+02:00||;False|2026-05-08T09:04:47.5869659+02:00||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; + True|2026-05-08T07:49:12.5533796Z||;True|2026-05-08T09:47:48.2838701+02:00||;True|2026-05-08T09:46:52.6514721+02:00||;True|2026-05-08T09:46:30.4584308+02:00||;True|2026-05-08T09:46:00.3430830+02:00||;True|2026-05-08T09:44:50.2145461+02:00||;False|2026-05-08T09:42:53.1130763+02:00||;True|2026-05-08T09:34:45.8167687+02:00||;False|2026-05-08T09:34:21.4017890+02:00||;True|2026-05-08T09:24:04.8527556+02:00||;False|2026-05-08T09:04:47.5869659+02:00||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAlmCNMQ0J1UqPM+RugOqtLgAAAAACAAAAAAAQZgAAAAEAACAAAABmDmKQdTOJuzYQ2FFyU+1htQ8H/TQ+IM9D7RchZs6pvgAAAAAOgAAAAAIAACAAAABeb+XK2KUWpsQ2fiYxFKeezXYyZloQPjo9Qkmjbf+FlyAAAAAR+ckV3KTLXMIMyW4f5PBdp6Uxv5tWJ5LldbO4N+tXYUAAAACJwytTC9fJKy3wyHTlSVYRd/OdBUQ8pCweu3wSK3CGvcpgwT+VFYooELXfzgEV8l6P6FrAdGoF0gt9O3yyn1X4 diff --git a/MP-RIOC/logs/.placeholder.file b/MP-RIOC/logs/.placeholder.file new file mode 100644 index 00000000..e69de29b From 9c1adee62aff22d9b06f4c66889344698ec891f7 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 11:24:36 +0200 Subject: [PATCH 10/13] Rimozione YARP da sito IOC: test in prod --- Directory.Packages.props | 5 +- MP-RIOC/MP.RIOC.csproj | 2 +- .../Properties/PublishProfiles/IIS01.pubxml | 9 +- .../PublishProfiles/IIS01.pubxml.user | 2 +- MP-RIOC/Resources/ChangeLog.html | 2 +- MP-RIOC/Resources/VersNum.txt | 2 +- MP-RIOC/Resources/manifest.xml | 2 +- MP-RIOC/Services/MetricsDbFlushService.cs | 14 + MP-RIOC/appsettings.Development.json | 5 + MP-RIOC/appsettings.Production.json | 4 +- MP-RIOC/appsettings.json | 7 +- MP.IOC/MP.IOC.csproj | 10 +- MP.IOC/Program.cs | 94 +---- .../Properties/PublishProfiles/IIS01.pubxml | 6 + .../PublishProfiles/IIS01.pubxml.user | 2 +- MP.IOC/RedisScript/RedisUpdateScript_v5.lua | 33 -- MP.IOC/RedisScript/RedisUpdateScript_v6.lua | 33 -- MP.IOC/Resources/ChangeLog.html | 4 +- MP.IOC/Resources/VersNum.txt | 2 +- MP.IOC/Resources/manifest.xml | 2 +- MP.IOC/Services/LuaScriptProvider.cs | 43 -- MP.IOC/Services/MetricsCalcService.cs | 223 ----------- MP.IOC/Services/MetricsDbFlushService.cs | 368 ------------------ MP.IOC/Services/PreserveBodyTransformer.cs | 41 -- MP.IOC/Services/RouteManager.cs | 173 -------- MP.IOC/Services/RouteStatsManager.cs | 71 ---- MP.IOC/appsettings.Production.json | 22 -- MP.IOC/appsettings.json | 29 +- 28 files changed, 56 insertions(+), 1154 deletions(-) delete mode 100644 MP.IOC/RedisScript/RedisUpdateScript_v5.lua delete mode 100644 MP.IOC/RedisScript/RedisUpdateScript_v6.lua delete mode 100644 MP.IOC/Services/LuaScriptProvider.cs delete mode 100644 MP.IOC/Services/MetricsCalcService.cs delete mode 100644 MP.IOC/Services/MetricsDbFlushService.cs delete mode 100644 MP.IOC/Services/PreserveBodyTransformer.cs delete mode 100644 MP.IOC/Services/RouteManager.cs delete mode 100644 MP.IOC/Services/RouteStatsManager.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 00d6ceed..f5b8b499 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,8 +7,8 @@ - - + + @@ -37,6 +37,7 @@ + diff --git a/MP-RIOC/MP.RIOC.csproj b/MP-RIOC/MP.RIOC.csproj index 1375b5dc..ad2a6515 100644 --- a/MP-RIOC/MP.RIOC.csproj +++ b/MP-RIOC/MP.RIOC.csproj @@ -5,7 +5,7 @@ enable enable MP.RIOC - 8.16.2605.809 + 8.16.2605.811 diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml index c7fdf4fd..9278d2ad 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml @@ -13,7 +13,7 @@ https://iis01.egalware.com:8172/MsDeploy.axd Default Web Site/MP/RIOC - false + false WMSVC true true @@ -21,5 +21,12 @@ <_SavePWD>true <_TargetId>IISWebDeploy net8.0 + win-x64 + + + filePath + logs\\.*\.log$ + + \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user index 04d8f2f6..3b31eede 100644 --- a/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user +++ b/MP-RIOC/Properties/PublishProfiles/IIS01.pubxml.user @@ -2,7 +2,7 @@ - True|2026-05-08T07:49:12.5533796Z||;True|2026-05-08T09:47:48.2838701+02:00||;True|2026-05-08T09:46:52.6514721+02:00||;True|2026-05-08T09:46:30.4584308+02:00||;True|2026-05-08T09:46:00.3430830+02:00||;True|2026-05-08T09:44:50.2145461+02:00||;False|2026-05-08T09:42:53.1130763+02:00||;True|2026-05-08T09:34:45.8167687+02:00||;False|2026-05-08T09:34:21.4017890+02:00||;True|2026-05-08T09:24:04.8527556+02:00||;False|2026-05-08T09:04:47.5869659+02:00||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; + True|2026-05-08T09:24:06.9798007Z||;True|2026-05-08T11:21:55.9425582+02:00||;True|2026-05-08T11:20:08.8391895+02:00||;True|2026-05-08T11:10:23.1435148+02:00||;True|2026-05-08T11:06:12.1010825+02:00||;True|2026-05-08T10:36:29.2623112+02:00||;False|2026-05-08T10:35:26.8348462+02:00||;True|2026-05-08T10:03:20.5314448+02:00||;True|2026-05-08T09:56:52.4040695+02:00||;True|2026-05-08T09:51:01.5094407+02:00||;False|2026-05-08T09:50:28.6127819+02:00||;False|2026-05-08T09:50:16.2479705+02:00||;True|2026-05-08T09:49:12.5533796+02:00||;True|2026-05-08T09:47:48.2838701+02:00||;True|2026-05-08T09:46:52.6514721+02:00||;True|2026-05-08T09:46:30.4584308+02:00||;True|2026-05-08T09:46:00.3430830+02:00||;True|2026-05-08T09:44:50.2145461+02:00||;False|2026-05-08T09:42:53.1130763+02:00||;True|2026-05-08T09:34:45.8167687+02:00||;False|2026-05-08T09:34:21.4017890+02:00||;True|2026-05-08T09:24:04.8527556+02:00||;False|2026-05-08T09:04:47.5869659+02:00||;False|2026-05-08T09:01:28.1405561+02:00||;False|2026-05-08T09:00:37.4358771+02:00||;False|2026-05-08T08:58:53.9394322+02:00||;False|2026-05-08T08:57:18.3710854+02:00||;False|2026-05-08T08:56:39.0642155+02:00||;True|2026-05-08T08:36:15.4113336+02:00||;False|2026-05-08T08:35:31.1992313+02:00||;False|2026-05-08T08:34:56.3101908+02:00||;False|2026-05-08T08:34:41.2943768+02:00||;True|2026-05-08T08:28:29.2065443+02:00||; AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAlmCNMQ0J1UqPM+RugOqtLgAAAAACAAAAAAAQZgAAAAEAACAAAABmDmKQdTOJuzYQ2FFyU+1htQ8H/TQ+IM9D7RchZs6pvgAAAAAOgAAAAAIAACAAAABeb+XK2KUWpsQ2fiYxFKeezXYyZloQPjo9Qkmjbf+FlyAAAAAR+ckV3KTLXMIMyW4f5PBdp6Uxv5tWJ5LldbO4N+tXYUAAAACJwytTC9fJKy3wyHTlSVYRd/OdBUQ8pCweu3wSK3CGvcpgwT+VFYooELXfzgEV8l6P6FrAdGoF0gt9O3yyn1X4 diff --git a/MP-RIOC/Resources/ChangeLog.html b/MP-RIOC/Resources/ChangeLog.html index 5a58a32f..3c179208 100644 --- a/MP-RIOC/Resources/ChangeLog.html +++ b/MP-RIOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-RIOC -

Versione: 8.16.2605.809

+

Versione: 8.16.2605.811


Note di rilascio:
  • diff --git a/MP-RIOC/Resources/VersNum.txt b/MP-RIOC/Resources/VersNum.txt index 61a5fcb0..ddc2c22a 100644 --- a/MP-RIOC/Resources/VersNum.txt +++ b/MP-RIOC/Resources/VersNum.txt @@ -1 +1 @@ -8.16.2605.809 +8.16.2605.811 diff --git a/MP-RIOC/Resources/manifest.xml b/MP-RIOC/Resources/manifest.xml index f973210b..bb27fee8 100644 --- a/MP-RIOC/Resources/manifest.xml +++ b/MP-RIOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 8.16.2605.809 + 8.16.2605.811 https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html false diff --git a/MP-RIOC/Services/MetricsDbFlushService.cs b/MP-RIOC/Services/MetricsDbFlushService.cs index 1b831f82..ba6444b2 100644 --- a/MP-RIOC/Services/MetricsDbFlushService.cs +++ b/MP-RIOC/Services/MetricsDbFlushService.cs @@ -96,6 +96,7 @@ namespace MP.RIOC.Services $"{_redisBaseKey}:stats:days:*" }; + var batch = _db.CreateBatch(); foreach (var pattern in patternsToScan) { // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer @@ -121,7 +122,12 @@ namespace MP.RIOC.Services // Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione if (isExpired && deleteConfirmed) { + // 1. Segna la chiave Hash (Dati) per l'eliminazione keysToDelete.Add(sKey); + + // 2. CORREZIONE: Devi rimuovere il riferimento dal Sorted Set (Indice) + // Usiamo il batch per essere efficienti + _ = batch.SortedSetRemoveAsync(indexKey, statKey); } // Recupero dati dalla Hash @@ -161,6 +167,7 @@ namespace MP.RIOC.Services } } } + batch.Execute(); } // --- FASE UPSERT DB --- @@ -218,6 +225,7 @@ namespace MP.RIOC.Services $"{_redisBaseKey}:stats:hours:*" }; + var batch = _db.CreateBatch(); foreach (var pattern in patternsToScan) { // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer @@ -243,7 +251,12 @@ namespace MP.RIOC.Services // Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione if (isExpired && deleteConfirmed) { + // 1. Segna la chiave Hash (Dati) per l'eliminazione keysToDelete.Add(sKey); + + // 2. CORREZIONE: Devi rimuovere il riferimento dal Sorted Set (Indice) + // Usiamo il batch per essere efficienti + _ = batch.SortedSetRemoveAsync(indexKey, statKey); } // Recupero dati dalla Hash @@ -283,6 +296,7 @@ namespace MP.RIOC.Services } } } + batch.Execute(); } // --- FASE UPSERT DB --- diff --git a/MP-RIOC/appsettings.Development.json b/MP-RIOC/appsettings.Development.json index 0c208ae9..b14a27c9 100644 --- a/MP-RIOC/appsettings.Development.json +++ b/MP-RIOC/appsettings.Development.json @@ -4,5 +4,10 @@ "Default": "Information", "Microsoft.AspNetCore": "Warning" } + }, + "ServerConf": { + "useFactory": false, + "OldApiUrl": "http://localhost/MP/IO/IOB/", + "NewApiUrl": "http://localhost/MP/IOC/api/IOB/" } } diff --git a/MP-RIOC/appsettings.Production.json b/MP-RIOC/appsettings.Production.json index 9d45229b..7a752d3d 100644 --- a/MP-RIOC/appsettings.Production.json +++ b/MP-RIOC/appsettings.Production.json @@ -14,7 +14,9 @@ "RedisAdmin": "localhost:6379,DefaultDatabase=5,connectTimeout=5000,syncTimeout=5000,asyncTimeout=5000,abortConnect=false,ssl=false,allowAdmin=true" }, "ServerConf": { - "useFactory": false + "useFactory": false, + "OldApiUrl": "http://maposrv.egalware.com/MP/IO/IOB/", + "NewApiUrl": "http://maposrv.egalware.com/MP/IOC/api/IOB/" }, "RedisScripts": { "Scripts": { diff --git a/MP-RIOC/appsettings.json b/MP-RIOC/appsettings.json index 67137685..590b8fb4 100644 --- a/MP-RIOC/appsettings.json +++ b/MP-RIOC/appsettings.json @@ -22,8 +22,7 @@ "logfile": { "type": "File", "fileName": "${basedir}/logs/${shortdate}.log", - "keepFileOpen": false, - "concurrentWrites": true, + "keepFileOpen": false, "archiveEvery": "Day", "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", "archiveNumbering": "DateAndSequence", @@ -62,8 +61,8 @@ "RoutePath": "/api/IOB", "HttpVersion": "1.1", "HttpVersionPolicy": "RequestVersionExact", - "OldApiUrl": "https://iis01.egalware.com/MP/IO/IOB/", - "NewApiUrl": "https://iis01.egalware.com/MP/IOC/api/IOB/", + "OldApiUrl": "http://iis01.egalware.com/MP/IO/IOB/", + "NewApiUrl": "http://iis01.egalware.com/MP/IOC/api/IOB/", "BaseUrlIoc": "/MP/RIOC/", "MpIoNS": "MoonPro:SQL2016DEV:MoonPro", "RedisBaseKey": "MP-IOC", diff --git a/MP.IOC/MP.IOC.csproj b/MP.IOC/MP.IOC.csproj index cc421694..0a714ad2 100644 --- a/MP.IOC/MP.IOC.csproj +++ b/MP.IOC/MP.IOC.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 8.16.2605.808 + 8.16.2605.811 @@ -33,8 +33,8 @@ + - @@ -48,12 +48,6 @@ Always - - Always - - - Always - diff --git a/MP.IOC/Program.cs b/MP.IOC/Program.cs index 62c3e23e..59a5e764 100644 --- a/MP.IOC/Program.cs +++ b/MP.IOC/Program.cs @@ -1,4 +1,3 @@ -using Microsoft.AspNetCore.Http.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.OpenApi.Models; @@ -10,7 +9,6 @@ using MP.IOC.Services; using NLog; using NLog.Web; using StackExchange.Redis; -using System.Net; using System.Reflection; var builder = WebApplication.CreateBuilder(args); @@ -29,47 +27,11 @@ logger.Info($"Current ASPNETCORE_ENVIRONMENT: {env.EnvironmentName}"); ConfigurationManager configuration = builder.Configuration; // REDIS setup logger.Info("Config OK"); -string confRedis = configuration.GetConnectionString("Redis"); +string confRedis = configuration.GetConnectionString("Redis") ?? "localhost:6379"; string redisSrvAddr = confRedis.Substring(0, confRedis.IndexOf(":")); logger.Info("Setup REDIS OK"); -// YARP base config -builder.Services.AddReverseProxy() - .LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")) - .ConfigureHttpClient((context, handler) => - { - // Se sei in sviluppo o sul server con problemi di certificato - handler.SslOptions.RemoteCertificateValidationCallback = (sender, cert, chain, sslPolicyErrors) => - { - // ATTENZIONE: In produzione filtra per hostname o usa cautela - return true; - }; - }); - -builder.Services.AddHttpForwarder(); - -// HttpMessageInvoker (SocketsHttpHandler) -builder.Services.AddSingleton(sp => -{ - var handler = new SocketsHttpHandler - { - AllowAutoRedirect = false, - UseCookies = false, - PooledConnectionLifetime = TimeSpan.FromMinutes(5), - AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate - }; - - // Accetta certificati non validi (dev only) - handler.SslOptions = new System.Net.Security.SslClientAuthenticationOptions - { - RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true - }; - - return new HttpMessageInvoker(handler); -}); -logger.Info("YARP reverse proxy configured"); - builder.Services.Configure( builder.Configuration.GetSection("RedisScripts")); logger.Info("RedisScript Provider configured"); @@ -93,13 +55,6 @@ builder.Services.AddDbContextFactory(options => // MP.Data Services Utils - Statistiche DB builder.Services.AddIocDataLayer(); -// base services -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); -builder.Services.AddHostedService(); -builder.Services.AddHostedService(); -builder.Services.AddSingleton(); - // Registra i servizi per Blazor builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); @@ -107,8 +62,6 @@ builder.Services.AddRazorComponents() //builder.Services.AddHealthChecks() // .AddSqlServer(builder.Configuration.GetConnectionString("CoreDb")); - - // generic controller builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle @@ -147,59 +100,14 @@ else } logger.Info($"Weight service configured | use Redis: {weightOnRedis}"); -// RouteManager registration (singleton) -builder.Services.AddSingleton(); -logger.Info("Singleton Route Manager registered"); var app = builder.Build(); -// Blocco per la migrazione automatica del DB Utils... -using (var scope = app.Services.CreateScope()) -{ - var services = scope.ServiceProvider; - try - { - var context = services.GetRequiredService(); - context.Database.Migrate(); - } - catch (Exception ex) - { - var migrateLogger = services.GetRequiredService>(); - migrateLogger.LogError(ex, "Si � verificato un errore durante l'aggiornamento del database."); - } -} - - // aggiunt base URL x routing corretto string baseUrl = configuration.GetValue("ServerConf:BaseUrlIoc") ?? "/MP/"; app.UsePathBase(baseUrl); -string routePath = configuration.GetValue("ServerConf:RoutePath") ?? "/api/RIOB"; -string fullPath = $"{baseUrl}{routePath}".Replace("//", "/"); logger.Info($"BaseUrl: {baseUrl}"); -app.Use(async (ctx, next) => -{ - logger.Debug($"Incoming request PathBase='{ctx.Request.PathBase}' Path='{ctx.Request.Path}' RawTarget='{ctx.Request.GetEncodedUrl()}'"); - await next(); -}); - - -// parametrizzare?!?!? -app.MapWhen(ctx => - ctx.Request.Path.StartsWithSegments(fullPath, StringComparison.OrdinalIgnoreCase) || - ctx.Request.Path.StartsWithSegments(routePath, StringComparison.OrdinalIgnoreCase) || - (ctx.Request.PathBase.HasValue && ctx.Request.Path.StartsWithSegments(routePath, StringComparison.OrdinalIgnoreCase)), - builder => - { - builder.Run(async ctx => - { - var routeManager = ctx.RequestServices.GetRequiredService(); - await routeManager.HandleAsync(ctx); - }); - }); - -logger.Info("App: route Mapped"); - // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment() || app.Environment.IsStaging()) diff --git a/MP.IOC/Properties/PublishProfiles/IIS01.pubxml b/MP.IOC/Properties/PublishProfiles/IIS01.pubxml index d7ddcd79..823f36eb 100644 --- a/MP.IOC/Properties/PublishProfiles/IIS01.pubxml +++ b/MP.IOC/Properties/PublishProfiles/IIS01.pubxml @@ -25,4 +25,10 @@ https://go.microsoft.com/fwlink/?LinkID=208121. net8.0 win-x64 + + + filePath + logs\\.*\.log$ + + \ No newline at end of file diff --git a/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user b/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user index 40aee328..f06abead 100644 --- a/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user +++ b/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user @@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAdCoESbxryUqXde3FfEOCTwAAAAACAAAAAAADZgAAwAAAABAAAAAFrH1vByj45Qn06hO/OH6tAAAAAASAAACgAAAAEAAAAIUK5NiEpc4lc11Op6/CLx8YAAAAMecN12fzIN9e3E8R/nu0ATe2PAsMy7M8FAAAAKbUyki2vkSFehjbpB8wCVVVa055 - True|2026-04-17T17:09:49.4900600Z||;True|2026-04-03T12:55:15.0251473+02:00||;True|2026-04-03T10:03:02.5833820+02:00||;True|2026-04-03T09:55:55.5274684+02:00||;True|2026-04-03T09:52:44.9063312+02:00||;False|2026-04-03T09:45:49.5943015+02:00||;True|2024-11-04T08:56:17.3071781+01:00||;True|2023-02-14T17:41:26.3850692+01:00||;True|2023-02-14T17:31:39.4933399+01:00||; + False|2026-05-08T09:06:51.2202890Z||;True|2026-05-08T10:37:55.0049896+02:00||;False|2026-05-08T10:37:05.1546068+02:00||;False|2026-05-08T10:36:33.0311629+02:00||;False|2026-05-08T10:35:55.7742437+02:00||;True|2026-04-17T19:09:49.4900600+02:00||;True|2026-04-03T12:55:15.0251473+02:00||;True|2026-04-03T10:03:02.5833820+02:00||;True|2026-04-03T09:55:55.5274684+02:00||;True|2026-04-03T09:52:44.9063312+02:00||;False|2026-04-03T09:45:49.5943015+02:00||;True|2024-11-04T08:56:17.3071781+01:00||;True|2023-02-14T17:41:26.3850692+01:00||;True|2023-02-14T17:31:39.4933399+01:00||; \ No newline at end of file diff --git a/MP.IOC/RedisScript/RedisUpdateScript_v5.lua b/MP.IOC/RedisScript/RedisUpdateScript_v5.lua deleted file mode 100644 index 1b4a2477..00000000 --- a/MP.IOC/RedisScript/RedisUpdateScript_v5.lua +++ /dev/null @@ -1,33 +0,0 @@ --- RedisUpdateScript_v5 -local key = KEYS[1] -local countInc = tonumber(ARGV[1]) or 0 -local totalMsInc = tonumber(ARGV[2]) or 0 -local newMax = tonumber(ARGV[3]) -local newMin = tonumber(ARGV[4]) -local sentinel = tonumber(ARGV[5]) - --- Incrementi base -redis.call('HINCRBY', key, 'count', countInc) -redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) - --- MAX -local currentMaxStr = redis.call('HGET', key, 'maxMs') -local currentMax = tonumber(currentMaxStr) - -if newMax ~= nil and newMax < sentinel then - if currentMax == nil or newMax > currentMax then - redis.call('HSET', key, 'maxMs', newMax) - end -end - --- MIN -local currentMinStr = redis.call('HGET', key, 'minMs') -local currentMin = tonumber(currentMinStr) - -if newMin ~= nil and newMin < sentinel then - if currentMin == nil or newMin < currentMin then - redis.call('HSET', key, 'minMs', newMin) - end -end - -return 1 diff --git a/MP.IOC/RedisScript/RedisUpdateScript_v6.lua b/MP.IOC/RedisScript/RedisUpdateScript_v6.lua deleted file mode 100644 index 230ed7f9..00000000 --- a/MP.IOC/RedisScript/RedisUpdateScript_v6.lua +++ /dev/null @@ -1,33 +0,0 @@ --- RedisUpdateScript_v6 -local key = KEYS[1] -local countInc = tonumber(ARGV[1]) or 0 -local totalMsInc = tonumber(ARGV[2]) or 0 -local newMax = tonumber(ARGV[3]) -local newMin = tonumber(ARGV[4]) -local sentinel = tonumber(ARGV[5]) - --- Incrementi -redis.call('HINCRBY', key, 'count', countInc) -redis.call('HINCRBYFLOAT', key, 'totalMs', totalMsInc) - --- MAX -local currentMaxStr = redis.call('HGET', key, 'maxMs') -local currentMax = tonumber(currentMaxStr) - -if newMax ~= nil and newMax < sentinel then - if currentMax == nil or newMax > currentMax then - redis.call('HSET', key, 'maxMs', tostring(newMax)) - end -end - --- MIN -local currentMinStr = redis.call('HGET', key, 'minMs') -local currentMin = tonumber(currentMinStr) - -if newMin ~= nil and newMin < sentinel then - if currentMin == nil or newMin < currentMin then - redis.call('HSET', key, 'minMs', tostring(newMin)) - end -end - -return 1 diff --git a/MP.IOC/Resources/ChangeLog.html b/MP.IOC/Resources/ChangeLog.html index c2c936a0..4ba1f793 100644 --- a/MP.IOC/Resources/ChangeLog.html +++ b/MP.IOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-IOC -

    Versione: 8.16.2605.808

    +

    Versione: 8.16.2605.811


    Note di rilascio:
    • @@ -25,7 +25,7 @@ diff --git a/MP.IOC/Resources/VersNum.txt b/MP.IOC/Resources/VersNum.txt index a37f9c6b..ddc2c22a 100644 --- a/MP.IOC/Resources/VersNum.txt +++ b/MP.IOC/Resources/VersNum.txt @@ -1 +1 @@ -8.16.2605.808 +8.16.2605.811 diff --git a/MP.IOC/Resources/manifest.xml b/MP.IOC/Resources/manifest.xml index 2f8a10b2..bb27fee8 100644 --- a/MP.IOC/Resources/manifest.xml +++ b/MP.IOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 8.16.2605.808 + 8.16.2605.811 https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html false diff --git a/MP.IOC/Services/LuaScriptProvider.cs b/MP.IOC/Services/LuaScriptProvider.cs deleted file mode 100644 index c7eb1e58..00000000 --- a/MP.IOC/Services/LuaScriptProvider.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.Extensions.Options; -using MP.Core.Conf; - -namespace MP.IOC.Services -{ - public sealed class LuaScriptProvider - { - private readonly Dictionary _scripts; - - public IReadOnlyDictionary Scripts => _scripts; - - public LuaScriptProvider( - IOptions cfg, - IWebHostEnvironment env) - { - _scripts = new Dictionary(StringComparer.OrdinalIgnoreCase); - - foreach (var kv in cfg.Value.Scripts) - { - var name = kv.Key; - var relativePath = kv.Value; - - var fullPath = Path.Combine(env.ContentRootPath, relativePath); - - if (!File.Exists(fullPath)) - throw new FileNotFoundException($"Script Lua non trovato: {fullPath}"); - - var content = File.ReadAllText(fullPath); - - _scripts[name] = content; - } - } - - public string Get(string name) - { - if (!_scripts.TryGetValue(name, out var script)) - throw new KeyNotFoundException($"Script Lua '{name}' non trovato."); - - return script; - } - } - -} diff --git a/MP.IOC/Services/MetricsCalcService.cs b/MP.IOC/Services/MetricsCalcService.cs deleted file mode 100644 index b9918619..00000000 --- a/MP.IOC/Services/MetricsCalcService.cs +++ /dev/null @@ -1,223 +0,0 @@ -using NLog; -using StackExchange.Redis; -using System.Globalization; - -namespace MP.IOC.Services -{ - public class MetricsCalcService : BackgroundService - { - #region Public Constructors - - /// - /// Metodo x calcolo metriche/statistiche di esecuzone realtime - /// - /// - /// - /// - public MetricsCalcService(RouteStatsManager stats, - LuaScriptProvider luaProvider, - IConfiguration config, - IConnectionMultiplexer mux) - { - _stats = stats; - _config = config; - _db = mux.GetDatabase(); - _redisBaseKey = _config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; - _updateScript = luaProvider.Get("Update"); - } - - #endregion Public Constructors - - #region Protected Methods - - /// - /// Valore SentinelValue x valore minimo in reids/Lua - /// Usiamo un valore molto grande (es. 10 milioni di secondi = ~115 giorni). - /// E' impossibile che una singola richiesta HTTP duri cos� tanto, quindi usarlo come "SentinelValue" � sicuro. - /// - private const string SentinelValue = "999999999"; - - /// - /// Script update Redis in Lua, recuperato dal provider script. - /// Lo script gestisce l'incremento e la logica condizionale per Min/Max in un'unica operazione atomica. - /// - private readonly string _updateScript; - - - // Classe di supporto per l'aggregazione locale dei valori Daily - private class AggregatedStats - { - public long Count; - public double TotalMs; - public double MaxMs; - public double MinMs = double.MaxValue; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - var interval = _config.GetValue("RouteMan:MetricCalcIntervalSeconds", 20); - - while (!stoppingToken.IsCancellationRequested) - { - try - { - await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken); - var snapshot = _stats.Snapshot(); - if (snapshot.Count == 0) continue; - - var adesso = DateTime.Now; - var hourStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, adesso.Hour, 0, 0); - var dayStart = new DateTime(adesso.Year, adesso.Month, adesso.Day, 0, 0, 0); - - if (_db == null) continue; - - var batch = _db.CreateBatch(); - var tasks = new List(); - - // Dizionario per aggregare i valori Daily prima di inviarli a Redis - var dailyAggregates = new Dictionary(); - - foreach (var kv in snapshot) - { - string dest = "IO"; - string method = "NA"; - string machineId = "ALL"; - string rawKey = kv.Key; - if (rawKey.Contains("|")) - { - var splitVal = rawKey.Split("|"); - dest = splitVal[0]; - if (splitVal.Length > 1) - method = splitVal[1]; - if (splitVal.Length > 2) - machineId = splitVal[2]; - } - else - { - method = rawKey; - } - - var stat = kv.Value; - var count = Interlocked.Read(ref stat.Count); - var totalMs = stat.TotalDuration.TotalMilliseconds; - var maxMs = stat.MaxDuration.TotalMilliseconds; - // Se la durata è ancora MaxValue, usiamo la SentinelValue per dire a Redis "non aggiornare" - var minMs = (stat.MinDuration == TimeSpan.MaxValue) - ? double.Parse(SentinelValue) - : stat.MinDuration.TotalMilliseconds; - - // --- LOGICA HOURLY (Per ogni route/method e' una chiave distinta) --- - var hourKey = HourBucketKey(dest, method, hourStart); - var hoursIndex = HoursIndexKey(dest, method); - var hourScore = ToEpochSeconds(hourStart); - - // Usiamo lo script Lua per l'aggiornamento atomico dell'ora - tasks.Add(batch.ScriptEvaluateAsync(_updateScript, - new RedisKey[] { hourKey }, - new RedisValue[] { - count.ToString(CultureInfo.InvariantCulture), - totalMs.ToString(CultureInfo.InvariantCulture), - maxMs.ToString(CultureInfo.InvariantCulture), - minMs.ToString(CultureInfo.InvariantCulture), - SentinelValue - })); - - tasks.Add(batch.SortedSetAddAsync(hoursIndex, hourKey, hourScore)); - - // --- LOGICA DAILY (Aggregazione locale per evitare sovrascritture nel loop) --- - var dayKey = DayBucketKey(dest, machineId, dayStart); - var daysIndex = DaysIndexKey(dest, machineId); - var dayScore = ToEpochSeconds(dayStart); - - if (!dailyAggregates.TryGetValue(dayKey, out var agg)) - { - agg = new AggregatedStats(); - dailyAggregates[dayKey] = agg; - } - - agg.Count += count; - agg.TotalMs += totalMs; - agg.MaxMs = Math.Max(agg.MaxMs, maxMs); - if (minMs != double.MaxValue) - agg.MinMs = Math.Min(agg.MinMs, minMs); - - // Aggiungiamo l'indice al batch - tasks.Add(batch.SortedSetAddAsync(daysIndex, dayKey, dayScore)); - } - - // --- INVIO AGGREGATI DAILY A REDIS --- - foreach (var dayKV in dailyAggregates) - { - var key = dayKV.Key; - var agg = dayKV.Value; - // Se agg.MinMs è inizializzato a double.MaxValue, lo portiamo alla SentinelValue - double finalMin = (agg.MinMs == double.MaxValue || agg.MinMs >= double.Parse(SentinelValue)) - ? double.Parse(SentinelValue) - : agg.MinMs; - - tasks.Add(batch.ScriptEvaluateAsync(_updateScript, - new RedisKey[] { key }, - new RedisValue[] { - agg.Count.ToString(CultureInfo.InvariantCulture), - agg.TotalMs.ToString(CultureInfo.InvariantCulture), - agg.MaxMs.ToString(CultureInfo.InvariantCulture), - finalMin.ToString(CultureInfo.InvariantCulture), - SentinelValue - })); - } - - // Esegui tutto il batch in un unico round-trip - batch.Execute(); - await Task.WhenAll(tasks); - - _stats.Clear(); - } - catch (TaskCanceledException) { } - catch (Exception ex) - { - Log.Error(ex, "Error flushing metrics"); - } - } - } - #endregion Protected Methods - - #region Private Fields - - private static string _redisBaseKey = ""; - private static Logger Log = LogManager.GetCurrentClassLogger(); - private readonly IConfiguration _config; - private readonly IDatabase _db; - private readonly RouteStatsManager _stats; - - #endregion Private Fields - - #region Private Methods - - private static string DayBucketKey(string dest, string machId, DateTime dtRif) - { - return $"{_redisBaseKey}:stats:day:{dest}:{machId}:{dtRif.ToString("yyyyMMdd", CultureInfo.InvariantCulture)}"; - } - - private static string DaysIndexKey(string dest, string machId) - { - return $"{_redisBaseKey}:stats:days:{dest}:{machId}"; - } - - private static string HourBucketKey(string dest, string method, DateTime dtRif) - { - return $"{_redisBaseKey}:stats:hour:{dest}:{method}:{dtRif.ToString("yyyyMMddHH", CultureInfo.InvariantCulture)}"; - } - - private static string HoursIndexKey(string dest, string method) - { - return $"{_redisBaseKey}:stats:hours:{dest}:{method}"; - } - - private static long ToEpochSeconds(DateTime dt) - { - return new DateTimeOffset(dt).ToUnixTimeSeconds(); - } - - #endregion Private Methods - } -} diff --git a/MP.IOC/Services/MetricsDbFlushService.cs b/MP.IOC/Services/MetricsDbFlushService.cs deleted file mode 100644 index 4aef9eb7..00000000 --- a/MP.IOC/Services/MetricsDbFlushService.cs +++ /dev/null @@ -1,368 +0,0 @@ -using MP.Data.DbModels.Utils; -using MP.Data.Services.Utils; -using NLog; -using StackExchange.Redis; -using System.Globalization; - -namespace MP.IOC.Services -{ - public class MetricsDbFlushService : BackgroundService - { - #region Public Constructors - - public MetricsDbFlushService( - IServiceScopeFactory scopeFactory, - IConfiguration config, - IConnectionMultiplexer mux) - { - _scopeFactory = scopeFactory; - _config = config; - _mux = mux; - _db = mux.GetDatabase(); - } - - #endregion Public Constructors - - #region Protected Methods - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - //var interval = _config.GetValue("RouteMan:MetricFlushIntervalSeconds", 120); - var interval = _config.GetValue("RouteMan:MetricFlushIntervalSeconds", 300); - - while (!stoppingToken.IsCancellationRequested) - { - try - { - await ProcessDayLiveMetricsAsync(); - await ProcessHourLiveMetricsAsync(); - await Task.Delay(TimeSpan.FromSeconds(interval), stoppingToken); - } - catch (TaskCanceledException) { break; } - catch (Exception ex) - { - Log.Error(ex, "Error flushing metrics to database"); - } - } - } - - #endregion Protected Methods - - #region Private Fields - - private const double SentinelValue = 999999999; - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private readonly IConfiguration _config; - private readonly IDatabase _db; - private readonly IConnectionMultiplexer _mux; - private readonly IServiceScopeFactory _scopeFactory; - - #endregion Private Fields - - #region Private Properties - - private string _redisBaseKey => _config.GetValue("ServerConf:RedisBaseKey") ?? "MP_IOC"; - - #endregion Private Properties - - #region Private Methods - - /// - /// Processing dati giornalieri (da Redis a DB) - /// - /// - private async Task ProcessDayLiveMetricsAsync() - { - var aggrRecordsToInsert = new List(); - var keysToDelete = new List(); - - bool deleteConfirmed = _config.GetValue("RouteMan:DeleteExpiredMetrics", false); - DateTime now = DateTime.Now; - - // Confini temporali per proteggere i dati in corso - DateTime currentDayStart = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); - - var endpoints = _mux.GetEndPoints(); - - foreach (var endpoint in endpoints) - { - var server = _mux.GetServer(endpoint); - - if (server.IsReplica) - { - continue; - } - - string[] patternsToScan = { - $"{_redisBaseKey}:stats:days:*" - }; - - foreach (var pattern in patternsToScan) - { - // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer - await foreach (var indexKey in server.KeysAsync(pattern: pattern)) - { - if (string.IsNullOrEmpty($"{indexKey}")) continue; - - // CORREZIONE: Utilizzo di SortedSetRangeByRankAsync con range 0 a -1 per prendere tutto il set - var memberKeys = await _db.SortedSetRangeByRankAsync(indexKey, 0, -1); - - foreach (var statKey in memberKeys) - { - var sKey = (RedisKey)$"{statKey}"; - if (!TryParseKeyMetadata(sKey, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType)) - continue; - - // Verifica se la chiave fosse scaduta rispetto all'orario corrente - bool isExpired = isHourType - //? timestamp < currentHourStart - ? false - : timestamp < currentDayStart; - - // Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione - if (isExpired && deleteConfirmed) - { - keysToDelete.Add(sKey); - } - - // Recupero dati dalla Hash - var hashData = await _db.HashGetAllAsync(sKey); - if (hashData.Length == 0) continue; - - var dict = hashData.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); - - if (dict.TryGetValue("count", out var countStr) && - dict.TryGetValue("totalMs", out var totalMsStr)) - { - long count = long.Parse(countStr); - count = long.Parse(countStr); - double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture); - - double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0; - double minMs = dict.ContainsKey("minMs") ? double.Parse(dict["minMs"], CultureInfo.InvariantCulture) : 0; - - // TUA CORREZIONE: Reset sentinella per evitare valori fuori scala nel DB - if (minMs >= SentinelValue) minMs = SentinelValue; - if (maxMs >= SentinelValue) maxMs = SentinelValue; - - if (count <= 0) continue; - - aggrRecordsToInsert.Add(new StatsAggregatedModel - { - Destination = dest, - MachineId = machId, - Hour = timestamp, - RequestCount = count, - AvgDuration = totalMs / count, - MinDuration = minMs, - MaxDuration = maxMs, - NoReply = 0 - }); - } - } - } - } - } - - // --- FASE UPSERT DB --- - if (aggrRecordsToInsert.Count > 0) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var aggrService = scope.ServiceProvider.GetRequiredService(); - await aggrService.UpsertManyAsync(aggrRecordsToInsert, false); - Log.Info($"[DAY] Upserted {aggrRecordsToInsert.Count} records to DB"); - } - - // --- FASE PULIZIA REDIS --- - if (deleteConfirmed && keysToDelete.Count > 0) - { - var batch = _db.CreateBatch(); - int deletedCount = 0; - foreach (var key in keysToDelete) - { - _ = batch.KeyDeleteAsync(key); - deletedCount++; - } - batch.Execute(); - Log.Info($"[CLEANUP DAY] Deleted {deletedCount} expired metric keys from Redis"); - } - } - - /// - /// Processing dati orari (da Redis a DB) - /// - /// - private async Task ProcessHourLiveMetricsAsync() - { - var detailRecordsToInsert = new List(); - var keysToDelete = new List(); - - bool deleteConfirmed = _config.GetValue("RouteMan:DeleteExpiredMetrics", false); - DateTime now = DateTime.Now; - - // Confini temporali per proteggere i dati in corso - DateTime currentHourStart = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0); - DateTime currentDayStart = new DateTime(now.Year, now.Month, now.Day, 0, 0, 0); - - var endpoints = _mux.GetEndPoints(); - - foreach (var endpoint in endpoints) - { - var server = _mux.GetServer(endpoint); - - if (server.IsReplica) - { - continue; - } - - string[] patternsToScan = { - $"{_redisBaseKey}:stats:hours:*" - }; - - foreach (var pattern in patternsToScan) - { - // Nota: KeyScanAsync/KeysAsync e' disponibile su IServer - await foreach (var indexKey in server.KeysAsync(pattern: pattern)) - { - if (string.IsNullOrEmpty($"{indexKey}")) continue; - - // CORREZIONE: Utilizzo di SortedSetRangeByRankAsync con range 0 a -1 per prendere tutto il set - var memberKeys = await _db.SortedSetRangeByRankAsync(indexKey, 0, -1); - - foreach (var statKey in memberKeys) - { - var sKey = (RedisKey)$"{statKey}"; - if (!TryParseKeyMetadata(sKey, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType)) - continue; - - // Verifica se la chiave fosse scaduta rispetto all'orario corrente - bool isExpired = isHourType - ? timestamp < currentHourStart - : false; - //: timestamp < currentDayStart; - - // Se fosse scaduta e abbiamo il permesso, segnamola per la cancellazione - if (isExpired && deleteConfirmed) - { - keysToDelete.Add(sKey); - } - - // Recupero dati dalla Hash - var hashData = await _db.HashGetAllAsync(sKey); - if (hashData.Length == 0) continue; - - var dict = hashData.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString()); - - if (dict.TryGetValue("count", out var countStr) && - dict.TryGetValue("totalMs", out var totalMsStr)) - { - long count = long.Parse(countStr); - count = long.Parse(countStr); - double totalMs = double.Parse(totalMsStr, CultureInfo.InvariantCulture); - - double maxMs = dict.ContainsKey("maxMs") ? double.Parse(dict["maxMs"], CultureInfo.InvariantCulture) : 0; - double minMs = dict.ContainsKey("minMs") ? double.Parse(dict["minMs"], CultureInfo.InvariantCulture) : 0; - - // TUA CORREZIONE: Reset sentinella per evitare valori fuori scala nel DB - if (minMs >= SentinelValue) minMs = SentinelValue; - if (maxMs >= SentinelValue) maxMs = SentinelValue; - - if (count <= 0) continue; - - detailRecordsToInsert.Add(new StatsDetailModel - { - Destination = dest, - Type = method, - Hour = timestamp, - RequestCount = count, - AvgDuration = totalMs / count, - MinDuration = minMs, - MaxDuration = maxMs, - NoReply = 0 - }); - } - } - } - } - } - - // --- FASE UPSERT DB --- - if (detailRecordsToInsert.Count > 0) - { - await using var scope = _scopeFactory.CreateAsyncScope(); - var detailService = scope.ServiceProvider.GetRequiredService(); - await detailService.UpsertManyAsync(detailRecordsToInsert, false); - Log.Info($"[HOUR] Upserted {detailRecordsToInsert.Count} records to DB"); - } - - // --- FASE PULIZIA REDIS --- - if (deleteConfirmed && keysToDelete.Count > 0) - { - var batch = _db.CreateBatch(); - int deletedCount = 0; - foreach (var key in keysToDelete) - { - _ = batch.KeyDeleteAsync(key); - deletedCount++; - } - batch.Execute(); - Log.Info($"[CLEANUP HOUR] Deleted {deletedCount} expired metric keys from Redis"); - } - } - - private bool TryParseKeyMetadata(RedisKey key, out string dest, out string method, out string machId, out DateTime timestamp, out bool isHourType) - { - dest = "NA"; - method = "NA"; - machId = "ALL"; - timestamp = DateTime.MinValue; - isHourType = true; - try - { - string k = key.ToString(); - string relativeKey = k.Replace($"{_redisBaseKey}:", ""); - var parts = relativeKey.Split(':'); - - if (parts.Length < 4) return false; - - string type = parts[1]; // "hour" o "day" - dest = parts[2]; - - if (type == "hour") - { - isHourType = true; - method = parts[3]; - if (parts.Length >= 5 && DateTime.TryParseExact(parts[4], "yyyyMMddHH", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) - { - timestamp = dt; - return true; - } - } - else if (type == "day") - { - isHourType = false; - method = "DAILY"; - string rawDate = ""; - if (parts.Length >= 5) - { - machId = parts[3]; - rawDate = parts[4]; - } - else - { - rawDate = parts[3]; - } - if (DateTime.TryParseExact(rawDate, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var dt)) - { - timestamp = dt; - return true; - } - } - } - catch { } - return false; - } - - #endregion Private Methods - } -} \ No newline at end of file diff --git a/MP.IOC/Services/PreserveBodyTransformer.cs b/MP.IOC/Services/PreserveBodyTransformer.cs deleted file mode 100644 index c8d6ed33..00000000 --- a/MP.IOC/Services/PreserveBodyTransformer.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Yarp.ReverseProxy.Forwarder; - -namespace MP.IOC.Services -{ - - public class PreserveBodyTransformer : HttpTransformer - { - public override async ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix, CancellationToken cancellationToken) - { - // Chiama il base (usa overload con cancellationToken) - await base.TransformRequestAsync(httpContext, proxyRequest, destinationPrefix, cancellationToken); - - // Imposta il method correttamente - proxyRequest.Method = new HttpMethod(httpContext.Request.Method); - - // Se vuoi leggere/loggare il body, abilita buffering e rewind. - // NON assegnare proxyRequest.Content: YARP copierà il body da HttpContext.Request. - if (httpContext.Request.ContentLength > 0 || httpContext.Request.Body.CanRead) - { - // Abilita buffering solo se necessario (attenzione a payload grandi) - httpContext.Request.EnableBuffering(); - - // Rewind per sicurezza - httpContext.Request.Body.Position = 0; - - // Se vuoi leggere il body per logging, fallo qui ma non sostituire proxyRequest.Content. - // Esempio (opzionale): leggere senza consumare - // using var sr = new StreamReader(httpContext.Request.Body, leaveOpen: true); - // var bodyText = await sr.ReadToEndAsync(); - // httpContext.Request.Body.Position = 0; - } - } - - public override ValueTask TransformResponseAsync(HttpContext httpContext, HttpResponseMessage? proxyResponse, CancellationToken cancellationToken) - { - return base.TransformResponseAsync(httpContext, proxyResponse, cancellationToken); - } - } - - -} diff --git a/MP.IOC/Services/RouteManager.cs b/MP.IOC/Services/RouteManager.cs deleted file mode 100644 index 529996e6..00000000 --- a/MP.IOC/Services/RouteManager.cs +++ /dev/null @@ -1,173 +0,0 @@ -using NLog; -using System.Diagnostics; -using Yarp.ReverseProxy.Forwarder; - -namespace MP.IOC.Services -{ - - - public class RouteManager - { - private readonly IHttpForwarder _forwarder; - private readonly HttpMessageInvoker _httpClientInvoker; - private readonly PreserveBodyTransformer _transformer; - private readonly RouteStatsManager _stats; - private readonly IWeightProvider _weightProvider; - private readonly IConfiguration _config; - - public RouteManager( - IHttpForwarder forwarder, - HttpMessageInvoker httpClientInvoker, - PreserveBodyTransformer transformer, - RouteStatsManager stats, - IWeightProvider weightProvider, - IConfiguration config) - { - _forwarder = forwarder; - _httpClientInvoker = httpClientInvoker; - _transformer = transformer; - _stats = stats; - _weightProvider = weightProvider; - _config = config; - _routePath = _config.GetValue("ServerConf:RoutePath") ?? "/api/RIOB"; - } - - private string _routePath = ""; - - private static Logger Log = LogManager.GetCurrentClassLogger(); - - - public async Task HandleAsync(HttpContext context) - { - var sw = Stopwatch.StartNew(); - var routePrefix = new PathString(_routePath); - var fullPrefix = context.Request.PathBase.Add(routePrefix); - - string relativePath; - string query = context.Request.QueryString.HasValue ? context.Request.QueryString.Value : ""; - - if (context.Request.Path.StartsWithSegments(fullPrefix, out var remaining)) - { - relativePath = remaining.Value.TrimStart('/'); - } - else if (context.Request.Path.StartsWithSegments(routePrefix, out remaining)) - { - relativePath = remaining.Value.TrimStart('/'); - } - else - { - var fullPath = (context.Request.PathBase + context.Request.Path).Value ?? ""; - var idx = fullPath.IndexOf("/RIOB/", StringComparison.OrdinalIgnoreCase); - relativePath = idx >= 0 ? fullPath[(idx + "/RIOB/".Length)..] : fullPath.TrimStart('/'); - } - - Log.Debug($"PathBase={context.Request.PathBase} | Path={context.Request.Path} | relativePath={relativePath}"); - - // Procedo a calcolare metodo e ID... - string metodo = "/"; - string id = "ALL"; - if (!string.IsNullOrEmpty(relativePath)) - { - // Rimuovo eventuale query string - var pathOnly = relativePath.Split('?')[0]; - - // Splitto per / - var parts = pathOnly.Split('/', StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length > 0) - metodo = parts[0]; - - if (parts.Length > 1) - id = parts[1]; - -#if false - // splitto se ho /... - if (relativePath.Contains("/")) - { - metodo = relativePath.Substring(0, relativePath.IndexOf("/")); - } - else - { - metodo = relativePath; - } -#endif - } - - Log.Debug($"Metodo: {metodo} | machineId: {id}"); - - var (oldW, newW) = _weightProvider.GetWeightsFor(metodo); - var pickNew = DecideByWeights(oldW, newW); - var target = pickNew ? "IOC" : "IO"; - - // Costruisci destination base in base al target - var destBase = pickNew - ? _config["ReverseProxy:Clusters:cluster-new:Destinations:new1:Address"] - : _config["ReverseProxy:Clusters:cluster-old:Destinations:old1:Address"]; - - if (string.IsNullOrEmpty(destBase)) - { - context.Response.StatusCode = 502; - await context.Response.WriteAsync("Destination not configured"); - return; - } - - if (!destBase.EndsWith("/")) destBase += "/"; - // salva path originale per ripristino - var originalPath = context.Request.Path; - var originalPathBase = context.Request.PathBase; - - try - { - string sKey = $"{target}|{metodo}|{id}"; - - // Registra scelta - _stats.Record(sKey); - - // imposta la Path che vogliamo che YARP appenda al destBase - context.Request.Path = new PathString("/" + relativePath); - // opzionale: se vuoi che PathBase sia vuoto per il backend, impostalo così - context.Request.PathBase = PathString.Empty; - - Log.Debug($"Forwarding to base {destBase} with forwarded path {context.Request.Path} | original PathBase:{originalPathBase} | Path={originalPath})"); - - var requestOptions = new ForwarderRequestConfig { ActivityTimeout = TimeSpan.FromSeconds(100) }; - var error = await _forwarder.SendAsync(context, destBase, _httpClientInvoker, requestOptions, _transformer, context.RequestAborted); - - sw.Stop(); - _stats.RecordDuration(sKey, sw.Elapsed); - - if (error != ForwarderError.None) - { - var feat = context.GetForwarderErrorFeature(); - Log.Error(feat?.Exception, "Forwarder error to {DestBase}", destBase); - context.Response.StatusCode = 502; - await context.Response.WriteAsync($"Forward error: {feat?.Exception?.Message}"); - } - } - finally - { - // ripristina i path originali per non rompere la pipeline successiva - context.Request.Path = originalPath; - context.Request.PathBase = originalPathBase; - } - } - - private bool DecideByWeights(int oldW, int newW) - { - bool result = false; - // se entrambi zero -> prefer legacy - var total = oldW + newW; - if (total <= 0) - { - result = false; - } - else - { - var rnd = Random.Shared.NextDouble(); // 0..1 - result = rnd < (double)newW / total; - } - return result; - } - } - -} diff --git a/MP.IOC/Services/RouteStatsManager.cs b/MP.IOC/Services/RouteStatsManager.cs deleted file mode 100644 index 2af2edee..00000000 --- a/MP.IOC/Services/RouteStatsManager.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Concurrent; - -namespace MP.IOC.Services -{ - public class RouteStats - { - public long Count; - public TimeSpan TotalDuration = TimeSpan.Zero; - public TimeSpan MaxDuration = TimeSpan.Zero; - public TimeSpan MinDuration = TimeSpan.MaxValue; - public ConcurrentDictionary StatusCodes = new(); - } - - public class RouteStatsManager - { - private readonly ConcurrentDictionary _map = new(); - - /// - /// Registrazione del metodo + destinazione - /// - /// - public void Record(string dest_method) - { - var stat = _map.GetOrAdd(dest_method, _ => new RouteStats()); - Interlocked.Increment(ref stat.Count); - } - - /// - /// Registrazione destinazione+metodo - /// - /// chiave dest+metodo x salvataggio statistiche - /// - public void RecordDuration(string dest_method, TimeSpan duration) - { - if (_map.TryGetValue(dest_method, out var stat)) - { - lock (stat) - { - stat.TotalDuration += duration; - - if (stat.MaxDuration < duration) - { - stat.MaxDuration = duration; - } - if (stat.MinDuration > duration) - { - stat.MinDuration = duration; - } - } - } - } - - public void RecordStatusCode(string method, int statusCode) - { - if (_map.TryGetValue(method, out var stat)) - { - stat.StatusCodes.AddOrUpdate(statusCode, 1, (_, v) => v + 1); - } - } - - public IReadOnlyDictionary Snapshot() - { - return _map.ToDictionary(kv => kv.Key, kv => kv.Value); - } - - public void Clear() - { - _map.Clear(); - } - } -} diff --git a/MP.IOC/appsettings.Production.json b/MP.IOC/appsettings.Production.json index 5b315b32..787c7891 100644 --- a/MP.IOC/appsettings.Production.json +++ b/MP.IOC/appsettings.Production.json @@ -5,23 +5,6 @@ "Microsoft.AspNetCore": "Warning" } }, - "ReverseProxy": { - "Routes": [ - { - "RouteId": "route-man-r-iob", - "ClusterId": "cluster-placeholder", - "Match": { "Path": "/api/RIOB/{**catch-all}" } - } - ], - "Clusters": { - "cluster-old": { - "Destinations": { "old1": { "Address": "https://maposrv.egalware.com/MP/IO/IOB/" } } - }, - "cluster-new": { - "Destinations": { "new1": { "Address": "https://maposrv.egalware.com/MP/IOC/api/IOB/" } } - } - } - }, "AllowedHosts": "*", "CodApp": "MP.IOC", "ConnectionStrings": { @@ -34,10 +17,5 @@ }, "ServerConf": { "useFactory": false - }, - "RedisScripts": { - "Scripts": { - "Update": "RedisScript/RedisUpdateScript_v5.lua" - } } } diff --git a/MP.IOC/appsettings.json b/MP.IOC/appsettings.json index e24667d4..e947a8ce 100644 --- a/MP.IOC/appsettings.json +++ b/MP.IOC/appsettings.json @@ -14,8 +14,6 @@ "baseFileDir": "${basedir}/logs/", "layout": "${longdate} | ${uppercase:${level}} | ${logger:shortName=false} | ${message}" }, - // "internalLogLevel": "Info", - // "internalLogFile": "c:\\temp\\internal-nlog.txt", "extensions": [ { "assembly": "NLog.Extensions.Logging" }, { "assembly": "NLog.Web.AspNetCore" } @@ -27,7 +25,6 @@ "type": "File", "fileName": "${basedir}/logs/${shortdate}.log", "keepFileOpen": false, - "concurrentWrites": true, "archiveEvery": "Day", "archiveFileName": "${basedir}/logs/old/${shortdate}_{#}.log", "archiveNumbering": "DateAndSequence", @@ -56,31 +53,7 @@ }, "AllowedHosts": "*", "CodApp": "MP.IOC", - "ReverseProxy": { - "Routes": [ - { - "RouteId": "route-man-r-iob", - "ClusterId": "cluster-placeholder", - "Match": { "Path": "/api/RIOB/{**catch-all}" } - } - ], - "Clusters": { - "cluster-old": { - "HttpRequest": { - "Version": "1.1", - "VersionPolicy": "RequestVersionExact" - }, - "Destinations": { "old1": { "Address": "https://iis01.egalware.com/MP/IO/IOB/" } } - }, - "cluster-new": { - "HttpRequest": { - "Version": "1.1", - "VersionPolicy": "RequestVersionExact" - }, - "Destinations": { "new1": { "Address": "https://iis01.egalware.com/MP/IOC/api/IOB/" } } - } - } - }, + "RouteMan": { "MetricCalcIntervalSeconds": 10, "MetricFlushIntervalSeconds": 60, From 3f79e6773527385f9caa51e1a336d80d5da838b5 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 11:56:12 +0200 Subject: [PATCH 11/13] Review LAND x preparare nuovi installers --- MP.AppAuth/AppAuthContext.cs | 16 ++++++++ MP.AppAuth/Controllers/AppAuthController.cs | 41 +++---------------- MP.AppAuth/Services/AppAuthService.cs | 6 +-- MP.Data/Controllers/MpLandController.cs | 6 ++- .../PublishProfiles/IIS01.pubxml.user | 2 +- MP.Land/MP.Land.csproj | 2 +- MP.Land/Pages/IobList.razor.cs | 23 ----------- MP.Land/Pages/UserQr.razor.cs | 5 --- MP.Land/Resources/ChangeLog.html | 2 +- MP.Land/Resources/VersNum.txt | 2 +- MP.Land/Resources/manifest.xml | 2 +- MP.Land/appsettings.json | 1 + 12 files changed, 33 insertions(+), 75 deletions(-) diff --git a/MP.AppAuth/AppAuthContext.cs b/MP.AppAuth/AppAuthContext.cs index 35fedc43..7a770a8f 100644 --- a/MP.AppAuth/AppAuthContext.cs +++ b/MP.AppAuth/AppAuthContext.cs @@ -1,5 +1,6 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; using MP.AppAuth.Models; using NLog; using System; @@ -23,9 +24,23 @@ namespace MP.AppAuth { } + private DbContextOptions _options; public AppAuthContext(IConfiguration configuration) { _configuration = configuration; + string connStr = _configuration.GetConnectionString("MP.Land.Auth"); + if (string.IsNullOrEmpty(connStr)) + { + connStr = _configuration.GetConnectionString("MP.Land"); + } + if (string.IsNullOrEmpty(connStr)) + { + connStr = _configuration.GetConnectionString("MP.Data"); + } + _options = new DbContextOptionsBuilder() + .UseSqlServer(connStr) + .Options; + try { // se non ci fosse... crea o migra! @@ -39,6 +54,7 @@ namespace MP.AppAuth public AppAuthContext(DbContextOptions options) : base(options) { + _options = options; try { // se non ci fosse... crea o migra! diff --git a/MP.AppAuth/Controllers/AppAuthController.cs b/MP.AppAuth/Controllers/AppAuthController.cs index 24757db7..bd6047d5 100644 --- a/MP.AppAuth/Controllers/AppAuthController.cs +++ b/MP.AppAuth/Controllers/AppAuthController.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using MP.AppAuth.Models; using NLog; using System; @@ -9,14 +10,14 @@ using System.Threading.Tasks; namespace MP.AppAuth.Controllers { - public class AppAuthController : IDisposable + public class AppAuthController { #region Public Constructors public AppAuthController(IConfiguration configuration) { _configuration = configuration; - dbCtx = new AppAuthContext(configuration); + Log.Info("Avviata classe AppAuthController"); } @@ -116,11 +117,6 @@ namespace MP.AppAuth.Controllers return dbResult; } - public void Dispose() - { - // Clear database context - dbCtx.Dispose(); - } /// /// Elenco completo permessi2funzione @@ -193,33 +189,7 @@ namespace MP.AppAuth.Controllers return dbResult; } - public void ResetController() - { - dbCtx = new AppAuthContext(_configuration); - Log.Info("Effettuato reset AppAuthController"); - } - - /// - /// Annulla modifiche su una specifica entity (cancel update) - /// - /// - /// - public bool RollBackEntity(object item) - { - bool answ = false; - try - { - if (dbCtx.Entry(item).State == Microsoft.EntityFrameworkCore.EntityState.Deleted || dbCtx.Entry(item).State == Microsoft.EntityFrameworkCore.EntityState.Modified) - { - dbCtx.Entry(item).Reload(); - } - } - catch (Exception exc) - { - Log.Error($"Eccezione in rollBackEntity{Environment.NewLine}{exc}"); - } - return answ; - } + /// /// Elenco Record x gestione Update @@ -259,7 +229,6 @@ namespace MP.AppAuth.Controllers #region Private Fields private static IConfiguration _configuration; - private static AppAuthContext dbCtx; private static Logger Log = LogManager.GetCurrentClassLogger(); #endregion Private Fields diff --git a/MP.AppAuth/Services/AppAuthService.cs b/MP.AppAuth/Services/AppAuthService.cs index cef52a0a..7ab866af 100644 --- a/MP.AppAuth/Services/AppAuthService.cs +++ b/MP.AppAuth/Services/AppAuthService.cs @@ -96,7 +96,7 @@ namespace MP.AppAuth.Services }; // conf DB - string connStr = _configuration.GetConnectionString("MP.Land"); + string connStr = _configuration.GetConnectionString("MP.Land.Auth"); if (string.IsNullOrEmpty(connStr)) { _logger.LogError("ConnString empty!"); @@ -431,10 +431,6 @@ namespace MP.AppAuth.Services public void Dispose() { // Clear database controller - if (dbController != null) - { - dbController.Dispose(); - } if (MpDbController != null) { MpDbController.Dispose(); diff --git a/MP.Data/Controllers/MpLandController.cs b/MP.Data/Controllers/MpLandController.cs index 956c460c..9a77ad18 100644 --- a/MP.Data/Controllers/MpLandController.cs +++ b/MP.Data/Controllers/MpLandController.cs @@ -19,7 +19,11 @@ namespace MP.Data.Controllers public MpLandController(IConfiguration configuration) { _configuration = configuration; - string connStr = _configuration.GetConnectionString("MP.Data"); + string connStr = _configuration.GetConnectionString("MP.Land"); + if(string.IsNullOrEmpty(connStr)) + { + connStr = _configuration.GetConnectionString("MP.Data"); + } options = new DbContextOptionsBuilder() .UseSqlServer(connStr) .Options; diff --git a/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user b/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user index f06abead..15901b1b 100644 --- a/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user +++ b/MP.IOC/Properties/PublishProfiles/IIS01.pubxml.user @@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. AQAAANCMnd8BFdERjHoAwE/Cl+sBAAAAdCoESbxryUqXde3FfEOCTwAAAAACAAAAAAADZgAAwAAAABAAAAAFrH1vByj45Qn06hO/OH6tAAAAAASAAACgAAAAEAAAAIUK5NiEpc4lc11Op6/CLx8YAAAAMecN12fzIN9e3E8R/nu0ATe2PAsMy7M8FAAAAKbUyki2vkSFehjbpB8wCVVVa055 - False|2026-05-08T09:06:51.2202890Z||;True|2026-05-08T10:37:55.0049896+02:00||;False|2026-05-08T10:37:05.1546068+02:00||;False|2026-05-08T10:36:33.0311629+02:00||;False|2026-05-08T10:35:55.7742437+02:00||;True|2026-04-17T19:09:49.4900600+02:00||;True|2026-04-03T12:55:15.0251473+02:00||;True|2026-04-03T10:03:02.5833820+02:00||;True|2026-04-03T09:55:55.5274684+02:00||;True|2026-04-03T09:52:44.9063312+02:00||;False|2026-04-03T09:45:49.5943015+02:00||;True|2024-11-04T08:56:17.3071781+01:00||;True|2023-02-14T17:41:26.3850692+01:00||;True|2023-02-14T17:31:39.4933399+01:00||; + True|2026-05-08T09:26:08.1731328Z||;False|2026-05-08T11:06:51.2202890+02:00||;True|2026-05-08T10:37:55.0049896+02:00||;False|2026-05-08T10:37:05.1546068+02:00||;False|2026-05-08T10:36:33.0311629+02:00||;False|2026-05-08T10:35:55.7742437+02:00||;True|2026-04-17T19:09:49.4900600+02:00||;True|2026-04-03T12:55:15.0251473+02:00||;True|2026-04-03T10:03:02.5833820+02:00||;True|2026-04-03T09:55:55.5274684+02:00||;True|2026-04-03T09:52:44.9063312+02:00||;False|2026-04-03T09:45:49.5943015+02:00||;True|2024-11-04T08:56:17.3071781+01:00||;True|2023-02-14T17:41:26.3850692+01:00||;True|2023-02-14T17:31:39.4933399+01:00||; \ No newline at end of file diff --git a/MP.Land/MP.Land.csproj b/MP.Land/MP.Land.csproj index 4b406421..4f1a232f 100644 --- a/MP.Land/MP.Land.csproj +++ b/MP.Land/MP.Land.csproj @@ -3,7 +3,7 @@ net8.0 MP.Land - 8.16.2605.0411 + 8.16.2605.0811 Debug;Release;Debug_LiManDebug en True diff --git a/MP.Land/Pages/IobList.razor.cs b/MP.Land/Pages/IobList.razor.cs index 87e6ca55..90157bb1 100644 --- a/MP.Land/Pages/IobList.razor.cs +++ b/MP.Land/Pages/IobList.razor.cs @@ -173,18 +173,6 @@ namespace MP.Land.Pages return answ; } -#if false - protected Core.Objects.IOB_data CurrIobInfo(string idxMacc) - { - Core.Objects.IOB_data answ = new Core.Objects.IOB_data(); - if (DictIobInfo.ContainsKey(idxMacc)) - { - answ = DictIobInfo[idxMacc]; - } - return answ; - } -#endif - protected void DataInit() { isLoading = true; @@ -219,17 +207,6 @@ namespace MP.Land.Pages return answ; } -#if false - protected string IobExeTrim(string IdxMacchina) - { - string answ = MacIobConf(IdxMacchina, "IobExe"); - if (!string.IsNullOrEmpty(answ) && answ.Contains(",")) - { - answ = answ.Substring(0, answ.IndexOf(',')); - } - return answ; - } -#endif protected override async Task OnInitializedAsync() { diff --git a/MP.Land/Pages/UserQr.razor.cs b/MP.Land/Pages/UserQr.razor.cs index 49f50c64..6d121f15 100644 --- a/MP.Land/Pages/UserQr.razor.cs +++ b/MP.Land/Pages/UserQr.razor.cs @@ -143,11 +143,9 @@ namespace MP.Land.Pages private async void OnFilterUpdated() { isLoading = true; - await Task.Delay(1); await InvokeAsync(StateHasChanged); ListRecords = null; currPage = 1; - await Task.Delay(1); await ReloadData(); } @@ -158,7 +156,6 @@ namespace MP.Land.Pages await InvokeAsync(StateHasChanged); ListRecords = null; currPage = 1; - await Task.Delay(1); await ReloadData(); } @@ -166,11 +163,9 @@ namespace MP.Land.Pages { isLoading = true; // importante altrimenti NON mostra update UI - await Task.Delay(1); SearchRecords = await DataService.AnagOperByGroupList(groupName, AppMService.SearchVal); ListRecords = SearchRecords.Skip((currPage - 1) * numRecord).Take(numRecord).ToList(); totalCount = SearchRecords.Count(); - await Task.Delay(1); isLoading = false; StateHasChanged(); } diff --git a/MP.Land/Resources/ChangeLog.html b/MP.Land/Resources/ChangeLog.html index 01aed9ae..65e63b56 100644 --- a/MP.Land/Resources/ChangeLog.html +++ b/MP.Land/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo Tablet MAPO - DotNet6 -

      Versione: 8.16.2605.0411

      +

      Versione: 8.16.2605.0811


      Note di rilascio:
        diff --git a/MP.Land/Resources/VersNum.txt b/MP.Land/Resources/VersNum.txt index af1dc925..3249ffa8 100644 --- a/MP.Land/Resources/VersNum.txt +++ b/MP.Land/Resources/VersNum.txt @@ -1 +1 @@ -8.16.2605.0411 +8.16.2605.0811 diff --git a/MP.Land/Resources/manifest.xml b/MP.Land/Resources/manifest.xml index d230416a..e201bc34 100644 --- a/MP.Land/Resources/manifest.xml +++ b/MP.Land/Resources/manifest.xml @@ -1,6 +1,6 @@ - 8.16.2605.0411 + 8.16.2605.0811 https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/MP.Land.zip https://nexus.steamware.net/repository/SWS/MP-LAND/stable/LAST/ChangeLog.html false diff --git a/MP.Land/appsettings.json b/MP.Land/appsettings.json index b3be2a6d..09dcc181 100644 --- a/MP.Land/appsettings.json +++ b/MP.Land/appsettings.json @@ -58,6 +58,7 @@ "ConnectionStrings": { "DefaultConnection": "Server=SQL2016DEV;Database=MoonPro;Trusted_Connection=True;MultipleActiveResultSets=true", "MP.All": "Server=SQL2016DEV;Database=MoonPro;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;", + "MP.Data": "Server=SQL2016DEV;Database=MoonPro;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;", "MP.Flux": "Server=SQL2016DEV;Database=MoonPro_FluxData;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;", "MP.Land": "Server=SQL2016DEV;Database=MoonPro;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;", "MP.Land.Auth": "Server=SQL2016DEV;Database=MoonPro_Anagrafica;User ID=sa;Password=keyhammer16;integrated security=False;MultipleActiveResultSets=True;App=MP.Land;", From 7ae537516ba4dbc047b6211f9069f513b1c27bc6 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 12:00:46 +0200 Subject: [PATCH 12/13] Aggiunto profilo x RIOC --- .../PublishProfiles/IISProfile.pubxml | 17 +++++++++++++++++ .../PublishProfiles/IISProfile.pubxml.user | 7 +++++++ 2 files changed, 24 insertions(+) create mode 100644 MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml create mode 100644 MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml.user diff --git a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml new file mode 100644 index 00000000..f44ed1a0 --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml @@ -0,0 +1,17 @@ + + + + + Package + Release + Any CPU + + true + false + f3794272-87ad-7c42-2528-6b119dbbc4d5 + bin\publish\MP.RIOC.zip + true + Default Web Site/MP/RIOC + <_TargetId>IISWebDeployPackage + + \ No newline at end of file diff --git a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml.user b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml.user new file mode 100644 index 00000000..3224acae --- /dev/null +++ b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml.user @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file From c0e70bd07f038df7913fc58935cb7a3b3d51bf7a Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Fri, 8 May 2026 12:00:52 +0200 Subject: [PATCH 13/13] Ancora pubxml update --- .gitlab-ci.yml | 101 +++++++++++++++++- MP-RIOC/MP.RIOC.csproj | 2 +- .../PublishProfiles/IISProfile.pubxml | 3 + MP-RIOC/Resources/ChangeLog.html | 2 +- MP-RIOC/Resources/VersNum.txt | 2 +- MP-RIOC/Resources/manifest.xml | 2 +- 6 files changed, 105 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3fffdd88..88ebaaa0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -285,6 +285,24 @@ IOC:build: script: - dotnet build $env:APP_NAME/$env:APP_NAME.csproj +RIOC:build: + stage: build + tags: + - win + variables: + APP_NAME: MP.RIOC + SOL_NAME: MP-RIOC + rules: + - if: $CI_COMMIT_BRANCH == 'develop' + - if: $CI_COMMIT_BRANCH == 'master' + - if: $CI_COMMIT_BRANCH =~ /^feature\/IOC.+/ + when: always + before_script: + - *nuget-fix + - dotnet restore "$env:SOL_NAME.sln" + script: + - dotnet build $env:APP_NAME/$env:APP_NAME.csproj + TAB3:build: stage: build tags: @@ -350,8 +368,6 @@ LAND:SDK:deploy: - '& "$env:NUGET_PATH" setapikey $NUGET_API_KEY -source http://nexus.steamware.net/repository/nuget-hosted' - '& "$env:NUGET_PATH" push *$env:NUM_DEB.nupkg -Source http://nexus.steamware.net/repository/nuget-hosted' - - PROG:IIS01:deploy: stage: deploy tags: @@ -485,6 +501,25 @@ IOC:IIS01:deploy: - dotnet build $env:APP_NAME/$env:APP_NAME.csproj - dotnet publish -p:PublishProfile=IIS01.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release -p:username=jenkins -p:Password=$IIS_PWD -p:AllowUntrustedCertificate=true -p:verbosity=quiet $env:APP_NAME/$env:APP_NAME.csproj +RIOC:IIS01:deploy: + stage: deploy + tags: + - win + variables: + APP_NAME: MP.RIOC + SOL_NAME: MP-RIOC + before_script: + - *nuget-fix + - dotnet restore "$env:SOL_NAME.sln" + rules: + - if: $CI_COMMIT_BRANCH == 'develop' + - if: $CI_COMMIT_BRANCH =~ /^feature\/IOC.+/ + when: always + needs: ["RIOC:build"] + script: + - dotnet build $env:APP_NAME/$env:APP_NAME.csproj + - dotnet publish -p:PublishProfile=IIS01.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release -p:username=jenkins -p:Password=$IIS_PWD -p:AllowUntrustedCertificate=true -p:verbosity=quiet $env:APP_NAME/$env:APP_NAME.csproj + TAB3:IIS01:deploy: stage: deploy tags: @@ -654,6 +689,24 @@ IOC:IIS03:deploy: - dotnet publish -p:PublishProfile=IIS03.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release -p:username=jenkins -p:Password=$IIS_PWD -p:AllowUntrustedCertificate=true -p:verbosity=quiet $env:APP_NAME/$env:APP_NAME.csproj - dotnet publish -p:PublishProfile=IIS04.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release -p:username=jenkins -p:Password=$IIS_PWD -p:AllowUntrustedCertificate=true -p:verbosity=quiet $env:APP_NAME/$env:APP_NAME.csproj +RIOC:IIS03:deploy: + stage: deploy + tags: + - win + variables: + APP_NAME: MP.RIOC + SOL_NAME: MP-RIOC + before_script: + - *nuget-fix + - dotnet restore "$env:SOL_NAME.sln" + rules: + - if: $CI_COMMIT_BRANCH == 'master' + needs: ["RIOC:build"] + script: + - dotnet build $env:APP_NAME/$env:APP_NAME.csproj + - dotnet publish -p:PublishProfile=IIS03.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release -p:username=jenkins -p:Password=$IIS_PWD -p:AllowUntrustedCertificate=true -p:verbosity=quiet $env:APP_NAME/$env:APP_NAME.csproj + - dotnet publish -p:PublishProfile=IIS04.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release -p:username=jenkins -p:Password=$IIS_PWD -p:AllowUntrustedCertificate=true -p:verbosity=quiet $env:APP_NAME/$env:APP_NAME.csproj + TAB3:IIS03:deploy: stage: deploy tags: @@ -873,6 +926,28 @@ IOC:installer: - *hashBuild - *nexusUpload +RIOC:installer: + stage: installer + tags: + - win + variables: + APP_NAME: MP.RIOC + SOL_NAME: MP-RIOC + NEXUS_PATH: MP-RIOC + before_script: + - *nuget-fix + - dotnet restore "$env:SOL_NAME.sln" + rules: + - if: $CI_COMMIT_BRANCH == 'master' + - if: $CI_COMMIT_BRANCH == 'develop' + needs: ["IOC:build"] + script: + - dotnet build $env:APP_NAME/$env:APP_NAME.csproj + - dotnet publish -p:PublishProfile=IISProfile.pubxml -p:RunCodeAnalysis=false -p:Configuration=Release $env:APP_NAME/$env:APP_NAME.csproj -o:publish -p:verbosity=quiet + # qui il deploy su nexus... + - *hashBuild + - *nexusUpload + # -------------------------------- # RELEASE (tags only + sdk) # -------------------------------- @@ -897,7 +972,6 @@ LAND:release: - dotnet build $env:APP_NAME/$env:APP_NAME.csproj - dotnet publish -c Release -o ./publish $env:APP_NAME/$env:APP_NAME.csproj -p:verbosity=quiet - LAND:SDK:release: stage: release tags: @@ -1067,3 +1141,24 @@ IOC:release: - dotnet build $env:APP_NAME/$env:APP_NAME.csproj - dotnet publish -c Release -o ./publish $env:APP_NAME/$env:APP_NAME.csproj -p:verbosity=quiet +RIOC:release: + stage: release + tags: + - win + variables: + APP_NAME: MP.RIOC + SOL_NAME: MP-RIOC + NEXUS_PATH: MP-RIOC + before_script: + - *nuget-fix + - dotnet restore "$env:SOL_NAME.sln" + rules: + - if: $CI_COMMIT_TAG + needs: ["IOC:build"] + artifacts: + paths: + - publish/ + script: + - dotnet build $env:APP_NAME/$env:APP_NAME.csproj + - dotnet publish -c Release -o ./publish $env:APP_NAME/$env:APP_NAME.csproj -p:verbosity=quiet + diff --git a/MP-RIOC/MP.RIOC.csproj b/MP-RIOC/MP.RIOC.csproj index ad2a6515..62b5640a 100644 --- a/MP-RIOC/MP.RIOC.csproj +++ b/MP-RIOC/MP.RIOC.csproj @@ -5,7 +5,7 @@ enable enable MP.RIOC - 8.16.2605.811 + 8.16.2605.812 diff --git a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml index f44ed1a0..43da778a 100644 --- a/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml +++ b/MP-RIOC/Properties/PublishProfiles/IISProfile.pubxml @@ -13,5 +13,8 @@ true Default Web Site/MP/RIOC <_TargetId>IISWebDeployPackage + net8.0 + win-x64 + false \ No newline at end of file diff --git a/MP-RIOC/Resources/ChangeLog.html b/MP-RIOC/Resources/ChangeLog.html index 3c179208..f24ab287 100644 --- a/MP-RIOC/Resources/ChangeLog.html +++ b/MP-RIOC/Resources/ChangeLog.html @@ -1,6 +1,6 @@ Modulo MP-RIOC -

        Versione: 8.16.2605.811

        +

        Versione: 8.16.2605.812


        Note di rilascio:
        • diff --git a/MP-RIOC/Resources/VersNum.txt b/MP-RIOC/Resources/VersNum.txt index ddc2c22a..3ac974ab 100644 --- a/MP-RIOC/Resources/VersNum.txt +++ b/MP-RIOC/Resources/VersNum.txt @@ -1 +1 @@ -8.16.2605.811 +8.16.2605.812 diff --git a/MP-RIOC/Resources/manifest.xml b/MP-RIOC/Resources/manifest.xml index bb27fee8..3cb930b3 100644 --- a/MP-RIOC/Resources/manifest.xml +++ b/MP-RIOC/Resources/manifest.xml @@ -1,6 +1,6 @@ - 8.16.2605.811 + 8.16.2605.812 https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/MP.IOC.zip https://nexus.steamware.net/repository/SWS/MP-IOC/stable/LAST/ChangeLog.html false