using System.Collections.Concurrent; using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; using YarpGateway.Data; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; namespace YarpGateway.Services; public class RouteInfo { public string Id { get; set; } = string.Empty; public string ClusterId { get; set; } = string.Empty; public string PathPattern { get; set; } = string.Empty; public int Priority { get; set; } } public interface IRouteCache { Task InitializeAsync(); Task ReloadAsync(); RouteInfo? GetRoute(string serviceName); RouteInfo? GetRouteByPath(string path); } public class RouteCache : IRouteCache { private readonly IDbContextFactory _dbContextFactory; private readonly ILogger _logger; private readonly ConcurrentDictionary _routes = new(); private readonly ConcurrentDictionary _pathRoutes = new(); private readonly ReaderWriterLockSlim _lock = new(); public RouteCache(IDbContextFactory dbContextFactory, ILogger logger) { _dbContextFactory = dbContextFactory; _logger = logger; } public async Task InitializeAsync() { _logger.LogInformation("Initializing route cache from database..."); try { await LoadFromDatabaseAsync(); _logger.LogInformation("Route cache initialized: {Count} routes", _routes.Count); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to load routes from database. This is normal if database is not initialized yet."); } } public async Task ReloadAsync() { _logger.LogInformation("Reloading route cache..."); await LoadFromDatabaseAsync(); _logger.LogInformation("Route cache reloaded: {Count} routes", _routes.Count); } public RouteInfo? GetRoute(string? serviceName) { if (string.IsNullOrEmpty(serviceName)) { _logger.LogDebug("GetRoute called with null or empty serviceName"); return null; } _lock.EnterReadLock(); try { if (_routes.TryGetValue(serviceName, out var route)) { _logger.LogDebug("Found route: {Service} -> {Cluster}", serviceName, route.ClusterId); return route; } _logger.LogWarning("No route found for: {Service}", serviceName); return null; } finally { _lock.ExitReadLock(); } } public RouteInfo? GetRouteByPath(string path) { return _pathRoutes.TryGetValue(path, out var route) ? route : null; } private async Task LoadFromDatabaseAsync() { using var db = _dbContextFactory.CreateDbContext(); List routes; try { routes = await db.GwRoutes .Where(r => r.Status == 1 && !r.IsDeleted) .ToListAsync(); } catch (Exception ex) { _logger.LogWarning(ex, "Database table not found. Returning empty route list."); routes = new List(); } _lock.EnterWriteLock(); try { _routes.Clear(); _pathRoutes.Clear(); foreach (var route in routes) { var pathPattern = route.Match?.Path ?? string.Empty; var routeInfo = new RouteInfo { Id = route.Id, ClusterId = route.ClusterId, PathPattern = pathPattern, Priority = route.Priority }; _routes[route.ServiceName] = routeInfo; _pathRoutes[pathPattern] = routeInfo; _logger.LogDebug("Loaded route: {Service} -> {Cluster}", route.ServiceName, route.ClusterId); } } finally { _lock.ExitWriteLock(); } } }