Files
Finlytic/FinlyticBackend/Services/FavoritesPriceBackgroundService.cs
T

93 lines
3.3 KiB
C#

using FinlyticBackend.Database;
using FinlyticBackend.Hubs;
using FinlyticBackend.Util;
using FinlyticCore.Dtos;
using FinlyticCore.Dtos.TechnicalAnalysis;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
namespace FinlyticBackend.Services;
public class FavoritesPriceBackgroundService(
IHubContext<FavoritesPriceHub> hubContext,
WebMqttClient mqttClient,
IServiceScopeFactory scopeFactory,
ILogger<FavoritesPriceBackgroundService> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Delay(4000, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
var activeUserIds = FavoritesPriceHub.GetActiveUserIds();
if (activeUserIds.Length > 0)
{
await ProcessActiveUserPricesAsync(activeUserIds, stoppingToken);
}
}
catch (Exception ex)
{
logger.LogWarning(ex, "[FavoritesPriceBackgroundService] Error broadcasting price updates.");
}
await Task.Delay(2000, stoppingToken);
}
}
private async Task ProcessActiveUserPricesAsync(string[] activeUserIds, CancellationToken cancellationToken)
{
using var scope = scopeFactory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<BackendDbContext>();
// 1. Direkt per EF nach aktiven Usern filtern & laden
var userFavorites = await db.UserFavoriteAssets
.AsNoTracking()
.Where(f => activeUserIds.Contains(f.UserId.ToString()))
.ToListAsync(cancellationToken);
if (userFavorites.Count == 0) return;
// 2. Pro User einfach die Kurse parallel abfragen und senden
foreach (var userGroup in userFavorites.GroupBy(f => f.UserId.ToString()))
{
var userId = userGroup.Key;
var priceUpdates = new System.Collections.Concurrent.ConcurrentDictionary<string, object>();
var tasks = userGroup.Select(async fav =>
{
var cleanIsin = fav.Isin.Trim().ToUpperInvariant();
if (!mqttClient.IsConnected) return;
try
{
var livePrice = await mqttClient.SendRpcRequestAsync<LivePriceDto, IsinRequest>(
"tr_GetLivePrice",
new IsinRequest(cleanIsin),
TimeSpan.FromSeconds(4)
);
if (livePrice != null)
{
priceUpdates[cleanIsin] = new
{
currentPrice = (double)livePrice.CurrentPrice,
dailyChangePercent = (double)livePrice.DailyChangePercent
};
}
}
catch { /* Ignorieren bei Einzel-Timeout */ }
});
await Task.WhenAll(tasks);
if (!priceUpdates.IsEmpty)
{
await hubContext.Clients.Group(FavoritesPriceHub.GetGroupName(userId))
.SendAsync("ReceiveFavoritePrices", new Dictionary<string, object>(priceUpdates), cancellationToken);
}
}
}
}