139 lines
4.5 KiB
C#
139 lines
4.5 KiB
C#
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<GatewayDbContext> _dbContextFactory;
|
|
private readonly ILogger<RouteCache> _logger;
|
|
|
|
private readonly ConcurrentDictionary<string, RouteInfo> _globalRoutes = new();
|
|
private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, RouteInfo>> _tenantRoutes = new();
|
|
private readonly ConcurrentDictionary<string, RouteInfo> _pathRoutes = new();
|
|
private readonly ReaderWriterLockSlim _lock = new();
|
|
|
|
public RouteCache(IDbContextFactory<GatewayDbContext> dbContextFactory, ILogger<RouteCache> 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();
|
|
}
|
|
}
|
|
}
|