diff --git a/Fengling.Platform.Infrastructure/ClusterStore.cs b/Fengling.Platform.Infrastructure/ClusterStore.cs new file mode 100644 index 0000000..f7a294d --- /dev/null +++ b/Fengling.Platform.Infrastructure/ClusterStore.cs @@ -0,0 +1,153 @@ +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; + +namespace Fengling.Platform.Infrastructure; + +/// +/// 集群存储实现 +/// +public class ClusterStore : IClusterStore + where TContext : PlatformDbContext +{ + private readonly TContext _context; + private readonly DbSet _clusters; + + public ClusterStore(TContext context) + { + _context = context; + _clusters = context.GwClusters; + } + + public void Dispose() { } + + public virtual Task FindByIdAsync(string? id, CancellationToken cancellationToken = default) + { + if (id == null) return Task.FromResult(null); + return _clusters.FirstOrDefaultAsync(c => c.Id == id, cancellationToken); + } + + public virtual Task FindByClusterIdAsync(string clusterId, CancellationToken cancellationToken = default) + { + return _clusters.FirstOrDefaultAsync(c => c.ClusterId == clusterId && !c.IsDeleted, cancellationToken); + } + + public virtual async Task> GetAllAsync(CancellationToken cancellationToken = default) + { + return await _clusters.Where(c => !c.IsDeleted).ToListAsync(cancellationToken); + } + + public virtual async Task> GetPagedAsync(int page, int pageSize, string? clusterId = null, + string? name = null, int? status = null, CancellationToken cancellationToken = default) + { + var query = _clusters.AsQueryable(); + + if (!string.IsNullOrEmpty(clusterId)) + query = query.Where(c => c.ClusterId.Contains(clusterId)); + + if (!string.IsNullOrEmpty(name)) + query = query.Where(c => c.Name.Contains(name)); + + if (status.HasValue) + query = query.Where(c => c.Status == status.Value); + + return await query + .Where(c => !c.IsDeleted) + .OrderByDescending(c => c.CreatedTime) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(cancellationToken); + } + + public virtual async Task GetCountAsync(string? clusterId = null, string? name = null, + int? status = null, CancellationToken cancellationToken = default) + { + var query = _clusters.AsQueryable(); + + if (!string.IsNullOrEmpty(clusterId)) + query = query.Where(c => c.ClusterId.Contains(clusterId)); + + if (!string.IsNullOrEmpty(name)) + query = query.Where(c => c.Name.Contains(name)); + + if (status.HasValue) + query = query.Where(c => c.Status == status.Value); + + return await query.Where(c => !c.IsDeleted).CountAsync(cancellationToken); + } + + public virtual async Task CreateAsync(GwCluster cluster, CancellationToken cancellationToken = default) + { + _clusters.Add(cluster); + await _context.SaveChangesAsync(cancellationToken); + return IdentityResult.Success; + } + + public virtual async Task UpdateAsync(GwCluster cluster, CancellationToken cancellationToken = default) + { + cluster.UpdatedTime = DateTime.UtcNow; + _clusters.Update(cluster); + await _context.SaveChangesAsync(cancellationToken); + return IdentityResult.Success; + } + + public virtual async Task DeleteAsync(GwCluster cluster, CancellationToken cancellationToken = default) + { + // 软删除 + cluster.IsDeleted = true; + cluster.UpdatedTime = DateTime.UtcNow; + _clusters.Update(cluster); + await _context.SaveChangesAsync(cancellationToken); + return IdentityResult.Success; + } + + public virtual async Task AddDestinationAsync(string clusterId, GwDestination destination, CancellationToken cancellationToken = default) + { + var cluster = await _clusters.FirstOrDefaultAsync(c => c.ClusterId == clusterId && !c.IsDeleted, cancellationToken); + if (cluster == null) return null; + + cluster.Destinations.Add(destination); + cluster.UpdatedTime = DateTime.UtcNow; + await _context.SaveChangesAsync(cancellationToken); + return cluster; + } + + public virtual async Task UpdateDestinationAsync(string clusterId, string destinationId, GwDestination destination, CancellationToken cancellationToken = default) + { + var cluster = await _clusters + .Include(c => c.Destinations) + .FirstOrDefaultAsync(c => c.ClusterId == clusterId && !c.IsDeleted, cancellationToken); + + if (cluster == null) return null; + + var existingDest = cluster.Destinations.FirstOrDefault(d => d.DestinationId == destinationId); + if (existingDest == null) return null; + + existingDest.Address = destination.Address; + existingDest.Health = destination.Health; + existingDest.Weight = destination.Weight; + existingDest.HealthStatus = destination.HealthStatus; + existingDest.Status = destination.Status; + + cluster.UpdatedTime = DateTime.UtcNow; + await _context.SaveChangesAsync(cancellationToken); + return cluster; + } + + public virtual async Task RemoveDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken = default) + { + var cluster = await _clusters + .Include(c => c.Destinations) + .FirstOrDefaultAsync(c => c.ClusterId == clusterId && !c.IsDeleted, cancellationToken); + + if (cluster == null) return null; + + var destination = cluster.Destinations.FirstOrDefault(d => d.DestinationId == destinationId); + if (destination == null) return null; + + cluster.Destinations.Remove(destination); + cluster.UpdatedTime = DateTime.UtcNow; + await _context.SaveChangesAsync(cancellationToken); + return cluster; + } +} diff --git a/Fengling.Platform.Infrastructure/Extensions.cs b/Fengling.Platform.Infrastructure/Extensions.cs index f52b040..cf27382 100644 --- a/Fengling.Platform.Infrastructure/Extensions.cs +++ b/Fengling.Platform.Infrastructure/Extensions.cs @@ -25,11 +25,11 @@ public static class Extensions // Gateway 服务 services.AddScoped>(); - services.AddScoped>(); + services.AddScoped>(); services.AddScoped(); serviceAction?.Invoke(services); return services; } -} \ No newline at end of file +} diff --git a/Fengling.Platform.Infrastructure/GatewayExtensions.cs b/Fengling.Platform.Infrastructure/GatewayExtensions.cs index 4cea94b..91313bc 100644 --- a/Fengling.Platform.Infrastructure/GatewayExtensions.cs +++ b/Fengling.Platform.Infrastructure/GatewayExtensions.cs @@ -18,11 +18,11 @@ public static class GatewayExtensions { // 注册 Gateway stores services.AddScoped>(); - services.AddScoped>(); + services.AddScoped>(); // 注册 Gateway managers services.AddScoped(); return services; } -} \ No newline at end of file +} diff --git a/Fengling.Platform.Infrastructure/IClusterStore.cs b/Fengling.Platform.Infrastructure/IClusterStore.cs new file mode 100644 index 0000000..f5fff4a --- /dev/null +++ b/Fengling.Platform.Infrastructure/IClusterStore.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Identity; + +using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; + +namespace Fengling.Platform.Infrastructure; + +/// +/// 集群存储接口 +/// +public interface IClusterStore +{ + Task FindByIdAsync(string? id, CancellationToken cancellationToken = default); + Task FindByClusterIdAsync(string clusterId, CancellationToken cancellationToken = default); + Task> GetAllAsync(CancellationToken cancellationToken = default); + Task> GetPagedAsync(int page, int pageSize, string? clusterId = null, + string? name = null, int? status = null, CancellationToken cancellationToken = default); + Task GetCountAsync(string? clusterId = null, string? name = null, + int? status = null, CancellationToken cancellationToken = default); + Task CreateAsync(GwCluster cluster, CancellationToken cancellationToken = default); + Task UpdateAsync(GwCluster cluster, CancellationToken cancellationToken = default); + Task DeleteAsync(GwCluster cluster, CancellationToken cancellationToken = default); + + // Destination management + Task AddDestinationAsync(string clusterId, GwDestination destination, CancellationToken cancellationToken = default); + Task UpdateDestinationAsync(string clusterId, string destinationId, GwDestination destination, CancellationToken cancellationToken = default); + Task RemoveDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken = default); +} diff --git a/Fengling.Platform.Infrastructure/IInstanceStore.cs b/Fengling.Platform.Infrastructure/IInstanceStore.cs deleted file mode 100644 index 7e145aa..0000000 --- a/Fengling.Platform.Infrastructure/IInstanceStore.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Microsoft.AspNetCore.Identity; - -using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; - -namespace Fengling.Platform.Infrastructure; - -/// -/// 服务实例存储接口 -/// -public interface IInstanceStore -{ - Task FindByIdAsync(string? id, CancellationToken cancellationToken = default); - Task FindByClusterIdAsync(string clusterId, CancellationToken cancellationToken = default); - Task FindByDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken = default); - Task> GetAllAsync(CancellationToken cancellationToken = default); - Task> GetPagedAsync(int page, int pageSize, string? clusterId = null, - InstanceHealth? health = null, InstanceStatus? status = null, CancellationToken cancellationToken = default); - Task GetCountAsync(string? clusterId = null, - InstanceHealth? health = null, InstanceStatus? status = null, CancellationToken cancellationToken = default); - Task CreateAsync(GwServiceInstance instance, CancellationToken cancellationToken = default); - Task UpdateAsync(GwServiceInstance instance, CancellationToken cancellationToken = default); - Task DeleteAsync(GwServiceInstance instance, CancellationToken cancellationToken = default); -} diff --git a/Fengling.Platform.Infrastructure/InstanceStore.cs b/Fengling.Platform.Infrastructure/InstanceStore.cs deleted file mode 100644 index a48c7b5..0000000 --- a/Fengling.Platform.Infrastructure/InstanceStore.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; - -namespace Fengling.Platform.Infrastructure; - -/// -/// 服务实例存储实现 -/// -public class InstanceStore : IInstanceStore - where TContext : PlatformDbContext -{ - private readonly TContext _context; - private readonly DbSet _instances; - - public InstanceStore(TContext context) - { - _context = context; - _instances = context.GwServiceInstances; - } - - public void Dispose() { } - - public virtual Task FindByIdAsync(string? id, CancellationToken cancellationToken = default) - { - if (id == null) return Task.FromResult(null); - return _instances.FirstOrDefaultAsync(i => i.Id == id, cancellationToken); - } - - public virtual Task FindByClusterIdAsync(string clusterId, CancellationToken cancellationToken = default) - { - return _instances.FirstOrDefaultAsync(i => i.ClusterId == clusterId && !i.IsDeleted, cancellationToken); - } - - public virtual Task FindByDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken = default) - { - return _instances.FirstOrDefaultAsync(i => i.ClusterId == clusterId && i.DestinationId == destinationId && !i.IsDeleted, cancellationToken); - } - - public virtual async Task> GetAllAsync(CancellationToken cancellationToken = default) - { - return await _instances.Where(i => !i.IsDeleted).ToListAsync(cancellationToken); - } - - public virtual async Task> GetPagedAsync(int page, int pageSize, string? clusterId = null, - InstanceHealth? health = null, InstanceStatus? status = null, CancellationToken cancellationToken = default) - { - var query = _instances.AsQueryable(); - - if (!string.IsNullOrEmpty(clusterId)) - query = query.Where(i => i.ClusterId.Contains(clusterId)); - - if (health.HasValue) - query = query.Where(i => i.Health == (int)health.Value); - - if (status.HasValue) - query = query.Where(i => i.Status == (int)status.Value); - - return await query - .Where(i => !i.IsDeleted) - .OrderByDescending(i => i.CreatedTime) - .Skip((page - 1) * pageSize) - .Take(pageSize) - .ToListAsync(cancellationToken); - } - - public virtual async Task GetCountAsync(string? clusterId = null, - InstanceHealth? health = null, InstanceStatus? status = null, CancellationToken cancellationToken = default) - { - var query = _instances.AsQueryable(); - - if (!string.IsNullOrEmpty(clusterId)) - query = query.Where(i => i.ClusterId.Contains(clusterId)); - - if (health.HasValue) - query = query.Where(i => i.Health == (int)health.Value); - - if (status.HasValue) - query = query.Where(i => i.Status == (int)status.Value); - - return await query.Where(i => !i.IsDeleted).CountAsync(cancellationToken); - } - - public virtual async Task CreateAsync(GwServiceInstance instance, CancellationToken cancellationToken = default) - { - _instances.Add(instance); - await _context.SaveChangesAsync(cancellationToken); - return IdentityResult.Success; - } - - public virtual async Task UpdateAsync(GwServiceInstance instance, CancellationToken cancellationToken = default) - { - instance.UpdatedTime = DateTime.UtcNow; - _instances.Update(instance); - await _context.SaveChangesAsync(cancellationToken); - return IdentityResult.Success; - } - - public virtual async Task DeleteAsync(GwServiceInstance instance, CancellationToken cancellationToken = default) - { - // 软删除 - instance.IsDeleted = true; - instance.UpdatedTime = DateTime.UtcNow; - _instances.Update(instance); - await _context.SaveChangesAsync(cancellationToken); - return IdentityResult.Success; - } -} diff --git a/Fengling.Platform.Infrastructure/PlatformDbContext.cs b/Fengling.Platform.Infrastructure/PlatformDbContext.cs index 584346f..bdd6bfa 100644 --- a/Fengling.Platform.Infrastructure/PlatformDbContext.cs +++ b/Fengling.Platform.Infrastructure/PlatformDbContext.cs @@ -18,9 +18,8 @@ public class PlatformDbContext(DbContextOptions options) public DbSet AuditLogs => Set(); // Gateway 实体 - public DbSet GwTenants => Set(); public DbSet GwTenantRoutes => Set(); - public DbSet GwServiceInstances => Set(); + public DbSet GwClusters => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { @@ -86,14 +85,6 @@ public class PlatformDbContext(DbContextOptions options) }); // Gateway 实体配置 - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Id); - entity.Property(e => e.TenantCode).HasMaxLength(50).IsRequired(); - entity.Property(e => e.TenantName).HasMaxLength(100).IsRequired(); - entity.HasIndex(e => e.TenantCode).IsUnique(); - }); - modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); @@ -107,14 +98,41 @@ public class PlatformDbContext(DbContextOptions options) entity.HasIndex(e => new { e.ServiceName, e.IsGlobal, e.Status }); }); - modelBuilder.Entity(entity => + // GwCluster 聚合根配置 - 使用 Owned 类型 + modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.ClusterId).HasMaxLength(100).IsRequired(); - entity.Property(e => e.DestinationId).HasMaxLength(100).IsRequired(); - entity.Property(e => e.Address).HasMaxLength(200).IsRequired(); - entity.HasIndex(e => new { e.ClusterId, e.DestinationId }).IsUnique(); - entity.HasIndex(e => e.Health); + entity.Property(e => e.Name).HasMaxLength(100).IsRequired(); + entity.Property(e => e.Description).HasMaxLength(500); + entity.Property(e => e.LoadBalancingPolicy).HasMaxLength(50); + entity.HasIndex(e => e.ClusterId).IsUnique(); + entity.HasIndex(e => e.Name); + entity.HasIndex(e => e.Status); + + // 配置内嵌的目标端点列表 - 使用 OwnedMany + entity.OwnsMany(e => e.Destinations, owned => + { + owned.WithOwner().HasForeignKey("ClusterId"); + owned.Property("ClusterId").HasMaxLength(100); + owned.Property(d => d.DestinationId).HasMaxLength(100).IsRequired(); + owned.Property(d => d.Address).HasMaxLength(200).IsRequired(); + owned.Property(d => d.Health).HasMaxLength(200); + owned.HasIndex("ClusterId", "DestinationId"); + }); + + // 配置内嵌健康检查配置 + entity.OwnsOne(e => e.HealthCheck, owned => + { + owned.Property(h => h.Path).HasMaxLength(200); + }); + + // 配置内嵌会话亲和配置 + entity.OwnsOne(e => e.SessionAffinity, owned => + { + owned.Property(s => s.Policy).HasMaxLength(50); + owned.Property(s => s.AffinityKeyName).HasMaxLength(50); + }); }); modelBuilder.ApplyConfigurationsFromAssembly(typeof(PlatformDbContext).Assembly);