Update x gestione mandata/ritorno messaggio redis con calcolo SVG

This commit is contained in:
Samuele Locatelli
2025-08-04 12:40:28 +02:00
parent 7f48bc9e0b
commit 3e9cbccf48
14 changed files with 226 additions and 94 deletions
@@ -43,6 +43,7 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NLog" Version="5.5.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.3" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="StackExchange.Redis" Version="2.8.41" />
</ItemGroup>
+110 -19
View File
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;
using NLog;
using RestSharp;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
@@ -21,6 +22,15 @@ namespace EgwCoreLib.Lux.Data.Services
svgChannel = _config.GetValue<string>("ServerConf:SvgChannel") ?? "svg:img";
liveTag = _config.GetValue<string>("ServerConf:ImageLiveTag") ?? "svg";
cacheTag = _config.GetValue<string>("ServerConf:ImageFileTag") ?? "svgfile";
calcTag = _config.GetValue<string>("ServerConf:ImageCalcTag") ?? "svg-preview";
// verifico la url base
apiUrl = _config.GetValue<string>("ServerConf:Prog.ApiUrl") ?? "https://iis01.egalware.com/lux/srv/api/window";
// fix opzioni base del RestClient
restOptStd = new RestClientOptions
{
Timeout = TimeSpan.FromSeconds(60),
BaseUrl = new Uri(apiUrl)
};
Log.Info("ImageCache Service Started");
}
@@ -28,6 +38,45 @@ namespace EgwCoreLib.Lux.Data.Services
#region Public Methods
/// <summary>
/// Effettua una generica chiamata ApiRest
/// </summary>
/// <param name="ApiUrl">URL Api di base</param>
/// <param name="ApiRequest">Richiesta completa</param>
/// <returns></returns>
public async Task<RestResponse> CallRestGet(string ApiUrl, string ApiRequest)
{
RestResponse response;
// cerco online
using (RestClient client = new RestClient(ApiUrl))
{
var request = new RestRequest(ApiRequest, Method.Get);
response = await client.GetAsync(request);
}
return response;
}
/// <summary>
/// Effettua una generica chiamata ApiRest
/// </summary>
/// <param name="ApiUrl">URL Api di base</param>
/// <param name="ApiRequest">Richiesta completa</param>
/// <param name="JsonBody">Corpo Json trasmesso con richiesta</param>
/// <returns></returns>
public async Task<RestResponse> CallRestPost(string ApiUrl, string ApiRequest, string JsonBody)
{
RestResponse response;
// cerco online
using (RestClient client = new RestClient(ApiUrl))
{
var request = new RestRequest(ApiRequest, Method.Post);
string rawData = JsonConvert.SerializeObject(JsonBody);
request.AddJsonBody(rawData);
response = await client.ExecutePostAsync(request);
}
return response;
}
/// <summary>
/// Calcola URL immagine dato
/// </summary>
@@ -70,6 +119,41 @@ namespace EgwCoreLib.Lux.Data.Services
return $"{rawVal}";
}
/// <summary>
/// Calcola URL immagine dato
/// </summary>
/// <param name="baseUrl">Base url IMG server (da config)</param>
/// <param name="imgUID">UID elemento/immagine</param>
/// <param name="serJwd">JWD serializzato di cui si richiede l'immagine aggiornata</param>
/// <returns></returns>
public string ReqUpdateSvg(string baseUrl, string imgUID, string serJwd)
{
string answ = "";
if (imgUID.EndsWith(".svg"))
{
imgUID.Replace(".svg", "");
}
string fullUrl = $"{baseUrl}/svg-preview/{imgUID}".Replace("////", "//");
return answ;
}
/// <summary>
/// Salva e invia su channel img redis SVG per il doc richiesto
/// </summary>
/// <param name="imgId"></param>
/// <param name="bomContent"></param>
public async Task<bool> SaveBomAsync(string imgId, string bomContent)
{
// salvo img in cache
string currKey = $"{redisBaseKey}:BOM:{imgId.Replace("/", ":")}";
var done = await _redisService.SetAsync(currKey, bomContent);
// invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale
string notifyChannel = $"{bomChannel}:{imgId}";
long numSent = await _redisService.PublishAsync(notifyChannel, bomContent);
return done;
}
/// <summary>
/// Salva e invia su channel img redis SVG per il doc richiesto
/// </summary>
@@ -103,36 +187,43 @@ namespace EgwCoreLib.Lux.Data.Services
return done;
}
/// <summary>
/// Salva e invia su channel img redis SVG per il doc richiesto
/// </summary>
/// <param name="imgId"></param>
/// <param name="bomContent"></param>
public async Task<bool> SaveBomAsync(string imgId, string bomContent)
{
// salvo img in cache
string currKey = $"{redisBaseKey}:BOM:{imgId.Replace("/", ":")}";
var done = await _redisService.SetAsync(currKey, bomContent);
// invio notifica nuova svg generata sul canale... viene inviato solo svg con ID nel canale
string notifyChannel = $"{bomChannel}:{imgId}";
long numSent = await _redisService.PublishAsync(notifyChannel, bomContent);
return done;
}
#endregion Public Methods
#region Private Fields
private static Logger Log = LogManager.GetCurrentClassLogger();
private readonly IRedisService _redisService;
private readonly string cacheTag = "svgfile";
private readonly string liveTag = "svg";
private readonly string svgChannel = "svg:img";
private readonly string apiUrl = "";
private readonly string bomChannel = "bom:item";
private readonly string cacheTag = "svgfile";
private readonly string calcTag = "svgpreview";
private readonly string liveTag = "svg";
private readonly string svgChannel = "svg:img";
private IConfiguration _config;
private string imageBaseUrl = "";
private string redisBaseKey = "Lux";
#endregion Private Fields
#region Private Properties
/// <summary>
/// Conf client RestSharp standard:
/// - base URI al sito
/// - timeout 1 min
/// </summary>
private RestClientOptions restOptStd { get; set; } = new RestClientOptions();
#endregion Private Properties
}
}
+5 -4
View File
@@ -33,7 +33,7 @@ namespace Lux.API.Controllers
/// <summary>
/// Chiamata POST: riceve Json in formato JWD serializzato, invia richeista calcolo modo 2 (BOM)
/// PUT: api/svgPreview/svg/00000000-0000-0000-0000-000000000000
/// PUT: api/window/bom/00000000-0000-0000-0000-000000000000
/// </summary>
/// <param name="id">id oggetto</param>
/// <returns></returns>
@@ -77,7 +77,7 @@ namespace Lux.API.Controllers
/// <summary>
/// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta
/// PUT: api/svgPreview/svg/00000000-0000-0000-0000-000000000000
/// PUT: api/window/svg/00000000-0000-0000-0000-000000000000
/// </summary>
/// <param name="id">id oggetto</param>
/// <returns></returns>
@@ -99,7 +99,7 @@ namespace Lux.API.Controllers
/// <summary>
/// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta
/// PUT: api/svgPreview/svg/00000000-0000-0000-0000-000000000000
/// PUT: api/window/svg-file/00000000-0000-0000-0000-000000000000
/// </summary>
/// <param name="id">id oggetto</param>
/// <returns></returns>
@@ -130,10 +130,11 @@ namespace Lux.API.Controllers
/// <summary>
/// Chiamata POST: riceve Json in formato JWD serializzato, lo ripete come risposta
/// PUT: api/svg-preview/svg/00000000-0000-0000-0000-000000000000
/// PUT: api/window/svg-preview/00000000-0000-0000-0000-000000000000
/// </summary>
/// <param name="id">id oggetto</param>
/// <returns></returns>
[HttpPost("svgpreview/{id}")]
[HttpPost("svg-preview/{id}")]
public async Task<ActionResult<string>> svgPreview(string id, [FromBody] string currJwd)
{
+1 -1
View File
@@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>0.9.2508.0411</Version>
<Version>0.9.2508.0412</Version>
</PropertyGroup>
<ItemGroup>
+2 -2
View File
@@ -47,8 +47,8 @@
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-cloud-moon px-2 fs-4" aria-hidden="true"></span> <span class="@hideText">Weather</span>
<NavLink class="nav-link" href="scratch">
<span class="bi bi-cloud-moon px-2 fs-4" aria-hidden="true"></span> <span class="@hideText">Scratch</span>
</NavLink>
</div>
+23
View File
@@ -0,0 +1,23 @@
@page "/scratch"
<h3>Scratch Test</h3>
<div class="row">
<div class="col-4">
<div class="form-floating mb-3">
<input type="text" class="form-control" placeholder="UID Finestra" @bind="@windowUid">
<label for="floatingInput">UID Finestra</label>
</div>
<div class="form-floating mb-3">
<textarea class="form-control small" style="min-height: 20rem;">@demoJwd</textarea>
<label for="floatingInput">JWD demo</label>
</div>
<button class="btn btn-primary" @onclick="() => SendCalc()">Req Calc</button>
</div>
<div class="col-8">
@outSvg
</div>
</div>
+66
View File
@@ -0,0 +1,66 @@
using EgwCoreLib.Lux.Data;
using EgwCoreLib.Lux.Data.Services;
using Microsoft.AspNetCore.Components;
namespace Lux.UI.Components.Pages
{
public partial class Scratch
{
private string windowUid = "TestWindow";
/// <summary>
/// Demorichiesta jwd x fare test richiesta calcolo
/// </summary>
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}";
[Inject]
protected ImageCacheService ICService { get; set; } = null!;
[Inject]
protected IRedisService RedServ { get; set; } = null!;
[Inject]
protected IConfiguration Config { get; set; } = null!;
private string apiUrl = "";
private string calcTag = "";
private string subChannel = "";
private string currChannel = "";
protected override Task OnInitializedAsync()
{
apiUrl = Config.GetValue<string>("ServerConf:Prog.ApiUrl") ?? "";
calcTag = Config.GetValue<string>("ServerConf:ImageCalcTag") ?? "";
subChannel = Config.GetValue<string>("ServerConf:SvgChannel") ?? "";
RedServ.Subscribe($"{subChannel}:*", (ch, msg) =>
{
saveSvg($"{ch}", $"{msg}");
});
return Task.CompletedTask;
}
private void saveSvg(string channel, string newSvg)
{
// se è la mia immagine
if (channel.EndsWith(windowUid) || true)
{
currSvg = newSvg;
}
}
protected async Task SendCalc()
{
await ICService.CallRestPost(apiUrl, $"{calcTag}/{windowUid}", demoJwd);
}
protected MarkupString outSvg { get; set; } = (MarkupString)"";
private string currSvg = "";
}
}
-64
View File
@@ -1,64 +0,0 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-Lux.UI-a758c101-a2f4-4e38-977d-1c4887dbbd50</UserSecretsId>
<Version>0.9.2508.0411</Version>
<Version>0.9.2508.0412</Version>
</PropertyGroup>
<ItemGroup>
+12
View File
@@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Localization;
using Microsoft.EntityFrameworkCore;
using NLog;
using NLog.Web;
using StackExchange.Redis;
using System.Globalization;
var builder = WebApplication.CreateBuilder(args);
@@ -50,8 +51,19 @@ builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.Requ
builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
// costruzione connectionMultiplexer redis...
string connStr = configuration.GetConnectionString("Redis") ?? "localhost";
ConnectionMultiplexer redisConn = ConnectionMultiplexer.Connect(connStr);
// registro connMultiplexer REDIS
builder.Services.AddSingleton<IConnectionMultiplexer>(redisConn);
// registro wrapper servizi REDIS
builder.Services.AddSingleton<IRedisService, RedisService>();
builder.Services.AddSingleton<RedisSubscriptionManager>();
// Aggiunta servizi specifici
builder.Services.AddSingleton<DataLayerServices>();
builder.Services.AddSingleton<ImageCacheService>();
var app = builder.Build();
+2
View File
@@ -58,6 +58,8 @@
"Redis": "redis.ufficio:26379, serviceName=devel, DefaultDatabase=6, keepAlive=180, connectTimeout=15000, syncTimeout=15000, asyncTimeout=15000, abortConnect=false, ssl=false, allowAdmin=true"
},
"ServerConf": {
"ImageCalcTag": "svg-preview",
"Prog.ApiUrl": "https://iis01.egalware.com/lux/srv/api/window",
"PubChannel": "EgwEngineInput",
"SubChannel": "EgwEngineOutput",
"SvgChannel": "svg:img",
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<i>LUX - Web Windows MES</i>
<h4>Versione: 0.9.2508.0411</h4>
<h4>Versione: 0.9.2508.0412</h4>
<br /> Note di rilascio:
<ul>
<li>
+1 -1
View File
@@ -1 +1 @@
0.9.2508.0411
0.9.2508.0412
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<item>
<version>0.9.2508.0411</version>
<version>0.9.2508.0412</version>
<url>http://nexus.steamware.net/repository/SWS/GPW/stable/GPW.UI.zip</url>
<changelog>http://nexus.steamware.net/repository/SWS/GPW/stable/ChangeLog.html</changelog>
<mandatory>false</mandatory>