using System.Collections.Concurrent; using YarpGateway.Models; using YarpGateway.Data; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; namespace YarpGateway.Services; public class RouteInfo { public long Id { get; set; } public string ClusterId { get; set; } = string.Empty; public string PathPattern { get; set; } = string.Empty; public int Priority { get; set; } public bool IsGlobal { get; set; } } public interface IRouteCache { Task InitializeAsync(); Task ReloadAsync(); RouteInfo? GetRoute(string tenantCode, string serviceName); RouteInfo? GetRouteByPath(string path); } public class RouteCache : IRouteCache { private readonly IDbContextFactory _dbContextFactory; private readonly ILogger _logger; private readonly ConcurrentDictionary _globalRoutes = new(); private readonly ConcurrentDictionary> _tenantRoutes = 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..."); await LoadFromDatabaseAsync(); _logger.LogInformation("Route cache initialized: {GlobalCount} global routes, {TenantCount} tenant routes", _globalRoutes.Count, _tenantRoutes.Count); } public async Task ReloadAsync() { _logger.LogInformation("Reloading route cache..."); await LoadFromDatabaseAsync(); _logger.LogInformation("Route cache reloaded"); } public RouteInfo? GetRoute(string tenantCode, string serviceName) { _lock.EnterUpgradeableReadLock(); try { // 1. 优先查找租户专用路由 if (_tenantRoutes.TryGetValue(tenantCode, out var tenantRouteMap) && tenantRouteMap.TryGetValue(serviceName, out var tenantRoute)) { _logger.LogDebug("Found tenant-specific route: {Tenant}/{Service} -> {Cluster}", tenantCode, serviceName, tenantRoute.ClusterId); return tenantRoute; } // 2. 查找全局路由 if (_globalRoutes.TryGetValue(serviceName, out var globalRoute)) { _logger.LogDebug("Found global route: {Service} -> {Cluster} for tenant {Tenant}", serviceName, globalRoute.ClusterId, tenantCode); return globalRoute; } // 3. 没找到 _logger.LogWarning("No route found for: {Tenant}/{Service}", tenantCode, serviceName); return null; } finally { _lock.ExitUpgradeableReadLock(); } } public RouteInfo? GetRouteByPath(string path) { return _pathRoutes.TryGetValue(path, out var route) ? route : null; } private async Task LoadFromDatabaseAsync() { using var db = _dbContextFactory.CreateDbContext(); var routes = await db.TenantRoutes .Where(r => r.Status == 1 && !r.IsDeleted) .ToListAsync(); _lock.EnterWriteLock(); try { _globalRoutes.Clear(); _tenantRoutes.Clear(); _pathRoutes.Clear(); foreach (var route in routes) { var routeInfo = new RouteInfo { Id = route.Id, ClusterId = route.ClusterId, PathPattern = route.PathPattern, Priority = route.Priority, IsGlobal = route.IsGlobal }; if (route.IsGlobal) { _globalRoutes[route.ServiceName] = routeInfo; _pathRoutes[route.PathPattern] = routeInfo; } else if (!string.IsNullOrEmpty(route.TenantCode)) { _tenantRoutes.GetOrAdd(route.TenantCode, _ => new()) [route.ServiceName] = routeInfo; _pathRoutes[route.PathPattern] = routeInfo; } } } finally { _lock.ExitWriteLock(); } } }