fengling-gateway/src/Services/RouteCache.cs
movingsam 564d664426
Some checks failed
Build and Deploy / build (push) Successful in 22s
Build and Deploy / docker (push) Successful in 26m23s
Build and Deploy / deploy (push) Failing after 3s
refactor: move project to src/ and add slnx
- Add YarpGateway.slnx solution file
- Move all project files to src/ directory
- Update Dockerfile for new src/ path structure
- Update CI/CD workflow with src/ project path
- Fix NuGet package references (use Gitea NuGet packages)
- Add CPM (Central Package Management) with Directory.Packages.props
2026-02-28 13:10:41 +08:00

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();
}
}
}