fengling-platform/Fengling.Platform.Infrastructure/PlatformDbContext.cs
Kimi CLI b66b231917
All checks were successful
Publish Platform NuGet Packages / build (push) Successful in 26s
refactor: replace GwTenantRoute with GwRoute, change Id type to string
- Remove GwTenantRoute (old tenant-specific route entity)
- Add GwRoute with string Id (Guid.CreateVersion7)
- Update IRouteManager and IRouteStore interfaces
- Update PlatformDbContext configuration for new schema
- GwRoute is now global, tenant-specific routing moved to GwDestination.TenantCode

BREAKING CHANGE: Database schema change requires table recreation
2026-03-08 15:21:43 +08:00

188 lines
8.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Text.Json;
namespace Fengling.Platform.Infrastructure;
public class PlatformDbContext(DbContextOptions options)
: IdentityDbContext<ApplicationUser, ApplicationRole, long>(options)
{
public DbSet<Tenant> Tenants => Set<Tenant>();
public DbSet<AccessLog> AccessLogs => Set<AccessLog>();
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
// Gateway 实体
public DbSet<GwRoute> GwRoutes => Set<GwRoute>();
public DbSet<GwCluster> GwClusters => Set<GwCluster>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (modelBuilder is null)
{
throw new ArgumentNullException(nameof(modelBuilder));
}
// 忽略这些类型,让它们只作为 JSON 值对象使用
modelBuilder.Ignore<GwRouteMatch>();
modelBuilder.Ignore<GwRouteHeader>();
modelBuilder.Ignore<GwRouteQueryParameter>();
modelBuilder.Ignore<GwTransform>();
modelBuilder.Entity<ApplicationUser>(entity =>
{
entity.Property(e => e.PhoneNumber).HasMaxLength(20);
entity.HasIndex(e => e.PhoneNumber).IsUnique();
entity.OwnsOne(e => e.TenantInfo, navigationBuilder =>
{
navigationBuilder.Property(t => t.TenantCode).HasColumnName("TenantCode");
navigationBuilder.Property(t => t.TenantId).HasColumnName("TenantId");
navigationBuilder.Property(t => t.TenantName).HasColumnName("TenantName");
navigationBuilder.WithOwner();
});
});
modelBuilder.Entity<ApplicationRole>(entity =>
{
entity.Property(e => e.Description).HasMaxLength(200);
});
modelBuilder.Entity<AccessLog>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.CreatedAt);
entity.HasIndex(e => e.UserName);
entity.HasIndex(e => e.TenantId);
entity.HasIndex(e => e.Action);
entity.HasIndex(e => e.Status);
entity.Property(e => e.UserName).HasMaxLength(50);
entity.Property(e => e.TenantId).HasMaxLength(50);
entity.Property(e => e.Action).HasMaxLength(20);
entity.Property(e => e.Resource).HasMaxLength(200);
entity.Property(e => e.Method).HasMaxLength(10);
entity.Property(e => e.IpAddress).HasMaxLength(50);
entity.Property(e => e.UserAgent).HasMaxLength(500);
entity.Property(e => e.Status).HasMaxLength(20);
});
modelBuilder.Entity<AuditLog>(entity =>
{
entity.HasKey(e => e.Id);
entity.HasIndex(e => e.CreatedAt);
entity.HasIndex(e => e.Operator);
entity.HasIndex(e => e.TenantId);
entity.HasIndex(e => e.Operation);
entity.HasIndex(e => e.Action);
entity.Property(e => e.Operator).HasMaxLength(50);
entity.Property(e => e.TenantId).HasMaxLength(50);
entity.Property(e => e.Operation).HasMaxLength(20);
entity.Property(e => e.Action).HasMaxLength(20);
entity.Property(e => e.TargetType).HasMaxLength(50);
entity.Property(e => e.TargetName).HasMaxLength(100);
entity.Property(e => e.IpAddress).HasMaxLength(50);
entity.Property(e => e.Description).HasMaxLength(500);
entity.Property(e => e.Status).HasMaxLength(20);
});
// Gateway 实体配置
modelBuilder.Entity<GwRoute>(entity =>
{
entity.ToTable("GwRoutes");
entity.HasKey(e => e.Id);
entity.Property(e => e.ServiceName).HasMaxLength(100).IsRequired();
entity.Property(e => e.ClusterId).HasMaxLength(100).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 列 - 使用值转换器
var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };
entity.Property(e => e.Match)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<GwRouteMatch>(v, jsonOptions)!,
new ValueComparer<GwRouteMatch>(
(c1, c2) => JsonSerializer.Serialize(c1, jsonOptions) == JsonSerializer.Serialize(c2, jsonOptions),
c => c == null ? 0 : JsonSerializer.Serialize(c, jsonOptions).GetHashCode(),
c => JsonSerializer.Deserialize<GwRouteMatch>(JsonSerializer.Serialize(c, jsonOptions), jsonOptions)!))
.HasColumnType("jsonb");
// 转换规则映射为 JSON 列 - 使用值转换器
entity.Property(e => e.Transforms)
.HasConversion(
v => JsonSerializer.Serialize(v, jsonOptions),
v => JsonSerializer.Deserialize<List<GwTransform>>(v, jsonOptions),
new ValueComparer<List<GwTransform>>(
(c1, c2) => JsonSerializer.Serialize(c1, jsonOptions) == JsonSerializer.Serialize(c2, jsonOptions),
c => c == null ? 0 : JsonSerializer.Serialize(c, jsonOptions).GetHashCode(),
c => JsonSerializer.Deserialize<List<GwTransform>>(JsonSerializer.Serialize(c, jsonOptions), jsonOptions)!))
.HasColumnType("jsonb");
entity.HasIndex(e => e.ServiceName);
entity.HasIndex(e => e.ClusterId);
entity.HasIndex(e => new { e.ServiceName, e.Status });
});
// GwCluster 聚合根配置
modelBuilder.Entity<GwCluster>(entity =>
{
entity.ToTable("ServiceInstances");
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)
.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);
// 配置内嵌的目标端点列表
entity.OwnsMany(e => e.Destinations, owned =>
{
owned.WithOwner().HasForeignKey("ClusterId");
owned.Property<string>("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");
});
// 配置内嵌健康检查配置JSON 列)
entity.OwnsOne(e => e.HealthCheck, owned =>
{
owned.ToJson();
});
// 配置内嵌会话亲和配置JSON 列)
entity.OwnsOne(e => e.SessionAffinity, owned =>
{
owned.ToJson();
});
});
modelBuilder.ApplyConfigurationsFromAssembly(typeof(PlatformDbContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}