From 89b69a47793e3712adae7c44e8320c93be4412b0 Mon Sep 17 00:00:00 2001 From: Samuele Locatelli Date: Mon, 21 Jul 2025 19:26:00 +0200 Subject: [PATCH] Inizio fix controller con deduplica INIT componenti REDIS (& co...) --- Lux.API/Controllers/WindowController.cs | 254 +++++++++++++++--------- Lux.API/Program.cs | 21 +- Lux.Data/Services/ImageCacheService.cs | 16 +- 3 files changed, 197 insertions(+), 94 deletions(-) diff --git a/Lux.API/Controllers/WindowController.cs b/Lux.API/Controllers/WindowController.cs index 537872ce..28c442f6 100644 --- a/Lux.API/Controllers/WindowController.cs +++ b/Lux.API/Controllers/WindowController.cs @@ -18,14 +18,21 @@ namespace Lux.API.Controllers { #region Public Constructors - public WindowController(IConfiguration config, ILogger logger, ImageCacheService imgServ, ExecProcessManager? EgwProcManager = null) + public WindowController(IConfiguration config, + ILogger logger, + ImageCacheService imgServ, + IConnectionMultiplexer redisConn) + //ExecProcessManager? EgwProcManager = null) { _logger = logger; _config = config; - // setup compoenti REDIS - redisConn = ConnectionMultiplexer.Connect(_config.GetConnectionString("Redis") ?? "localhost"); + // setup REDIS da DI +#if false + redisConn = ConnectionMultiplexer.Connect(_config.GetConnectionString("Redis") ?? "localhost"); +#endif redisDb = redisConn.GetDatabase(); + _currSub = redisConn.GetSubscriber(); // json serializer... FIX errore loop circolare https://www.ryadel.com/en/jsonserializationexception-self-referencing-loop-detected-error-fix-entity-framework-asp-net-core/ JSSettings = new JsonSerializerSettings() @@ -34,83 +41,24 @@ namespace Lux.API.Controllers }; // init classe sottoscrizione PubSub CHannel messages REDIS - messageDisp.Subscribe(ChannelName("", true), (channel, message) => + _currSub.Subscribe(ChannelName("", true), (channel, message) => { SaveCalcData(channel, message); }); +#if false // verifico se usare engine enableEgwEng = _config.GetValue("ServerConf:EgwEngineEnab"); -#if false if (enableEgwEng && EgwProcManager != null) { ProcessMan = EgwProcManager; ProcessMan.m_AnswerReceived += ProcessMan_m_AnswerReceived; - } + } #endif ImgService = imgServ; } - /// - /// Demorichiesta jwd x fare test richiesta calcolo - /// - private string demoJwd = "{\r\n \"ProfilePath\": \"Profilo78\",\r\n \"AreaList\": [\r\n {\r\n \"Shape\": \"RECTANGLE\",\r\n \"DimensionList\": [\r\n {\r\n \"nIndex\": 1,\r\n \"sName\": \"Width\",\r\n \"dValue\": 1200.0\r\n },\r\n {\r\n \"nIndex\": 2,\r\n \"sName\": \"Height\",\r\n \"dValue\": 1500.0\r\n }\r\n ],\r\n \"JointList\": [\r\n {\r\n \"nIndex\": 1,\r\n \"JointType\": \"FULL_V\"\r\n },\r\n {\r\n \"nIndex\": 2,\r\n \"JointType\": \"FULL_V\"\r\n },\r\n {\r\n \"nIndex\": 3,\r\n \"JointType\": \"FULL_V\"\r\n },\r\n {\r\n \"nIndex\": 4,\r\n \"JointType\": \"FULL_V\"\r\n }\r\n ],\r\n \"BottomRail\": false,\r\n \"BottomRailQty\": 0,\r\n \"AreaList\": [\r\n {\r\n \"bIsSashVertical\": true,\r\n \"SashList\": [\r\n {\r\n \"OpeningType\": \"TURNONLY_LEFT\",\r\n \"bHasHandle\": false,\r\n \"dDimension\": 50.0\r\n },\r\n {\r\n \"OpeningType\": \"TILTTURN_RIGHT\",\r\n \"bHasHandle\": true,\r\n \"dDimension\": 50.0\r\n }\r\n ],\r\n \"SashType\": \"NULL\",\r\n \"JointList\": [\r\n {\r\n \"nIndex\": 1,\r\n \"JointType\": \"FULL_V\"\r\n },\r\n {\r\n \"nIndex\": 2,\r\n \"JointType\": \"FULL_V\"\r\n },\r\n {\r\n \"nIndex\": 3,\r\n \"JointType\": \"FULL_V\"\r\n },\r\n {\r\n \"nIndex\": 4,\r\n \"JointType\": \"FULL_V\"\r\n }\r\n ],\r\n \"Hardware\": \"000000\",\r\n \"AreaList\": [\r\n {\r\n \"AreaList\": [\r\n {\r\n \"FillType\": \"GLASS\",\r\n \"AreaList\": [],\r\n \"AreaType\": \"FILL\"\r\n }\r\n ],\r\n \"AreaType\": \"SPLITTED\"\r\n },\r\n {\r\n \"AreaList\": [\r\n {\r\n \"FillType\": \"GLASS\",\r\n \"AreaList\": [],\r\n \"AreaType\": \"FILL\"\r\n }\r\n ],\r\n \"AreaType\": \"SPLITTED\"\r\n }\r\n ],\r\n \"AreaType\": \"SASH\"\r\n }\r\n ],\r\n \"AreaType\": \"FRAME\"\r\n }\r\n ]\r\n}"; - - /// - /// Salva risultato calcolo da broadcast channel REDIS - /// - /// - /// - private void SaveCalcData(RedisChannel channel, RedisValue message) - { - string rawData = $"{message}"; - if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2) - { - // provo a deserializzare - try - { - var retData = JsonConvert.DeserializeObject(rawData); - if (retData != null) - { - // verifico nId di risposta x salvare correttamente - ProcessMan_m_AnswerReceived(retData); - } - } - catch (Exception exc) - { - _logger.LogError($"Errore in fase decodifica messaggio da REDIS Channel{Environment.NewLine}{exc}"); - } - } - } - - - /// - /// Wrapper per invio messaggio richieste calcolo via REDIS - /// - /// - /// - private void sendMessage(string notifyChannel, string message) - { - RedisChannel pubNotifyChannel = new RedisChannel(notifyChannel, RedisChannel.PatternMode.Literal); - messageDisp.Publish(pubNotifyChannel, message); - } - - /// - /// Nome del channel sottoscritto per ritorno calcoli - /// - /// - /// - private string ChannelName(string servId, bool isOut) - { - return isOut ? $"EgwEngineOutput" : $"EgwEngineInput"; - // se implementassimo multi-elaborazione calcoli ogni esecutore ha un suo channel - //return $"EgwEngineOutput_{servId}"; - } - - /// - /// Oggetto subscriber x pubblicazione/sottoscrizione canali REDIS - /// - private ISubscriber _currSub { get; set; } = null!; +#if false /// /// Message Dispatcher: oggetto comunicazione pub/sub via REDIS channels corrente /// @@ -133,20 +81,8 @@ namespace Lux.API.Controllers // restituisco oggetto DB return answ; } - } - /// - /// conf speciale serializzatore JSON - /// - protected JsonSerializerSettings? JSSettings; - - /// - /// Oggetto per connessione a REDIS - /// - protected ConnectionMultiplexer redisConn = null!; - /// - /// Oggetto DB redis da impiegare x chiamate R/W - /// - protected IDatabase redisDb = null!; + } +#endif #endregion Public Constructors @@ -172,7 +108,10 @@ namespace Lux.API.Controllers // ...se ricevo percorso --> leggo jwd/svg cablato if (!string.IsNullOrEmpty(currJwd)) { +#if false if (true || (enableEgwEng && ProcessMan != null)) +#endif + if (true) { Dictionary DictExec = new Dictionary(); DictExec.Add("Mode", "1"); @@ -192,7 +131,7 @@ namespace Lux.API.Controllers { numWait--; await Task.Delay(waitDelay); - } + } #endif } #if false @@ -206,7 +145,7 @@ namespace Lux.API.Controllers else { svgContent = "EMPTY"; - } + } #endif } sw.Stop(); @@ -236,36 +175,131 @@ namespace Lux.API.Controllers return Ok(svgContent); } + /// + /// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta + /// PUT: api/calc/svg/00000000-0000-0000-0000-000000000000 + /// + /// id oggetto + /// + [HttpGet("svgfile/{id}")] + public ActionResult svgFile(string id) + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + if (string.IsNullOrEmpty(id)) + return NotFound(); + + string svgContent = ImgService.LoadSvg(id); + byte[] bytes = System.Text.Encoding.UTF8.GetBytes(svgContent); + + sw.Stop(); + _logger.LogInformation($"svgString | {sw.Elapsed.TotalMilliseconds:N3} ms"); + + return File(bytes, "image/svg+xml"); + } + + /// + /// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta + /// PUT: api/calc/svg/00000000-0000-0000-0000-000000000000 + /// + /// id oggetto + /// + [HttpGet("svgfileasync/{id}")] + public async Task> svgFileAsync(string id) + { + Stopwatch sw = new Stopwatch(); + sw.Start(); + + if (string.IsNullOrEmpty(id)) + return NotFound(); + + string svgContent = await ImgService.LoadSvgAsync(id); + byte[] bytes = System.Text.Encoding.UTF8.GetBytes(svgContent); + + sw.Stop(); + _logger.LogInformation($"svgString | {sw.Elapsed.TotalMilliseconds:N3} ms"); + + return File(bytes, "image/svg+xml"); + } + #endregion Public Methods + #region Protected Fields + + /// + /// conf speciale serializzatore JSON + /// + protected JsonSerializerSettings? JSSettings; + + /// + /// Oggetto per connessione a REDIS + /// + protected ConnectionMultiplexer redisConn = null!; + + /// + /// Oggetto DB redis da impiegare x chiamate R/W + /// + protected IDatabase redisDb = null!; + + #endregion Protected Fields + + #region Protected Properties + + + + #endregion Protected Properties + #region Private Fields private readonly ILogger _logger; + private IConfiguration _config; - private bool enableEgwEng = true; + + /// + /// Demorichiesta jwd x fare test richiesta calcolo + /// + private string demoJwd = "{\r\n\"ProfilePath\":\"Profilo78\",\r\n\"AreaList\":[\r\n{\r\n\"Shape\":\"RECTANGLE\",\r\n\"DimensionList\":[\r\n{\r\n\"nIndex\":1,\r\n\"sName\":\"Width\",\r\n\"dValue\":1200.0\r\n},\r\n{\r\n\"nIndex\":2,\r\n\"sName\":\"Height\",\r\n\"dValue\":1500.0\r\n}\r\n],\r\n\"JointList\":[\r\n{\r\n\"nIndex\":1,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":2,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":3,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":4,\r\n\"JointType\":\"FULL_V\"\r\n}\r\n],\r\n\"BottomRail\":false,\r\n\"BottomRailQty\":0,\r\n\"AreaList\":[\r\n{\r\n\"bIsSashVertical\":true,\r\n\"SashList\":[\r\n{\r\n\"OpeningType\":\"TURNONLY_LEFT\",\r\n\"bHasHandle\":false,\r\n\"dDimension\":50.0\r\n},\r\n{\r\n\"OpeningType\":\"TILTTURN_RIGHT\",\r\n\"bHasHandle\":true,\r\n\"dDimension\":50.0\r\n}\r\n],\r\n\"SashType\":\"NULL\",\r\n\"JointList\":[\r\n{\r\n\"nIndex\":1,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":2,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":3,\r\n\"JointType\":\"FULL_V\"\r\n},\r\n{\r\n\"nIndex\":4,\r\n\"JointType\":\"FULL_V\"\r\n}\r\n],\r\n\"Hardware\":\"000000\",\r\n\"AreaList\":[\r\n{\r\n\"AreaList\":[\r\n{\r\n\"FillType\":\"GLASS\",\r\n\"AreaList\":[],\r\n\"AreaType\":\"FILL\"\r\n}\r\n],\r\n\"AreaType\":\"SPLITTED\"\r\n},\r\n{\r\n\"AreaList\":[\r\n{\r\n\"FillType\":\"GLASS\",\r\n\"AreaList\":[],\r\n\"AreaType\":\"FILL\"\r\n}\r\n],\r\n\"AreaType\":\"SPLITTED\"\r\n}\r\n],\r\n\"AreaType\":\"SASH\"\r\n}\r\n],\r\n\"AreaType\":\"FRAME\"\r\n}\r\n]\r\n}"; + + private bool enableEgwEng = false; //private Dictionary listSvg = new Dictionary(); private string lastSvg = ""; - private int waitDelay = 10; - - private bool waitResult = false; - #endregion Private Fields #region Private Properties + /// + /// Oggetto subscriber x pubblicazione/sottoscrizione canali REDIS + /// + private ISubscriber _currSub { get; set; } = null!; + private ImageCacheService ImgService { get; set; } + #endregion Private Properties + +#if false /// /// Esecutore di processi generico /// private ExecProcessManager? ProcessMan { get; set; } - - #endregion Private Properties +#endif #region Private Methods + /// + /// Nome del channel sottoscritto per ritorno calcoli + /// + /// + /// + private string ChannelName(string servId, bool isOut) + { + return isOut ? $"EgwEngineOutput" : $"EgwEngineInput"; + // se implementassimo multi-elaborazione calcoli ogni esecutore ha un suo channel + //return $"EgwEngineOutput_{servId}"; + } + /// /// Restituisce una risposta all'esecuzione /// @@ -282,11 +316,53 @@ namespace Lux.API.Controllers string newSvg = result.Args["Svg"]; // salvo nel dizionario lastSvg = newSvg; + // salvo su redis + ImgService.SaveSvg("123456", lastSvg); +#if false waitResult = false; +#endif } } } + /// + /// Salva risultato calcolo da broadcast channel REDIS + /// + /// + /// + private void SaveCalcData(RedisChannel channel, RedisValue message) + { + string rawData = $"{message}"; + if (!string.IsNullOrEmpty(rawData) && rawData.Length > 2) + { + // provo a deserializzare + try + { + var retData = JsonConvert.DeserializeObject(rawData); + if (retData != null) + { + // verifico nId di risposta x salvare correttamente + ProcessMan_m_AnswerReceived(retData); + } + } + catch (Exception exc) + { + _logger.LogError($"Errore in fase decodifica messaggio da REDIS Channel{Environment.NewLine}{exc}"); + } + } + } + + /// + /// Wrapper per invio messaggio richieste calcolo via REDIS + /// + /// + /// + private void sendMessage(string notifyChannel, string message) + { + RedisChannel pubNotifyChannel = new RedisChannel(notifyChannel, RedisChannel.PatternMode.Literal); + _currSub.Publish(pubNotifyChannel, message); + } + #endregion Private Methods } } \ No newline at end of file diff --git a/Lux.API/Program.cs b/Lux.API/Program.cs index 043ade18..07d2a842 100644 --- a/Lux.API/Program.cs +++ b/Lux.API/Program.cs @@ -2,6 +2,7 @@ using EgwMultiEngineManager; using Lux.Data.Services; using NLog; using NLog.Web; +using StackExchange.Redis; var builder = WebApplication.CreateBuilder(args); var logger = LogManager.Setup() @@ -21,6 +22,7 @@ builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); +#if false ExecProcessManager? myExecProcessManager = null; // solo se abilitato da conf @@ -32,7 +34,20 @@ if (enableEgwEng) myExecProcessManager = new ExecProcessManager(sCamExePath, sMainLuaPath, 1, ExecProcessManager.ReturnModes.EVENT_); myExecProcessManager.StartExecutionThread(); builder.Services.AddSingleton(myExecProcessManager); -} +} +#endif + +// costruzione connectionMultiplexer redis... +string connStr = configuration.GetConnectionString("Redis") ?? "localhost"; +ConnectionMultiplexer redisConn = ConnectionMultiplexer.Connect(connStr); +builder.Services.AddSingleton(redisConn); + +#if false +builder.Services.AddSingleton(sp => +ConnectionMultiplexer.Connect(sp.GetRequiredService().GetConnectionString("Redis") ?? "localhost") +); +#endif + builder.Services.AddSingleton(); var app = builder.Build(); @@ -60,10 +75,12 @@ app.MapControllers(); // https://dotnetblog.asphostportal.com/how-to-handle-application-shutdown-in-asp-net-core/ app.Lifetime.ApplicationStopping.Register(() => { +#if false if (myExecProcessManager != null) { myExecProcessManager.StopExecutionThread(); - } + } +#endif }); // avvio app diff --git a/Lux.Data/Services/ImageCacheService.cs b/Lux.Data/Services/ImageCacheService.cs index c40840ca..aed20b91 100644 --- a/Lux.Data/Services/ImageCacheService.cs +++ b/Lux.Data/Services/ImageCacheService.cs @@ -58,11 +58,21 @@ namespace Lux.Data.Services /// public string LoadSvg(string imgId) { - string rawVal = ""; // recupero img da cache string currKey = $"{redisBaseKey}:{imgId.Replace("/", ":")}"; - rawVal = redisDb.StringGet(currKey); - return rawVal; + var rawVal = redisDb.StringGet(currKey); + return $"{rawVal}"; + } + /// + /// Recupera img redis SVG x id porta richiesta + /// + /// + public async Task LoadSvgAsync(string imgId) + { + // recupero img da cache + string currKey = $"{redisBaseKey}:{imgId.Replace("/", ":")}"; + var rawVal = await redisDb.StringGetAsync(currKey); + return $"{rawVal}"; } ///