diff --git a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs index 9139bd6..f268df1 100644 --- a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs +++ b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs @@ -25,20 +25,20 @@ public class GwCluster /// /// 目标端点列表(内嵌) /// - public List Destinations { get; set; } = new(); + public List Destinations { get; set; } = []; /// /// 负载均衡策略 /// - public string LoadBalancingPolicy { get; set; } = "RoundRobin"; + public GwLoadBalancingPolicy LoadBalancingPolicy { get; set; } = GwLoadBalancingPolicy.RoundRobin; /// - /// 健康检查配置 + /// 健康检查配置(JSON 列存储) /// public GwHealthCheckConfig? HealthCheck { get; set; } /// - /// 会话亲和配置 + /// 会话亲和配置(JSON 列存储) /// public GwSessionAffinityConfig? SessionAffinity { get; set; } @@ -76,4 +76,4 @@ public class GwCluster /// 版本号,用于乐观并发 /// public int Version { get; set; } = 0; -} +} \ No newline at end of file diff --git a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwLoadBalancingPolicy.cs b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwLoadBalancingPolicy.cs new file mode 100644 index 0000000..05ca97d --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwLoadBalancingPolicy.cs @@ -0,0 +1,14 @@ +namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; + +/// +/// 负载均衡策略 +/// +public enum GwLoadBalancingPolicy +{ + RoundRobin = 0, + Random = 1, + PowerOfTwoChoices = 2, + LeastRequests = 3, + First = 4, + WeightedRoundRobin = 5 +} \ No newline at end of file diff --git a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwRouteMatch.cs b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwRouteMatch.cs new file mode 100644 index 0000000..69a5704 --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwRouteMatch.cs @@ -0,0 +1,108 @@ +namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; + +/// +/// 路由匹配配置(值对象) +/// 对应 YARP 的 RouteMatch,以 JSON 存储在数据库中 +/// +public class GwRouteMatch +{ + /// + /// 路径匹配模式 + /// + public string Path { get; set; } = string.Empty; + + /// + /// HTTP 方法列表(如 ["GET", "POST"]) + /// + public List? Methods { get; set; } + + /// + /// Host 匹配列表(如 ["api.example.com", "*.example.com"]) + /// + public List? Hosts { get; set; } + + /// + /// Header 匹配规则 + /// + public List? Headers { get; set; } + + /// + /// 查询参数匹配规则 + /// + public List? QueryParameters { get; set; } +} + +/// +/// Header 匹配规则(值对象) +/// +public class GwRouteHeader +{ + /// + /// Header 名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 匹配值列表 + /// + public List Values { get; set; } = []; + + /// + /// 匹配模式 + /// + public GwHeaderMatchMode Mode { get; set; } = GwHeaderMatchMode.ExactHeader; + + /// + /// 是否区分大小写 + /// + public bool IsCaseSensitive { get; set; } = false; +} + +/// +/// 查询参数匹配规则(值对象) +/// +public class GwRouteQueryParameter +{ + /// + /// 参数名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 匹配值列表 + /// + public List Values { get; set; } = []; + + /// + /// 匹配模式 + /// + public GwQueryParameterMatchMode Mode { get; set; } = GwQueryParameterMatchMode.Exact; + + /// + /// 是否区分大小写 + /// + public bool IsCaseSensitive { get; set; } = false; +} + +/// +/// Header 匹配模式 +/// +public enum GwHeaderMatchMode +{ + ExactHeader = 0, + Prefix = 1, + Contains = 2, + NotContains = 3, + Exists = 4 +} + +/// +/// 查询参数匹配模式 +/// +public enum GwQueryParameterMatchMode +{ + Exact = 0, + Contains = 1, + Prefix = 2, + Exists = 3 +} \ No newline at end of file diff --git a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs index 660a7ea..467f09d 100644 --- a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs +++ b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs @@ -23,15 +23,45 @@ public class GwTenantRoute public string ClusterId { get; set; } = string.Empty; /// - /// 路径匹配模式 + /// 路由匹配配置(JSON 列存储) /// - public string PathPattern { get; set; } = string.Empty; + public GwRouteMatch Match { get; set; } = new(); /// - /// 优先级 + /// 优先级(对应 YARP Order,数值越小优先级越高) /// public int Priority { get; set; } = 0; + /// + /// 路由级别负载均衡策略覆盖(可选,默认使用集群策略) + /// + public GwLoadBalancingPolicy? LoadBalancingPolicy { get; set; } + + /// + /// 授权策略名称 + /// + public string? AuthorizationPolicy { get; set; } + + /// + /// CORS 策略名称 + /// + public string? CorsPolicy { get; set; } + + /// + /// 限流策略名称 + /// + public string? RateLimiterPolicy { get; set; } + + /// + /// 请求/响应转换规则(JSON 列存储) + /// + public List? Transforms { get; set; } + + /// + /// 请求超时时间(秒) + /// + public int? TimeoutSeconds { get; set; } + /// /// 状态 /// @@ -71,45 +101,4 @@ public class GwTenantRoute /// 版本号,用于乐观并发 /// public int Version { get; set; } = 0; - - // ===== 路由匹配能力 ===== - - /// - /// HTTP 方法匹配(如 "GET,POST") - /// - public string? Methods { get; set; } - - /// - /// Host 头匹配(如 "api.example.com") - /// - public string? Hosts { get; set; } - - /// - /// Header 匹配规则(JSON 格式) - /// - public string? Headers { get; set; } - - // ===== 策略配置 ===== - - /// - /// 路由级别负载均衡策略覆盖 - /// - public string? LoadBalancingPolicy { get; set; } - - /// - /// 授权策略 - /// - public string? AuthorizationPolicy { get; set; } - - /// - /// CORS 策略 - /// - public string? CorsPolicy { get; set; } - - // ===== 请求转换 ===== - - /// - /// 请求/响应转换规则(JSON 格式) - /// - public string? Transforms { get; set; } -} +} \ No newline at end of file diff --git a/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTransform.cs b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTransform.cs new file mode 100644 index 0000000..4922fd7 --- /dev/null +++ b/Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTransform.cs @@ -0,0 +1,77 @@ +namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; + +/// +/// 请求/响应转换规则(值对象) +/// 以 JSON 列存储在数据库中 +/// +public class GwTransform +{ + /// + /// 转换规则键值对 + /// 例如: {"RequestHeader": "X-Custom-Header", "Set": "value"} + /// + public Dictionary Rules { get; set; } = new(); +} + +/// +/// 常用转换规则工厂 +/// +public static class GwTransforms +{ + /// + /// 设置请求头 + /// + public static GwTransform SetRequestHeader(string headerName, string value) + => new() { Rules = new Dictionary + { + { "RequestHeader", headerName }, + { "Set", value } + }}; + + /// + /// 添加请求头 + /// + public static GwTransform AppendRequestHeader(string headerName, string value) + => new() { Rules = new Dictionary + { + { "RequestHeader", headerName }, + { "Append", value } + }}; + + /// + /// 设置响应头 + /// + public static GwTransform SetResponseHeader(string headerName, string value) + => new() { Rules = new Dictionary + { + { "ResponseHeader", headerName }, + { "Set", value } + }}; + + /// + /// 移除路径前缀 + /// + public static GwTransform PathRemovePrefix(string prefix) + => new() { Rules = new Dictionary + { + { "PathRemovePrefix", prefix } + }}; + + /// + /// 设置路径前缀 + /// + public static GwTransform PathSetPrefix(string prefix) + => new() { Rules = new Dictionary + { + { "PathPrefix", prefix } + }}; + + /// + /// 使用原始 Host 头 + /// + public static GwTransform RequestHeaderOriginalHost() + => new() { Rules = new Dictionary + { + { "RequestHeaderOriginalHost", "true" } + }}; +} \ No newline at end of file diff --git a/Fengling.Platform.Infrastructure/PlatformDbContext.cs b/Fengling.Platform.Infrastructure/PlatformDbContext.cs index bdd6bfa..b5b71bc 100644 --- a/Fengling.Platform.Infrastructure/PlatformDbContext.cs +++ b/Fengling.Platform.Infrastructure/PlatformDbContext.cs @@ -1,15 +1,12 @@ using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; - using Fengling.Platform.Domain.AggregatesModel.RoleAggregate; using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using Fengling.Platform.Domain.AggregatesModel.UserAggregate; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; - namespace Fengling.Platform.Infrastructure; - public class PlatformDbContext(DbContextOptions options) : IdentityDbContext(options) { @@ -91,26 +88,57 @@ public class PlatformDbContext(DbContextOptions options) entity.Property(e => e.TenantCode).HasMaxLength(50); entity.Property(e => e.ServiceName).HasMaxLength(100).IsRequired(); entity.Property(e => e.ClusterId).HasMaxLength(100).IsRequired(); - entity.Property(e => e.PathPattern).HasMaxLength(200).IsRequired(); + entity.Property(e => e.AuthorizationPolicy).HasMaxLength(100); + entity.Property(e => e.CorsPolicy).HasMaxLength(100); + entity.Property(e => e.RateLimiterPolicy).HasMaxLength(100); + + // 枚举转换为字符串 + entity.Property(e => e.LoadBalancingPolicy) + .HasConversion( + v => v.HasValue ? v.Value.ToString() : null, + v => v != null ? Enum.Parse(v) : null + ) + .HasMaxLength(50); + + // 值对象映射为 JSON 列 + entity.OwnsOne(e => e.Match, navigationBuilder => + { + navigationBuilder.ToJson(); + }); + + // 转换规则映射为 JSON 列 + entity.OwnsMany(e => e.Transforms, navigationBuilder => + { + navigationBuilder.ToJson(); + }); + entity.HasIndex(e => e.TenantCode); entity.HasIndex(e => e.ServiceName); entity.HasIndex(e => e.ClusterId); entity.HasIndex(e => new { e.ServiceName, e.IsGlobal, e.Status }); }); - // GwCluster 聚合根配置 - 使用 Owned 类型 + // GwCluster 聚合根配置 modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.Property(e => e.ClusterId).HasMaxLength(100).IsRequired(); entity.Property(e => e.Name).HasMaxLength(100).IsRequired(); entity.Property(e => e.Description).HasMaxLength(500); - entity.Property(e => e.LoadBalancingPolicy).HasMaxLength(50); + + // 枚举转换为字符串 + entity.Property(e => e.LoadBalancingPolicy) + .HasConversion( + v => v.ToString(), + v => Enum.Parse(v) + ) + .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"); @@ -121,21 +149,20 @@ public class PlatformDbContext(DbContextOptions options) owned.HasIndex("ClusterId", "DestinationId"); }); - // 配置内嵌健康检查配置 + // 配置内嵌健康检查配置(JSON 列) entity.OwnsOne(e => e.HealthCheck, owned => { - owned.Property(h => h.Path).HasMaxLength(200); + owned.ToJson(); }); - // 配置内嵌会话亲和配置 + // 配置内嵌会话亲和配置(JSON 列) entity.OwnsOne(e => e.SessionAffinity, owned => { - owned.Property(s => s.Policy).HasMaxLength(50); - owned.Property(s => s.AffinityKeyName).HasMaxLength(50); + owned.ToJson(); }); }); modelBuilder.ApplyConfigurationsFromAssembly(typeof(PlatformDbContext).Assembly); base.OnModelCreating(modelBuilder); } -} +} \ No newline at end of file