feat(backend): generic settings RPC bridge, LogStreamHub SignalR, and log ringbuffer

This commit is contained in:
2026-08-15 21:30:56 +02:00
parent 1f9d66405a
commit f18f75c1ab
5 changed files with 207 additions and 65 deletions
+29 -1
View File
@@ -7,6 +7,7 @@ using System.Threading.Tasks;
using FinlyticBackend.Database;
using FinlyticBackend.Hubs;
using FinlyticBackend.Services;
using FinlyticCore.Dtos.Logging;
using FinlyticCore.Dtos.News;
using FinlyticCore.Models;
using FinlyticCore.Models.Auth;
@@ -28,12 +29,14 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
{
public static readonly ConcurrentDictionary<string, JsonElement> FundamentalsCache = new(StringComparer.OrdinalIgnoreCase);
public static readonly ConcurrentDictionary<string, JsonElement> TechnicalsCache = new(StringComparer.OrdinalIgnoreCase);
public static readonly ConcurrentDictionary<string, ConcurrentQueue<LogMessageDto>> ServiceLogsRingBuffer = new(StringComparer.OrdinalIgnoreCase);
private readonly IConfiguration _configuration;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IHubContext<TradeRealtimeHub, ITradeClient> _hubContext;
private readonly IHubContext<TradeHub> _tradeHubContext;
private readonly IHubContext<NewsHub> _newsHubContext;
private readonly IHubContext<LogStreamHub> _logHubContext;
private readonly IFirebaseNotificationService _firebaseService;
private readonly ILogger<BackendMqttBridge> _logger;
@@ -43,6 +46,7 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
IHubContext<TradeRealtimeHub, ITradeClient> hubContext,
IHubContext<TradeHub> tradeHubContext,
IHubContext<NewsHub> newsHubContext,
IHubContext<LogStreamHub> logHubContext,
IFirebaseNotificationService firebaseService,
ILogger<BackendMqttBridge> logger) : base(logger)
{
@@ -51,6 +55,7 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
_hubContext = hubContext;
_tradeHubContext = tradeHubContext;
_newsHubContext = newsHubContext;
_logHubContext = logHubContext;
_firebaseService = firebaseService;
_logger = logger;
}
@@ -95,6 +100,8 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
await SubscribeAsync("finlytic/technicalanalysis/#");
await SubscribeAsync("finlytic/ta/#");
// Real-time Logs
await SubscribeAsync("finlytic/logs/#");
}
/// <inheritdoc />
@@ -104,7 +111,11 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
try
{
if (topic.StartsWith("finlytic/trades/proposed/", StringComparison.OrdinalIgnoreCase) ||
if (topic.StartsWith("finlytic/logs/", StringComparison.OrdinalIgnoreCase))
{
await HandleLogMessageAsync(payloadStr);
}
else if (topic.StartsWith("finlytic/trades/proposed/", StringComparison.OrdinalIgnoreCase) ||
topic.StartsWith("finlytic/trades/update", StringComparison.OrdinalIgnoreCase))
{
await HandleTradeProposalAsync(payloadStr);
@@ -136,6 +147,23 @@ public class BackendMqttBridge : ManagedMqttClient, IHostedService
}
}
private async Task HandleLogMessageAsync(string payloadStr)
{
var logDto = JsonSerializer.Deserialize<LogMessageDto>(payloadStr, FinlyticJsonSerializerContext.Default.LogMessageDto);
if (logDto == null) return;
string serviceKey = logDto.ServiceName;
var queue = ServiceLogsRingBuffer.GetOrAdd(serviceKey, _ => new ConcurrentQueue<LogMessageDto>());
queue.Enqueue(logDto);
// Keep buffer capped at 250 entries
while (queue.Count > 250 && queue.TryDequeue(out _)) { }
// Broadcast to SignalR clients
await _logHubContext.Clients.Group(serviceKey).SendAsync("ReceiveLogMessage", logDto);
await _logHubContext.Clients.All.SendAsync("ReceiveLogMessage", logDto);
}
private async Task HandleTradeProposalAsync(string payloadStr)
{
var proposal = JsonSerializer.Deserialize<TradeProposalDto>(payloadStr);
+18 -1
View File
@@ -61,10 +61,27 @@ public class WebMqttClient : ManagedMqttClient, IHostedService
await SubscribeAsync("services/response/assets_GetDiscovery/#");
await SubscribeAsync("services/response/assets_GetDerivatives/#");
await SubscribeAsync("services/response/trades_Get/#");
await SubscribeAsync("services/response/trades_Close/#");
await SubscribeAsync("services/response/trades_Reject/#");
await SubscribeAsync("services/response/trades_Accept/#");
await SubscribeAsync("services/response/analyzer_TriggerManual/#");
await SubscribeAsync("services/response/health_Ping/#");
// Settings RPC response channels for all microservices
await SubscribeAsync("services/response/fundamentals_settings_GetAll/#");
await SubscribeAsync("services/response/fundamentals_settings_Update/#");
await SubscribeAsync("services/response/news_settings_GetAll/#");
await SubscribeAsync("services/response/news_settings_Update/#");
await SubscribeAsync("services/response/ta_settings_GetAll/#");
await SubscribeAsync("services/response/ta_settings_Update/#");
await SubscribeAsync("services/response/sentiment_settings_GetAll/#");
await SubscribeAsync("services/response/sentiment_settings_Update/#");
await SubscribeAsync("services/response/analyzer_settings_GetAll/#");
await SubscribeAsync("services/response/analyzer_settings_Update/#");
await SubscribeAsync("services/response/trades_settings_GetAll/#");
await SubscribeAsync("services/response/trades_settings_Update/#");
await SubscribeAsync("services/response/assets_settings_GetAll/#");
await SubscribeAsync("services/response/assets_settings_Update/#");
}
protected override Task OnMessageReceivedAsync(string topic, string payload)