refactor(gateway): 使用值对象替代字符串类型属性

- GwRouteMatch: 路由匹配配置值对象(Path, Methods, Hosts, Headers, QueryParameters)
- GwRouteHeader: Header 匹配规则值对象
- GwRouteQueryParameter: 查询参数匹配规则值对象
- GwLoadBalancingPolicy: 负载均衡策略枚举
- GwTransform: 请求/响应转换规则值对象
- EF Core 使用 ToJson() 将值对象映射为 JSON 列
This commit is contained in:
movingsam 2026-03-03 20:16:12 +08:00
parent 0841d81318
commit 033fcc9e9b
6 changed files with 278 additions and 63 deletions

View File

@ -25,20 +25,20 @@ public class GwCluster
/// <summary>
/// 目标端点列表(内嵌)
/// </summary>
public List<GwDestination> Destinations { get; set; } = new();
public List<GwDestination> Destinations { get; set; } = [];
/// <summary>
/// 负载均衡策略
/// </summary>
public string LoadBalancingPolicy { get; set; } = "RoundRobin";
public GwLoadBalancingPolicy LoadBalancingPolicy { get; set; } = GwLoadBalancingPolicy.RoundRobin;
/// <summary>
/// 健康检查配置
/// 健康检查配置JSON 列存储)
/// </summary>
public GwHealthCheckConfig? HealthCheck { get; set; }
/// <summary>
/// 会话亲和配置
/// 会话亲和配置JSON 列存储)
/// </summary>
public GwSessionAffinityConfig? SessionAffinity { get; set; }

View File

@ -0,0 +1,14 @@
namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
/// <summary>
/// 负载均衡策略
/// </summary>
public enum GwLoadBalancingPolicy
{
RoundRobin = 0,
Random = 1,
PowerOfTwoChoices = 2,
LeastRequests = 3,
First = 4,
WeightedRoundRobin = 5
}

View File

@ -0,0 +1,108 @@
namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
/// <summary>
/// 路由匹配配置(值对象)
/// 对应 YARP 的 RouteMatch以 JSON 存储在数据库中
/// </summary>
public class GwRouteMatch
{
/// <summary>
/// 路径匹配模式
/// </summary>
public string Path { get; set; } = string.Empty;
/// <summary>
/// HTTP 方法列表(如 ["GET", "POST"]
/// </summary>
public List<string>? Methods { get; set; }
/// <summary>
/// Host 匹配列表(如 ["api.example.com", "*.example.com"]
/// </summary>
public List<string>? Hosts { get; set; }
/// <summary>
/// Header 匹配规则
/// </summary>
public List<GwRouteHeader>? Headers { get; set; }
/// <summary>
/// 查询参数匹配规则
/// </summary>
public List<GwRouteQueryParameter>? QueryParameters { get; set; }
}
/// <summary>
/// Header 匹配规则(值对象)
/// </summary>
public class GwRouteHeader
{
/// <summary>
/// Header 名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 匹配值列表
/// </summary>
public List<string> Values { get; set; } = [];
/// <summary>
/// 匹配模式
/// </summary>
public GwHeaderMatchMode Mode { get; set; } = GwHeaderMatchMode.ExactHeader;
/// <summary>
/// 是否区分大小写
/// </summary>
public bool IsCaseSensitive { get; set; } = false;
}
/// <summary>
/// 查询参数匹配规则(值对象)
/// </summary>
public class GwRouteQueryParameter
{
/// <summary>
/// 参数名称
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 匹配值列表
/// </summary>
public List<string> Values { get; set; } = [];
/// <summary>
/// 匹配模式
/// </summary>
public GwQueryParameterMatchMode Mode { get; set; } = GwQueryParameterMatchMode.Exact;
/// <summary>
/// 是否区分大小写
/// </summary>
public bool IsCaseSensitive { get; set; } = false;
}
/// <summary>
/// Header 匹配模式
/// </summary>
public enum GwHeaderMatchMode
{
ExactHeader = 0,
Prefix = 1,
Contains = 2,
NotContains = 3,
Exists = 4
}
/// <summary>
/// 查询参数匹配模式
/// </summary>
public enum GwQueryParameterMatchMode
{
Exact = 0,
Contains = 1,
Prefix = 2,
Exists = 3
}

View File

@ -23,15 +23,45 @@ public class GwTenantRoute
public string ClusterId { get; set; } = string.Empty;
/// <summary>
/// 路径匹配模式
/// 路由匹配配置JSON 列存储)
/// </summary>
public string PathPattern { get; set; } = string.Empty;
public GwRouteMatch Match { get; set; } = new();
/// <summary>
/// 优先级
/// 优先级(对应 YARP Order数值越小优先级越高
/// </summary>
public int Priority { get; set; } = 0;
/// <summary>
/// 路由级别负载均衡策略覆盖(可选,默认使用集群策略)
/// </summary>
public GwLoadBalancingPolicy? LoadBalancingPolicy { get; set; }
/// <summary>
/// 授权策略名称
/// </summary>
public string? AuthorizationPolicy { get; set; }
/// <summary>
/// CORS 策略名称
/// </summary>
public string? CorsPolicy { get; set; }
/// <summary>
/// 限流策略名称
/// </summary>
public string? RateLimiterPolicy { get; set; }
/// <summary>
/// 请求/响应转换规则JSON 列存储)
/// </summary>
public List<GwTransform>? Transforms { get; set; }
/// <summary>
/// 请求超时时间(秒)
/// </summary>
public int? TimeoutSeconds { get; set; }
/// <summary>
/// 状态
/// </summary>
@ -71,45 +101,4 @@ public class GwTenantRoute
/// 版本号,用于乐观并发
/// </summary>
public int Version { get; set; } = 0;
// ===== 路由匹配能力 =====
/// <summary>
/// HTTP 方法匹配(如 "GET,POST"
/// </summary>
public string? Methods { get; set; }
/// <summary>
/// Host 头匹配(如 "api.example.com"
/// </summary>
public string? Hosts { get; set; }
/// <summary>
/// Header 匹配规则JSON 格式)
/// </summary>
public string? Headers { get; set; }
// ===== 策略配置 =====
/// <summary>
/// 路由级别负载均衡策略覆盖
/// </summary>
public string? LoadBalancingPolicy { get; set; }
/// <summary>
/// 授权策略
/// </summary>
public string? AuthorizationPolicy { get; set; }
/// <summary>
/// CORS 策略
/// </summary>
public string? CorsPolicy { get; set; }
// ===== 请求转换 =====
/// <summary>
/// 请求/响应转换规则JSON 格式)
/// </summary>
public string? Transforms { get; set; }
}

View File

@ -0,0 +1,77 @@
namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
/// <summary>
/// 请求/响应转换规则(值对象)
/// 以 JSON 列存储在数据库中
/// </summary>
public class GwTransform
{
/// <summary>
/// 转换规则键值对
/// 例如: {"RequestHeader": "X-Custom-Header", "Set": "value"}
/// </summary>
public Dictionary<string, string> Rules { get; set; } = new();
}
/// <summary>
/// 常用转换规则工厂
/// </summary>
public static class GwTransforms
{
/// <summary>
/// 设置请求头
/// </summary>
public static GwTransform SetRequestHeader(string headerName, string value)
=> new() { Rules = new Dictionary<string, string>
{
{ "RequestHeader", headerName },
{ "Set", value }
}};
/// <summary>
/// 添加请求头
/// </summary>
public static GwTransform AppendRequestHeader(string headerName, string value)
=> new() { Rules = new Dictionary<string, string>
{
{ "RequestHeader", headerName },
{ "Append", value }
}};
/// <summary>
/// 设置响应头
/// </summary>
public static GwTransform SetResponseHeader(string headerName, string value)
=> new() { Rules = new Dictionary<string, string>
{
{ "ResponseHeader", headerName },
{ "Set", value }
}};
/// <summary>
/// 移除路径前缀
/// </summary>
public static GwTransform PathRemovePrefix(string prefix)
=> new() { Rules = new Dictionary<string, string>
{
{ "PathRemovePrefix", prefix }
}};
/// <summary>
/// 设置路径前缀
/// </summary>
public static GwTransform PathSetPrefix(string prefix)
=> new() { Rules = new Dictionary<string, string>
{
{ "PathPrefix", prefix }
}};
/// <summary>
/// 使用原始 Host 头
/// </summary>
public static GwTransform RequestHeaderOriginalHost()
=> new() { Rules = new Dictionary<string, string>
{
{ "RequestHeaderOriginalHost", "true" }
}};
}

View File

@ -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<ApplicationUser, ApplicationRole, long>(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<GwLoadBalancingPolicy>(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<GwCluster>(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<GwLoadBalancingPolicy>(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,17 +149,16 @@ 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();
});
});