using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; using Fengling.Platform.Infrastructure; using Fengling.Console.Models.Dtos; namespace Fengling.Console.Services; public interface IGatewayService { Task GetStatisticsAsync(); Task> GetServicesAsync(bool globalOnly = false, string? tenantCode = null); Task GetServiceAsync(string serviceName, string? tenantCode = null); Task RegisterServiceAsync(CreateGatewayServiceDto dto); Task UnregisterServiceAsync(string serviceName, string? tenantCode = null); Task> GetRoutesAsync(bool globalOnly = false); Task CreateRouteAsync(CreateGatewayRouteDto dto); Task> GetInstancesAsync(string clusterId); Task AddInstanceAsync(CreateGatewayInstanceDto dto); Task RemoveInstanceAsync(string instanceId); Task UpdateInstanceWeightAsync(string instanceId, int weight); Task ReloadGatewayAsync(); } public class GatewayService : IGatewayService { private readonly IRouteStore _routeStore; private readonly IInstanceStore _instanceStore; private readonly ILogger _logger; public GatewayService( IRouteStore routeStore, IInstanceStore instanceStore, ILogger logger) { _routeStore = routeStore; _instanceStore = instanceStore; _logger = logger; } public async Task 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> 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 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 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 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> 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 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> 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 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 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 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 }; } }