- 将网关服务相关实体Id类型由long改为string,统一使用Guid V7格式Id - 新增ConsoleDbContext,配置数据库表命名规范,适配PostgreSQL约定 - 引入IRouteStore和IInstanceStore接口,替代直接使用DbContext访问数据库 - 修改GatewayService实现,调用存储接口进行数据操作以支持解耦扩展 - 调整GatewayController中实例Id参数类型为string,保证一致性 - 更新GatewayDto中各实体的Id类型为string,确保与数据库模型匹配 - 在项目配置中添加EntityFrameworkCore.Design依赖及版本更新 - 新增DesignTimeDbContextFactory方便迁移和设计时上下文创建 - 删除appsettings.json中的GatewayConnection配置,简化连接字符串配置
366 lines
13 KiB
C#
366 lines
13 KiB
C#
using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
|
|
using Fengling.Platform.Infrastructure;
|
|
using Fengling.Console.Models.Dtos;
|
|
|
|
namespace Fengling.Console.Services;
|
|
|
|
public interface IGatewayService
|
|
{
|
|
Task<GatewayStatisticsDto> GetStatisticsAsync();
|
|
Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null);
|
|
Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null);
|
|
Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto);
|
|
Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null);
|
|
Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false);
|
|
Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto);
|
|
Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId);
|
|
Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto);
|
|
Task<bool> RemoveInstanceAsync(string instanceId);
|
|
Task<bool> UpdateInstanceWeightAsync(string instanceId, int weight);
|
|
Task ReloadGatewayAsync();
|
|
}
|
|
|
|
public class GatewayService : IGatewayService
|
|
{
|
|
private readonly IRouteStore _routeStore;
|
|
private readonly IInstanceStore _instanceStore;
|
|
private readonly ILogger<GatewayService> _logger;
|
|
|
|
public GatewayService(
|
|
IRouteStore routeStore,
|
|
IInstanceStore instanceStore,
|
|
ILogger<GatewayService> logger)
|
|
{
|
|
_routeStore = routeStore;
|
|
_instanceStore = instanceStore;
|
|
_logger = logger;
|
|
}
|
|
|
|
public async Task<GatewayStatisticsDto> GetStatisticsAsync()
|
|
{
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var instances = await _instanceStore.GetAllAsync();
|
|
|
|
var activeRoutes = routes.Where(r => !r.IsDeleted).ToList();
|
|
var activeInstances = instances.Where(i => !i.IsDeleted).ToList();
|
|
|
|
return new GatewayStatisticsDto
|
|
{
|
|
TotalServices = activeRoutes.Select(r => r.ServiceName).Distinct().Count(),
|
|
GlobalRoutes = activeRoutes.Count(r => r.IsGlobal),
|
|
TenantRoutes = activeRoutes.Count(r => !r.IsGlobal),
|
|
TotalInstances = activeInstances.Count,
|
|
HealthyInstances = activeInstances.Count(i => i.Health == (int)InstanceHealth.Healthy),
|
|
RecentServices = activeRoutes
|
|
.OrderByDescending(r => r.CreatedTime)
|
|
.Take(5)
|
|
.Select(MapToServiceDto)
|
|
.ToList()
|
|
};
|
|
}
|
|
|
|
public async Task<List<GatewayServiceDto>> GetServicesAsync(bool globalOnly = false, string? tenantCode = null)
|
|
{
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var instances = await _instanceStore.GetAllAsync();
|
|
|
|
var query = routes.Where(r => !r.IsDeleted);
|
|
|
|
if (globalOnly)
|
|
query = query.Where(r => r.IsGlobal);
|
|
else if (!string.IsNullOrEmpty(tenantCode))
|
|
query = query.Where(r => r.TenantCode == tenantCode);
|
|
|
|
var routeList = query.OrderByDescending(r => r.CreatedTime).ToList();
|
|
var clusters = routeList.Select(r => r.ClusterId).Distinct().ToList();
|
|
|
|
var instancesDict = instances
|
|
.Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
|
|
.GroupBy(i => i.ClusterId)
|
|
.ToDictionary(g => g.Key, g => g.Count());
|
|
|
|
return routeList.Select(r => MapToServiceDto(r, instancesDict.GetValueOrDefault(r.ClusterId, 0))).ToList();
|
|
}
|
|
|
|
public async Task<GatewayServiceDto?> GetServiceAsync(string serviceName, string? tenantCode = null)
|
|
{
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var route = routes.FirstOrDefault(r =>
|
|
r.ServiceName == serviceName &&
|
|
!r.IsDeleted &&
|
|
(r.IsGlobal || r.TenantCode == tenantCode));
|
|
|
|
if (route == null) return null;
|
|
|
|
var instances = await _instanceStore.GetAllAsync();
|
|
var instanceCount = instances.Count(i => i.ClusterId == route.ClusterId && !i.IsDeleted);
|
|
|
|
return MapToServiceDto(route, instanceCount);
|
|
}
|
|
|
|
public async Task<GatewayServiceDto> RegisterServiceAsync(CreateGatewayServiceDto dto)
|
|
{
|
|
var clusterId = $"{dto.ServicePrefix}-service";
|
|
var pathPattern = $"/{dto.ServicePrefix}/{dto.Version}/{{**path}}";
|
|
var destinationId = string.IsNullOrEmpty(dto.DestinationId)
|
|
? $"{dto.ServicePrefix}-1"
|
|
: dto.DestinationId;
|
|
|
|
// Check if route already exists
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var existingRoute = routes.FirstOrDefault(r =>
|
|
r.ServiceName == dto.ServicePrefix &&
|
|
r.IsGlobal == dto.IsGlobal &&
|
|
(dto.IsGlobal || r.TenantCode == dto.TenantCode));
|
|
|
|
if (existingRoute != null)
|
|
{
|
|
throw new InvalidOperationException($"Service {dto.ServicePrefix} already registered");
|
|
}
|
|
|
|
// Add instance
|
|
var instanceId = Guid.CreateVersion7().ToString("N");
|
|
var instance = new GwServiceInstance
|
|
{
|
|
Id = instanceId,
|
|
ClusterId = clusterId,
|
|
DestinationId = destinationId,
|
|
Address = dto.ServiceAddress,
|
|
Weight = dto.Weight,
|
|
Health = (int)InstanceHealth.Healthy,
|
|
Status = (int)InstanceStatus.Active,
|
|
CreatedTime = DateTime.UtcNow
|
|
};
|
|
await _instanceStore.CreateAsync(instance);
|
|
|
|
// Add route
|
|
var routeId = Guid.CreateVersion7().ToString("N");
|
|
var route = new GwTenantRoute
|
|
{
|
|
Id = routeId,
|
|
TenantCode = dto.IsGlobal ? "" : dto.TenantCode ?? "",
|
|
ServiceName = dto.ServicePrefix,
|
|
ClusterId = clusterId,
|
|
PathPattern = pathPattern,
|
|
Priority = dto.IsGlobal ? 0 : 10,
|
|
Status = (int)RouteStatus.Active,
|
|
IsGlobal = dto.IsGlobal,
|
|
CreatedTime = DateTime.UtcNow
|
|
};
|
|
await _routeStore.CreateAsync(route);
|
|
|
|
_logger.LogInformation("Registered service {Service} at {Address}", dto.ServicePrefix, dto.ServiceAddress);
|
|
|
|
return MapToServiceDto(route, 1);
|
|
}
|
|
|
|
public async Task<bool> UnregisterServiceAsync(string serviceName, string? tenantCode = null)
|
|
{
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var route = routes.FirstOrDefault(r =>
|
|
r.ServiceName == serviceName &&
|
|
!r.IsDeleted &&
|
|
(r.IsGlobal || r.TenantCode == tenantCode));
|
|
|
|
if (route == null) return false;
|
|
|
|
// Soft delete route
|
|
route.IsDeleted = true;
|
|
route.UpdatedTime = DateTime.UtcNow;
|
|
await _routeStore.UpdateAsync(route);
|
|
|
|
// Soft delete instances
|
|
var instances = await _instanceStore.GetAllAsync();
|
|
var routeInstances = instances.Where(i => i.ClusterId == route.ClusterId && !i.IsDeleted).ToList();
|
|
|
|
foreach (var instance in routeInstances)
|
|
{
|
|
instance.IsDeleted = true;
|
|
instance.UpdatedTime = DateTime.UtcNow;
|
|
await _instanceStore.UpdateAsync(instance);
|
|
}
|
|
|
|
_logger.LogInformation("Unregistered service {Service}", serviceName);
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<List<GatewayRouteDto>> GetRoutesAsync(bool globalOnly = false)
|
|
{
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var instances = await _instanceStore.GetAllAsync();
|
|
|
|
var query = routes.Where(r => !r.IsDeleted);
|
|
|
|
if (globalOnly)
|
|
query = query.Where(r => r.IsGlobal);
|
|
|
|
var routeList = query.OrderByDescending(r => r.Priority).ToList();
|
|
var clusters = routeList.Select(r => r.ClusterId).Distinct().ToList();
|
|
|
|
var instancesDict = instances
|
|
.Where(i => clusters.Contains(i.ClusterId) && !i.IsDeleted)
|
|
.GroupBy(i => i.ClusterId)
|
|
.ToDictionary(g => g.Key, g => g.Count());
|
|
|
|
return routeList.Select(r => new GatewayRouteDto
|
|
{
|
|
Id = r.Id,
|
|
ServiceName = r.ServiceName,
|
|
ClusterId = r.ClusterId,
|
|
PathPattern = r.PathPattern,
|
|
Priority = r.Priority,
|
|
IsGlobal = r.IsGlobal,
|
|
TenantCode = r.TenantCode,
|
|
Status = (int)r.Status,
|
|
InstanceCount = instancesDict.GetValueOrDefault(r.ClusterId, 0)
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<GatewayRouteDto> CreateRouteAsync(CreateGatewayRouteDto dto)
|
|
{
|
|
var routes = await _routeStore.GetAllAsync();
|
|
var existing = routes.FirstOrDefault(r =>
|
|
r.ServiceName == dto.ServiceName &&
|
|
r.IsGlobal == dto.IsGlobal &&
|
|
(dto.IsGlobal || r.TenantCode == dto.TenantCode));
|
|
|
|
if (existing != null)
|
|
{
|
|
throw new InvalidOperationException($"Route for {dto.ServiceName} already exists");
|
|
}
|
|
|
|
var route = new GwTenantRoute
|
|
{
|
|
Id = Guid.CreateVersion7().ToString("N"),
|
|
TenantCode = dto.IsGlobal ? "" : dto.TenantCode ?? "",
|
|
ServiceName = dto.ServiceName,
|
|
ClusterId = dto.ClusterId,
|
|
PathPattern = dto.PathPattern,
|
|
Priority = dto.Priority,
|
|
Status = (int)RouteStatus.Active,
|
|
IsGlobal = dto.IsGlobal,
|
|
CreatedTime = DateTime.UtcNow
|
|
};
|
|
|
|
await _routeStore.CreateAsync(route);
|
|
|
|
return new GatewayRouteDto
|
|
{
|
|
Id = route.Id,
|
|
ServiceName = route.ServiceName,
|
|
ClusterId = route.ClusterId,
|
|
PathPattern = route.PathPattern,
|
|
Priority = route.Priority,
|
|
IsGlobal = route.IsGlobal,
|
|
TenantCode = route.TenantCode,
|
|
Status = (int)route.Status,
|
|
InstanceCount = 0
|
|
};
|
|
}
|
|
|
|
public async Task<List<GatewayInstanceDto>> GetInstancesAsync(string clusterId)
|
|
{
|
|
var instances = await _instanceStore.GetAllAsync();
|
|
var clusterInstances = instances
|
|
.Where(i => i.ClusterId == clusterId && !i.IsDeleted)
|
|
.OrderByDescending(i => i.Weight)
|
|
.ToList();
|
|
|
|
return clusterInstances.Select(i => new GatewayInstanceDto
|
|
{
|
|
Id = i.Id,
|
|
ClusterId = i.ClusterId,
|
|
DestinationId = i.DestinationId,
|
|
Address = i.Address,
|
|
Weight = i.Weight,
|
|
Health = (int)i.Health,
|
|
Status = (int)i.Status,
|
|
CreatedAt = i.CreatedTime
|
|
}).ToList();
|
|
}
|
|
|
|
public async Task<GatewayInstanceDto> AddInstanceAsync(CreateGatewayInstanceDto dto)
|
|
{
|
|
var existing = await _instanceStore.FindByDestinationAsync(dto.ClusterId, dto.DestinationId);
|
|
if (existing != null && !existing.IsDeleted)
|
|
{
|
|
throw new InvalidOperationException($"Instance {dto.DestinationId} already exists in cluster {dto.ClusterId}");
|
|
}
|
|
|
|
var instance = new GwServiceInstance
|
|
{
|
|
Id = Guid.CreateVersion7().ToString("N"),
|
|
ClusterId = dto.ClusterId,
|
|
DestinationId = dto.DestinationId,
|
|
Address = dto.Address,
|
|
Weight = dto.Weight,
|
|
Health = (int)InstanceHealth.Healthy,
|
|
Status = (int)InstanceStatus.Active,
|
|
CreatedTime = DateTime.UtcNow
|
|
};
|
|
|
|
await _instanceStore.CreateAsync(instance);
|
|
|
|
return new GatewayInstanceDto
|
|
{
|
|
Id = instance.Id,
|
|
ClusterId = instance.ClusterId,
|
|
DestinationId = instance.DestinationId,
|
|
Address = instance.Address,
|
|
Weight = instance.Weight,
|
|
Health = (int)instance.Health,
|
|
Status = (int)instance.Status,
|
|
CreatedAt = instance.CreatedTime
|
|
};
|
|
}
|
|
|
|
public async Task<bool> RemoveInstanceAsync(string instanceId)
|
|
{
|
|
var instance = await _instanceStore.FindByIdAsync(instanceId);
|
|
if (instance == null) return false;
|
|
|
|
instance.IsDeleted = true;
|
|
instance.UpdatedTime = DateTime.UtcNow;
|
|
await _instanceStore.UpdateAsync(instance);
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> UpdateInstanceWeightAsync(string instanceId, int weight)
|
|
{
|
|
var instance = await _instanceStore.FindByIdAsync(instanceId);
|
|
if (instance == null) return false;
|
|
|
|
instance.Weight = weight;
|
|
instance.UpdatedTime = DateTime.UtcNow;
|
|
await _instanceStore.UpdateAsync(instance);
|
|
return true;
|
|
}
|
|
|
|
public async Task ReloadGatewayAsync()
|
|
{
|
|
_logger.LogInformation("Gateway configuration reloaded");
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
private static GatewayServiceDto MapToServiceDto(GwTenantRoute route, int instanceCount = 0)
|
|
{
|
|
return new GatewayServiceDto
|
|
{
|
|
Id = route.Id,
|
|
ServicePrefix = route.ServiceName,
|
|
ServiceName = route.ServiceName,
|
|
ClusterId = route.ClusterId,
|
|
PathPattern = route.PathPattern,
|
|
ServiceAddress = "",
|
|
DestinationId = "",
|
|
Weight = 1,
|
|
InstanceCount = instanceCount,
|
|
IsGlobal = route.IsGlobal,
|
|
TenantCode = route.TenantCode,
|
|
Status = (int)route.Status,
|
|
CreatedAt = route.CreatedTime
|
|
};
|
|
}
|
|
}
|