using System.Security.Claims; using System.Text; using System.Text.Json; using Fengling.Platform.Domain.AggregatesModel.GatewayAggregate; using Fengling.Platform.Domain.AggregatesModel.TenantAggregate; using Microsoft.IdentityModel.Tokens; using YarpGateway.Data; using YarpGateway.Models; namespace YarpGateway.Tests.Integration; /// /// 集成测试数据准备 /// public static class TestData { #region 租户数据 public static async Task SeedTenantsAsync(GatewayDbContext dbContext) { var tenants = new List { new() { TenantCode = "tenant1", Name = "Tenant One", Status = TenantStatus.Active, CreatedAt = DateTime.UtcNow }, new() { TenantCode = "tenant2", Name = "Tenant Two", Status = TenantStatus.Active, CreatedAt = DateTime.UtcNow }, new() { TenantCode = "default", Name = "Default Tenant", Status = TenantStatus.Active, CreatedAt = DateTime.UtcNow } }; foreach (var tenant in tenants) { if (!dbContext.Tenants.Any(t => t.TenantCode == tenant.TenantCode)) { dbContext.Tenants.Add(tenant); } } await dbContext.SaveChangesAsync(); } #endregion #region 路由数据 public static async Task SeedRoutesAsync(GatewayDbContext dbContext) { var routes = new List { // 全局路由 - member 服务 new() { Id = Guid.CreateVersion7().ToString("N"), TenantCode = "", ServiceName = "member", ClusterId = "member-cluster", Match = new GwRouteMatch { Path = "/api/member/**" }, Priority = 1, Status = 1, IsGlobal = true, IsDeleted = false, CreatedTime = DateTime.UtcNow }, // 全局路由 - order 服务 new() { Id = Guid.CreateVersion7().ToString("N"), TenantCode = "", ServiceName = "order", ClusterId = "order-cluster", Match = new GwRouteMatch { Path = "/api/order/**" }, Priority = 1, Status = 1, IsGlobal = true, IsDeleted = false, CreatedTime = DateTime.UtcNow }, // 租户专属路由 - tenant1 的 member 服务 new() { Id = Guid.CreateVersion7().ToString("N"), TenantCode = "tenant1", ServiceName = "member", ClusterId = "tenant1-member-cluster", Match = new GwRouteMatch { Path = "/api/member/**" }, Priority = 0, // 更高优先级 Status = 1, IsGlobal = false, IsDeleted = false, CreatedTime = DateTime.UtcNow } }; foreach (var route in routes) { if (!dbContext.GwTenantRoutes.Any(r => r.Id == route.Id)) { dbContext.GwTenantRoutes.Add(route); } } await dbContext.SaveChangesAsync(); } #endregion #region 集群和目标数据 public static async Task SeedClustersAsync(GatewayDbContext dbContext) { var clusters = new List { // member-cluster - 默认集群 new() { Id = Guid.CreateVersion7().ToString("N"), ClusterId = "member-cluster", Name = "Member Service Cluster", LoadBalancingPolicy = GwLoadBalancingPolicy.RoundRobin, Status = 1, CreatedTime = DateTime.UtcNow, Destinations = new List { new() { DestinationId = "default", Address = "http://default-member:8080", Weight = 1, Status = 1, TenantCode = null // 默认目标 } } }, // tenant1-member-cluster - tenant1 专属集群 new() { Id = Guid.CreateVersion7().ToString("N"), ClusterId = "tenant1-member-cluster", Name = "Tenant1 Member Service Cluster", LoadBalancingPolicy = GwLoadBalancingPolicy.RoundRobin, Status = 1, CreatedTime = DateTime.UtcNow, Destinations = new List { new() { DestinationId = "tenant1-dest", Address = "http://tenant1-member:8080", Weight = 1, Status = 1, TenantCode = "tenant1" // 租户专属目标 } } }, // order-cluster - 默认集群 new() { Id = Guid.CreateVersion7().ToString("N"), ClusterId = "order-cluster", Name = "Order Service Cluster", LoadBalancingPolicy = GwLoadBalancingPolicy.RoundRobin, Status = 1, CreatedTime = DateTime.UtcNow, Destinations = new List { new() { DestinationId = "default", Address = "http://default-order:8080", Weight = 1, Status = 1, TenantCode = null // 默认目标 } } } }; foreach (var cluster in clusters) { if (!dbContext.GwClusters.Any(c => c.ClusterId == cluster.ClusterId)) { dbContext.GwClusters.Add(cluster); } } await dbContext.SaveChangesAsync(); } #endregion #region K8s Service 模拟数据 /// /// 创建模拟的 K8s Service 发现数据 /// public static GwPendingServiceDiscovery CreateK8sService( string serviceName, string @namespace, Dictionary labels, string clusterIp = "10.96.123.45", int podCount = 1) { return new GwPendingServiceDiscovery { K8sServiceName = serviceName, K8sNamespace = @namespace, K8sClusterIP = clusterIp, Labels = JsonSerializer.Serialize(labels), DiscoveredPorts = "[8080]", PodCount = podCount, Status = (int)PendingConfigStatus.Pending, DiscoveredAt = DateTime.UtcNow, Version = 1, IsDeleted = false }; } /// /// 创建带路由标签的 K8s Service /// public static GwPendingServiceDiscovery CreateRoutedK8sService( string serviceName, string prefix, string clusterName, string destination = "default", string @namespace = "default") { return CreateK8sService( serviceName, @namespace, new Dictionary { ["app-router-name"] = serviceName, ["app-router-prefix"] = prefix, ["app-cluster-name"] = clusterName, ["app-cluster-destination"] = destination }); } #endregion #region JWT Token 生成 private static readonly SymmetricSecurityKey TestSigningKey = new( Encoding.UTF8.GetBytes("test-signing-key-that-is-long-enough-for-hs256-algorithm")); /// /// 生成测试用 JWT Token /// public static string GenerateJwtToken( string userId, string tenantCode, string[]? roles = null, DateTime? expires = null) { var claims = new List { new(ClaimTypes.NameIdentifier, userId), new("sub", userId), new("tenant", tenantCode), new("tenant_id", tenantCode), new(ClaimTypes.Name, $"test-user-{userId}"), new("name", $"Test User {userId}") }; if (roles != null) { foreach (var role in roles) { claims.Add(new Claim(ClaimTypes.Role, role)); claims.Add(new Claim("role", role)); } } var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(claims), Expires = expires ?? DateTime.UtcNow.AddHours(1), Issuer = "test-issuer", Audience = "test-audience", SigningCredentials = new SigningCredentials(TestSigningKey, SecurityAlgorithms.HmacSha256) }; var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } /// /// 获取 tenant1 的测试 Token /// public static string GetTenant1Token(string userId = "user1") { return GenerateJwtToken(userId, "tenant1", new[] { "user" }); } /// /// 获取 tenant2 的测试 Token /// public static string GetTenant2Token(string userId = "user2") { return GenerateJwtToken(userId, "tenant2", new[] { "user" }); } /// /// 获取默认租户的测试 Token /// public static string GetDefaultToken(string userId = "default-user") { return GenerateJwtToken(userId, "default", new[] { "user" }); } #endregion } #region 支持模型 /// /// 待确认配置状态 /// public enum PendingConfigStatus { Pending = 0, Confirmed = 1, Rejected = 2 } /// /// PendingServiceDiscovery 扩展方法 /// public static class PendingServiceDiscoveryExtensions { /// /// 获取解析后的标签 /// public static Dictionary GetParsedLabels(this GwPendingServiceDiscovery discovery) { return JsonSerializer.Deserialize>(discovery.Labels) ?? new(); } /// /// 获取路由名称 /// public static string GetRouteName(this GwPendingServiceDiscovery discovery) { var labels = GetParsedLabels(discovery); return labels.GetValueOrDefault("app-router-name", discovery.K8sServiceName); } /// /// 获取路由前缀 /// public static string GetRoutePrefix(this GwPendingServiceDiscovery discovery) { var labels = GetParsedLabels(discovery); return labels.GetValueOrDefault("app-router-prefix", $"/api/{discovery.K8sServiceName}"); } /// /// 获取集群名称 /// public static string GetClusterName(this GwPendingServiceDiscovery discovery) { var labels = GetParsedLabels(discovery); return labels.GetValueOrDefault("app-cluster-name", discovery.K8sServiceName); } /// /// 获取目标 ID /// public static string GetDestinationId(this GwPendingServiceDiscovery discovery) { var labels = GetParsedLabels(discovery); return labels.GetValueOrDefault("app-cluster-destination", "default"); } /// /// 构建服务地址 /// public static string BuildAddress(this GwPendingServiceDiscovery discovery) { var host = discovery.K8sClusterIP ?? $"{discovery.K8sServiceName}.{discovery.K8sNamespace}"; var ports = JsonSerializer.Deserialize(discovery.DiscoveredPorts) ?? new[] { 8080 }; var port = ports.FirstOrDefault(8080); return $"http://{host}:{port}"; } } #endregion