docs(phase-03): add research and 4 execution plans for gateway restructuring
This commit is contained in:
parent
8dce917105
commit
75b0f9bd35
0
.planning/phases/03-/.gitkeep
Normal file
0
.planning/phases/03-/.gitkeep
Normal file
325
.planning/phases/03-/03-RESEARCH.md
Normal file
325
.planning/phases/03-/03-RESEARCH.md
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
# Phase 3: 调整网关部分的需求 - 技术研究
|
||||||
|
|
||||||
|
**日期:** 2026-03-03
|
||||||
|
**状态:** 研究完成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. EF Core Owned Entity 配置模式
|
||||||
|
|
||||||
|
### GwDestination 内嵌实体
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 在 GwCluster 中配置 Owned Entity
|
||||||
|
modelBuilder.Entity<GwCluster>(entity =>
|
||||||
|
{
|
||||||
|
entity.HasKey(e => e.Id);
|
||||||
|
entity.Property(e => e.ClusterId).HasMaxLength(100).IsRequired();
|
||||||
|
entity.HasIndex(e => e.ClusterId).IsUnique();
|
||||||
|
|
||||||
|
// 配置内嵌的 Destinations 列表
|
||||||
|
entity.OwnsMany(e => e.Destinations, dest =>
|
||||||
|
{
|
||||||
|
dest.WithOwner().HasForeignKey("GwClusterId");
|
||||||
|
dest.Property(d => d.DestinationId).HasMaxLength(100).IsRequired();
|
||||||
|
dest.Property(d => d.Address).HasMaxLength(200).IsRequired();
|
||||||
|
dest.Property(d => d.Health).HasMaxLength(200);
|
||||||
|
dest.Property(d => d.Weight).HasDefaultValue(1);
|
||||||
|
dest.Property(d => d.HealthStatus).HasDefaultValue(1);
|
||||||
|
dest.Property(d => d.Status).HasDefaultValue(1);
|
||||||
|
|
||||||
|
// 复合唯一索引
|
||||||
|
dest.HasIndex(d => new { d.DestinationId }).IsUnique();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### GwHealthCheckConfig 内嵌值对象
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
entity.OwnsOne(e => e.HealthCheck, hc =>
|
||||||
|
{
|
||||||
|
hc.Property(h => h.Enabled).HasDefaultValue(false);
|
||||||
|
hc.Property(h => h.Path).HasMaxLength(100).HasDefaultValue("/health");
|
||||||
|
hc.Property(h => h.IntervalSeconds).HasDefaultValue(30);
|
||||||
|
hc.Property(h => h.TimeoutSeconds).HasDefaultValue(10);
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### GwSessionAffinityConfig 内嵌值对象
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
entity.OwnsOne(e => e.SessionAffinity, sa =>
|
||||||
|
{
|
||||||
|
sa.Property(s => s.Enabled).HasDefaultValue(false);
|
||||||
|
sa.Property(s => s.Policy).HasMaxLength(50).HasDefaultValue("Header");
|
||||||
|
sa.Property(s => s.AffinityKeyName).HasMaxLength(100).HasDefaultValue("X-Session-Key");
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 实体迁移策略
|
||||||
|
|
||||||
|
### 删除现有实体
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// 1. 从 PlatformDbContext 中移除 DbSet
|
||||||
|
public DbSet<GwTenant> GwTenants => Set<GwTenant>(); // 删除
|
||||||
|
public DbSet<GwServiceInstance> GwServiceInstances => Set<GwServiceInstance>(); // 删除
|
||||||
|
|
||||||
|
// 2. 移除 OnModelCreating 中的配置
|
||||||
|
// modelBuilder.Entity<GwTenant>(...) - 删除
|
||||||
|
// modelBuilder.Entity<GwServiceInstance>(...) - 删除
|
||||||
|
```
|
||||||
|
|
||||||
|
### 创建 EF Core 迁移
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 创建迁移
|
||||||
|
dotnet ef migrations add RestructureGatewayEntities --project Fengling.Platform.Infrastructure
|
||||||
|
|
||||||
|
# 迁移将执行:
|
||||||
|
# - DROP TABLE GwTenants
|
||||||
|
# - DROP TABLE GwServiceInstances
|
||||||
|
# - CREATE TABLE GwClusters (包含 Destinations 作为 JSON 或关联表)
|
||||||
|
# - ALTER TABLE GwTenantRoutes (添加新字段)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. GwCluster → YARP ClusterConfig 映射
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public static ClusterConfig ToClusterConfig(this GwCluster cluster)
|
||||||
|
{
|
||||||
|
return new ClusterConfig
|
||||||
|
{
|
||||||
|
ClusterId = cluster.ClusterId,
|
||||||
|
LoadBalancingPolicy = cluster.LoadBalancingPolicy ?? "PowerOfTwoChoices",
|
||||||
|
Destinations = cluster.Destinations
|
||||||
|
.Where(d => d.Status == 1)
|
||||||
|
.ToDictionary(
|
||||||
|
d => d.DestinationId,
|
||||||
|
d => new DestinationConfig
|
||||||
|
{
|
||||||
|
Address = d.Address,
|
||||||
|
Health = d.Health,
|
||||||
|
Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["Weight"] = d.Weight.ToString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
HealthCheck = cluster.HealthCheck?.Enabled == true
|
||||||
|
? new HealthCheckConfig
|
||||||
|
{
|
||||||
|
Active = new ActiveHealthCheckConfig
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
Path = cluster.HealthCheck.Path ?? "/health",
|
||||||
|
Interval = TimeSpan.FromSeconds(cluster.HealthCheck.IntervalSeconds),
|
||||||
|
Timeout = TimeSpan.FromSeconds(cluster.HealthCheck.TimeoutSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
SessionAffinity = cluster.SessionAffinity?.Enabled == true
|
||||||
|
? new SessionAffinityConfig
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
Policy = cluster.SessionAffinity.Policy,
|
||||||
|
AffinityKeyName = cluster.SessionAffinity.AffinityKeyName
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. GwTenantRoute → YARP RouteConfig 映射
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public static RouteConfig ToRouteConfig(this GwTenantRoute route)
|
||||||
|
{
|
||||||
|
return new RouteConfig
|
||||||
|
{
|
||||||
|
RouteId = route.Id,
|
||||||
|
Match = new RouteMatch
|
||||||
|
{
|
||||||
|
Path = route.PathPattern,
|
||||||
|
Methods = route.Methods?.Split(',').ToList(),
|
||||||
|
Hosts = route.Hosts?.Split(',').ToList(),
|
||||||
|
Headers = ParseHeaderMatch(route.Headers)
|
||||||
|
},
|
||||||
|
ClusterId = route.ClusterId,
|
||||||
|
Order = route.Priority,
|
||||||
|
Metadata = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["TenantCode"] = route.TenantCode,
|
||||||
|
["ServiceName"] = route.ServiceName,
|
||||||
|
["IsGlobal"] = route.IsGlobal.ToString()
|
||||||
|
},
|
||||||
|
Transforms = ParseTransforms(route.Transforms)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<RouteHeader>? ParseHeaderMatch(string? headersJson)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(headersJson)) return null;
|
||||||
|
|
||||||
|
// 解析 JSON 格式的 Header 匹配规则
|
||||||
|
// [{"Name":"X-Custom","Values":["value1"],"Mode":"ExactHeader"}]
|
||||||
|
return JsonSerializer.Deserialize<List<RouteHeader>>(headersJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<IReadOnlyDictionary<string, string>>? ParseTransforms(string? transformsJson)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(transformsJson)) return null;
|
||||||
|
|
||||||
|
// 解析 JSON 格式的转换规则
|
||||||
|
return JsonSerializer.Deserialize<List<Dictionary<string, string>>>(transformsJson);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. IClusterStore 接口设计
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IClusterStore
|
||||||
|
{
|
||||||
|
// 基础查询
|
||||||
|
Task<GwCluster?> FindByIdAsync(string id, CancellationToken cancellationToken = default);
|
||||||
|
Task<GwCluster?> FindByClusterIdAsync(string clusterId, CancellationToken cancellationToken = default);
|
||||||
|
Task<IList<GwCluster>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
Task<IList<GwCluster>> GetPagedAsync(
|
||||||
|
int page,
|
||||||
|
int pageSize,
|
||||||
|
string? name = null,
|
||||||
|
ClusterStatus? status = null,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<int> GetCountAsync(
|
||||||
|
string? name = null,
|
||||||
|
ClusterStatus? status = null,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// CRUD 操作
|
||||||
|
Task<IdentityResult> CreateAsync(GwCluster cluster, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> UpdateAsync(GwCluster cluster, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> DeleteAsync(GwCluster cluster, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
// Destination 管理
|
||||||
|
Task<IdentityResult> AddDestinationAsync(string clusterId, GwDestination destination, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> UpdateDestinationAsync(string clusterId, GwDestination destination, CancellationToken cancellationToken = default);
|
||||||
|
Task<IdentityResult> RemoveDestinationAsync(string clusterId, string destinationId, CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 会话亲和实现
|
||||||
|
|
||||||
|
### 中间件:设置会话键
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public class SessionAffinityMiddleware
|
||||||
|
{
|
||||||
|
private readonly RequestDelegate _next;
|
||||||
|
|
||||||
|
public SessionAffinityMiddleware(RequestDelegate next)
|
||||||
|
{
|
||||||
|
_next = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InvokeAsync(HttpContext context)
|
||||||
|
{
|
||||||
|
// 优先使用 UserId
|
||||||
|
var userId = context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
|
||||||
|
// 其次使用 TenantCode
|
||||||
|
var tenantCode = context.User?.FindFirst("TenantCode")?.Value
|
||||||
|
?? context.Request.Headers["X-Tenant-Code"].FirstOrDefault();
|
||||||
|
|
||||||
|
// 设置会话亲和键
|
||||||
|
var sessionKey = userId ?? tenantCode ?? "anonymous";
|
||||||
|
context.Items["SessionAffinityKey"] = sessionKey;
|
||||||
|
|
||||||
|
// 添加到请求头供 YARP 使用
|
||||||
|
context.Request.Headers["X-Session-Key"] = sessionKey;
|
||||||
|
|
||||||
|
await _next(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 依赖关系图
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Wave 1 │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ GwCluster.cs │ │ GwTenantRoute │ │
|
||||||
|
│ │ GwDestination │ │ (扩展字段) │ │
|
||||||
|
│ │ 值对象 │ │ │ │
|
||||||
|
│ └────────┬────────┘ └────────┬────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
└───────────┼────────────────────┼────────────────────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Wave 2 │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ PlatformDbContext.cs │ │
|
||||||
|
│ │ - 移除 GwTenant, GwServiceInstance DbSet │ │
|
||||||
|
│ │ - 添加 GwCluster DbSet │ │
|
||||||
|
│ │ - 配置 Owned Entities │ │
|
||||||
|
│ └─────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Wave 3 │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||||||
|
│ │ IClusterStore │ │ IRouteStore │ │
|
||||||
|
│ │ ClusterStore │ │ RouteStore │ │
|
||||||
|
│ │ (替换Instance) │ │ (更新) │ │
|
||||||
|
│ └────────┬────────┘ └────────┬────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ ▼ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ Extensions.cs │ │
|
||||||
|
│ │ - 移除 IInstanceStore 注册 │ │
|
||||||
|
│ │ - 添加 IClusterStore 注册 │ │
|
||||||
|
│ └─────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 风险评估
|
||||||
|
|
||||||
|
| 风险 | 影响 | 缓解措施 |
|
||||||
|
|------|------|----------|
|
||||||
|
| 删除 GwTenant 导致数据丢失 | 高 | 确认无数据后再删除,或提供迁移脚本 |
|
||||||
|
| 删除 GwServiceInstance 导致数据丢失 | 高 | 同上 |
|
||||||
|
| EF Core 迁移失败 | 中 | 手动编写 SQL 迁移脚本 |
|
||||||
|
| Owned Entity 查询性能 | 低 | EF Core 8+ 已优化 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 参考资源
|
||||||
|
|
||||||
|
- [EF Core Owned Entities](https://learn.microsoft.com/ef/core/modeling/owned-entities)
|
||||||
|
- [YARP Configuration](https://microsoft.github.io/reverse-proxy/)
|
||||||
|
- [YARP GitHub](https://github.com/microsoft/reverse-proxy)
|
||||||
|
- 项目文档: `docs/yarp-configuration-model.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*研究完成日期: 2026-03-03*
|
||||||
207
.planning/phases/03-/03-gateway-cluster-entities-PLAN.md
Normal file
207
.planning/phases/03-/03-gateway-cluster-entities-PLAN.md
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
phase: 03-
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwDestination.cs
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwHealthCheckConfig.cs
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwSessionAffinityConfig.cs
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- GATEWAY-RESTRUCTURE-01
|
||||||
|
- GATEWAY-RESTRUCTURE-02
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "GwCluster 使用 string Id (GUID)"
|
||||||
|
- "GwDestination 作为 Owned Entity 内嵌"
|
||||||
|
- "值对象使用 Owned Entity 配置"
|
||||||
|
artifacts:
|
||||||
|
- path: "Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs"
|
||||||
|
provides: "集群聚合根"
|
||||||
|
min_lines: 50
|
||||||
|
- path: "Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwDestination.cs"
|
||||||
|
provides: "目标端点值对象"
|
||||||
|
min_lines: 30
|
||||||
|
- path: "Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwHealthCheckConfig.cs"
|
||||||
|
provides: "健康检查配置值对象"
|
||||||
|
min_lines: 20
|
||||||
|
- path: "Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwSessionAffinityConfig.cs"
|
||||||
|
provides: "会话亲和配置值对象"
|
||||||
|
min_lines: 20
|
||||||
|
---
|
||||||
|
|
||||||
|
# 计划 01: 创建 GwCluster 聚合根和值对象
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
创建新的 GwCluster 聚合根及相关值对象,替代原有的 GwServiceInstance 实体设计。
|
||||||
|
|
||||||
|
**目的:** 将服务实例管理改为集群聚合根模式,内嵌 Destinations 列表,符合 YARP ClusterConfig 结构。
|
||||||
|
|
||||||
|
**输出:** GwCluster 聚合根、GwDestination 值对象、GwHealthCheckConfig 值对象、GwSessionAffinityConfig 值对象。
|
||||||
|
|
||||||
|
## 上下文
|
||||||
|
|
||||||
|
@.planning/phases/03-/03-CONTEXT.md
|
||||||
|
@.planning/phases/03-/03-RESEARCH.md
|
||||||
|
@Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs (现有实体参考)
|
||||||
|
@Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GatewayEnums.cs (枚举)
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 1: 创建 GwHealthCheckConfig 值对象</name>
|
||||||
|
<files>Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwHealthCheckConfig.cs</files>
|
||||||
|
<action>
|
||||||
|
创建健康检查配置值对象:
|
||||||
|
```csharp
|
||||||
|
namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 健康检查配置(值对象)
|
||||||
|
/// </summary>
|
||||||
|
public class GwHealthCheckConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用健康检查
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 健康检查路径
|
||||||
|
/// </summary>
|
||||||
|
public string? Path { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 检查间隔(秒)
|
||||||
|
/// </summary>
|
||||||
|
public int IntervalSeconds { get; set; } = 30;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 超时时间(秒)
|
||||||
|
/// </summary>
|
||||||
|
public int TimeoutSeconds { get; set; } = 10;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</action>
|
||||||
|
<verify>文件可编译</verify>
|
||||||
|
<done>GwHealthCheckConfig 值对象已创建</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 2: 创建 GwSessionAffinityConfig 值对象</name>
|
||||||
|
<files>Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwSessionAffinityConfig.cs</files>
|
||||||
|
<action>
|
||||||
|
创建会话亲和配置值对象:
|
||||||
|
```csharp
|
||||||
|
namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 会话亲和配置(值对象)
|
||||||
|
/// </summary>
|
||||||
|
public class GwSessionAffinityConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 是否启用会话亲和
|
||||||
|
/// </summary>
|
||||||
|
public bool Enabled { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略:Header, Cookie
|
||||||
|
/// </summary>
|
||||||
|
public string Policy { get; set; } = "Header";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 亲和键名称
|
||||||
|
/// </summary>
|
||||||
|
public string AffinityKeyName { get; set; } = "X-Session-Key";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</action>
|
||||||
|
<verify>文件可编译</verify>
|
||||||
|
<done>GwSessionAffinityConfig 值对象已创建</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 3: 创建 GwDestination 值对象</name>
|
||||||
|
<files>Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwDestination.cs</files>
|
||||||
|
<action>
|
||||||
|
创建目标端点值对象:
|
||||||
|
```csharp
|
||||||
|
namespace Fengling.Platform.Domain.AggregatesModel.GatewayAggregate;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标端点(值对象,内嵌于 GwCluster)
|
||||||
|
/// </summary>
|
||||||
|
public class GwDestination
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 目标标识
|
||||||
|
/// </summary>
|
||||||
|
public string DestinationId { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 后端地址
|
||||||
|
/// </summary>
|
||||||
|
public string Address { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 健康检查端点
|
||||||
|
/// </summary>
|
||||||
|
public string? Health { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 权重(用于加权负载均衡)
|
||||||
|
/// </summary>
|
||||||
|
public int Weight { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 健康状态
|
||||||
|
/// </summary>
|
||||||
|
public int HealthStatus { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; } = 1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</action>
|
||||||
|
<verify>文件可编译</verify>
|
||||||
|
<done>GwDestination 值对象已创建</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 4: 创建 GwCluster 聚合根</name>
|
||||||
|
<files>Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwCluster.cs</files>
|
||||||
|
<action>
|
||||||
|
创建 GwCluster 聚合根:
|
||||||
|
- Id: string (GUID)
|
||||||
|
- ClusterId: string (业务标识)
|
||||||
|
- Name: string
|
||||||
|
- Description: string?
|
||||||
|
- Destinations: List<GwDestination> (内嵌)
|
||||||
|
- LoadBalancingPolicy: string
|
||||||
|
- HealthCheck: GwHealthCheckConfig?
|
||||||
|
- SessionAffinity: GwSessionAffinityConfig?
|
||||||
|
- Status: int
|
||||||
|
- 审计字段
|
||||||
|
|
||||||
|
参考现有 GwTenantRoute 的结构风格。
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build Fengling.Platform.Domain 通过</verify>
|
||||||
|
<done>GwCluster 聚合根已创建,包含所有字段</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- [ ] 所有 4 个文件已创建
|
||||||
|
- [ ] Build 无错误通过
|
||||||
|
- [ ] 值对象结构符合 YARP 配置模型
|
||||||
|
|
||||||
|
## 成功标准
|
||||||
|
|
||||||
|
Domain 实体准备好进行 Infrastructure 层更新。
|
||||||
74
.planning/phases/03-/03-gateway-di-update-PLAN.md
Normal file
74
.planning/phases/03-/03-gateway-di-update-PLAN.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
---
|
||||||
|
phase: 03-
|
||||||
|
plan: 04
|
||||||
|
type: execute
|
||||||
|
wave: 3
|
||||||
|
depends_on:
|
||||||
|
- 03-gateway-infrastructure-update
|
||||||
|
files_modified:
|
||||||
|
- Fengling.Platform.Infrastructure/Extensions.cs
|
||||||
|
- Fengling.Platform.Infrastructure/GatewayExtensions.cs
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- GATEWAY-RESTRUCTURE-07
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Extensions.cs 注册 IClusterStore 而非 IInstanceStore"
|
||||||
|
- "GatewayExtensions 可独立使用"
|
||||||
|
---
|
||||||
|
|
||||||
|
# 计划 04: 更新 Extensions 和 DI 注册
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
更新 Extensions.cs 以注册新的 IClusterStore,清理旧的 IInstanceStore 注册。
|
||||||
|
|
||||||
|
**目的:** 完成 DI 容器配置的更新,使新服务可被注入使用。
|
||||||
|
|
||||||
|
**输出:** 更新的 Extensions.cs。
|
||||||
|
|
||||||
|
## 上下文
|
||||||
|
|
||||||
|
@Fengling.Platform.Infrastructure/Extensions.cs
|
||||||
|
@Fengling.Platform.Infrastructure/GatewayExtensions.cs
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 1: 更新 Extensions.cs</name>
|
||||||
|
<files>Fengling.Platform.Infrastructure/Extensions.cs</files>
|
||||||
|
<action>
|
||||||
|
在 AddPlatformCore 方法中:
|
||||||
|
1. 移除旧注册:
|
||||||
|
- 移除 services.AddScoped<IInstanceStore, InstanceStore<TContext>>()
|
||||||
|
- 移除 services.AddScoped<IRouteManager, RouteManager>()(如果之前在 Gateway 部分)
|
||||||
|
|
||||||
|
2. 添加新注册:
|
||||||
|
- 添加 services.AddScoped<IClusterStore, ClusterStore<TContext>>()
|
||||||
|
|
||||||
|
注意:保持与现有 GatewayExtensions 的兼容性。
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>Extensions.cs 已更新</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 2: 更新 GatewayExtensions.cs(如需要)</name>
|
||||||
|
<files>Fengling.Platform.Infrastructure/GatewayExtensions.cs</files>
|
||||||
|
<action>
|
||||||
|
检查 GatewayExtensions.cs:
|
||||||
|
- 如果已注册 IClusterStore,确保与 Extensions.cs 一致
|
||||||
|
- 如果需要,添加注释说明两种注册方式
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>GatewayExtensions 检查完成</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- [ ] Extensions.cs 已更新
|
||||||
|
- [ ] Build 无错误通过
|
||||||
|
|
||||||
|
## 成功标准
|
||||||
|
|
||||||
|
Gateway 模块重构完成,所有服务正确注册。
|
||||||
128
.planning/phases/03-/03-gateway-infrastructure-update-PLAN.md
Normal file
128
.planning/phases/03-/03-gateway-infrastructure-update-PLAN.md
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
---
|
||||||
|
phase: 03-
|
||||||
|
plan: 03
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on:
|
||||||
|
- 03-gateway-cluster-entities
|
||||||
|
- 03-gateway-route-update
|
||||||
|
files_modified:
|
||||||
|
- Fengling.Platform.Infrastructure/PlatformDbContext.cs
|
||||||
|
- Fengling.Platform.Infrastructure/IInstanceStore.cs
|
||||||
|
- Fengling.Platform.Infrastructure/InstanceStore.cs
|
||||||
|
- Fengling.Platform.Infrastructure/IClusterStore.cs
|
||||||
|
- Fengling.Platform.Infrastructure/ClusterStore.cs
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- GATEWAY-RESTRUCTURE-05
|
||||||
|
- GATEWAY-RESTRUCTURE-06
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "PlatformDbContext 包含GwCluster DbSet"
|
||||||
|
- "IClusterStore 替代 IInstanceStore"
|
||||||
|
artifacts:
|
||||||
|
- path: "Fengling.Platform.Infrastructure/PlatformDbContext.cs"
|
||||||
|
provides: "更新的 DbContext"
|
||||||
|
- path: "Fengling.Platform.Infrastructure/IClusterStore.cs"
|
||||||
|
provides: "集群存储接口"
|
||||||
|
min_lines: 40
|
||||||
|
---
|
||||||
|
|
||||||
|
# 计划 03: 更新 Infrastructure 层
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
更新 PlatformDbContext 配置,创建 IClusterStore 接口和实现,替换原有的 IInstanceStore。
|
||||||
|
|
||||||
|
**目的:** 支持新的 GwCluster 聚合根,移除已废弃的实体 DbSet。
|
||||||
|
|
||||||
|
**输出:** 更新的 PlatformDbContext、IClusterStore/ClusterStore。
|
||||||
|
|
||||||
|
## 上下文
|
||||||
|
|
||||||
|
@Fengling.Platform.Infrastructure/PlatformDbContext.cs
|
||||||
|
@Fengling.Platform.Infrastructure/IInstanceStore.cs
|
||||||
|
@Fengling.Platform.Infrastructure/RouteStore.cs (参考模式)
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 1: 更新 PlatformDbContext</name>
|
||||||
|
<files>Fengling.Platform.Infrastructure/PlatformDbContext.cs</files>
|
||||||
|
<action>
|
||||||
|
1. 移除 DbSet:
|
||||||
|
- 移除 DbSet<GwTenant> GwTenants
|
||||||
|
- 移除 DbSet<GwServiceInstance> GwServiceInstances
|
||||||
|
|
||||||
|
2. 添加 DbSet:
|
||||||
|
- 添加 DbSet<GwCluster> GwClusters
|
||||||
|
|
||||||
|
3. 在 OnModelCreating 中配置:
|
||||||
|
- GwCluster 聚合根配置
|
||||||
|
- OwnsMany GwDestinations 配置
|
||||||
|
- OwnsOne GwHealthCheckConfig 配置
|
||||||
|
- OwnsOne GwSessionAffinityConfig 配置
|
||||||
|
|
||||||
|
参考 03-RESEARCH.md 中的 EF Core 配置代码。
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>PlatformDbContext 已更新</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 2: 创建 IClusterStore 接口</name>
|
||||||
|
<files>Fengling.Platform.Infrastructure/IClusterStore.cs</files>
|
||||||
|
<action>
|
||||||
|
创建 IClusterStore 接口,包含:
|
||||||
|
- FindByIdAsync, FindByClusterIdAsync
|
||||||
|
- GetAllAsync, GetPagedAsync, GetCountAsync
|
||||||
|
- CreateAsync, UpdateAsync, DeleteAsync
|
||||||
|
- AddDestinationAsync, UpdateDestinationAsync, RemoveDestinationAsync
|
||||||
|
|
||||||
|
参考 IRouteStore 模式。
|
||||||
|
</action>
|
||||||
|
<verify>文件可编译</verify>
|
||||||
|
<done>IClusterStore 接口已创建</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 3: 创建 ClusterStore 实现</name>
|
||||||
|
<files>Fengling.Platform.Infrastructure/ClusterStore.cs</files>
|
||||||
|
<action>
|
||||||
|
创建 ClusterStore<TContext> 实现 IClusterStore:
|
||||||
|
- 泛型约束: where TContext : PlatformDbContext
|
||||||
|
- 实现所有接口方法
|
||||||
|
- 支持软删除
|
||||||
|
- 支持内嵌 Destination 的 CRUD
|
||||||
|
|
||||||
|
参考 RouteStore 模式。
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>ClusterStore 实现已创建</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 4: 删除 IInstanceStore 和 InstanceStore</name>
|
||||||
|
<files>
|
||||||
|
Fengling.Platform.Infrastructure/IInstanceStore.cs
|
||||||
|
Fengling.Platform.Infrastructure/InstanceStore.cs
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
删除这两个文件:
|
||||||
|
- 已被 IClusterStore/ClusterStore 替代
|
||||||
|
- 确认无其他引用
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>旧文件已删除</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- [ ] PlatformDbContext 已更新
|
||||||
|
- [ ] IClusterStore/ClusterStore 已创建
|
||||||
|
- [ ] 旧文件已删除
|
||||||
|
- [ ] Build 无错误通过
|
||||||
|
|
||||||
|
## 成功标准
|
||||||
|
|
||||||
|
Infrastructure 层更新完成,准备更新 DI 注册。
|
||||||
105
.planning/phases/03-/03-gateway-route-update-PLAN.md
Normal file
105
.planning/phases/03-/03-gateway-route-update-PLAN.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
phase: 03-
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on:
|
||||||
|
- 03-gateway-cluster-entities
|
||||||
|
files_modified:
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenant.cs
|
||||||
|
- Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwServiceInstance.cs
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- GATEWAY-RESTRUCTURE-03
|
||||||
|
- GATEWAY-RESTRUCTURE-04
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "GwTenantRoute 扩展了 Methods, Hosts, Headers, Transforms 等字段"
|
||||||
|
- "旧实体 GwTenant 和 GwServiceInstance 标记为删除或移除"
|
||||||
|
artifacts:
|
||||||
|
- path: "Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs"
|
||||||
|
provides: "扩展的路由实体"
|
||||||
|
min_lines: 50
|
||||||
|
---
|
||||||
|
|
||||||
|
# 计划 02: 扩展 GwTenantRoute 并删除旧实体
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
扩展 GwTenantRoute 实体以支持完整的路由匹配能力和转换规则,并删除已废弃的 GwTenant 和 GwServiceInstance 实体。
|
||||||
|
|
||||||
|
**目的:** 添加 YARP 所需的路由匹配字段,同时清理不再需要的实体。
|
||||||
|
|
||||||
|
**输出:** 更新的 GwTenantRoute.cs,删除的 GwTenant.cs 和 GwServiceInstance.cs。
|
||||||
|
|
||||||
|
## 上下文
|
||||||
|
|
||||||
|
@.planning/phases/03-/03-CONTEXT.md
|
||||||
|
@Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs (现有)
|
||||||
|
|
||||||
|
## 任务
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 1: 扩展 GwTenantRoute 字段</name>
|
||||||
|
<files>Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs</files>
|
||||||
|
<action>
|
||||||
|
在 GwTenantRoute 中添加以下新字段:
|
||||||
|
|
||||||
|
1. 路由匹配能力:
|
||||||
|
- Methods?: string (HTTP 方法,如 "GET,POST")
|
||||||
|
- Hosts?: string (Host 头匹配,如 "api.example.com")
|
||||||
|
- Headers?: string (Header 匹配规则,JSON 格式)
|
||||||
|
|
||||||
|
2. 策略配置:
|
||||||
|
- LoadBalancingPolicy?: string (路由级别负载均衡策略覆盖)
|
||||||
|
- AuthorizationPolicy?: string (授权策略)
|
||||||
|
- CorsPolicy?: string (CORS 策略)
|
||||||
|
|
||||||
|
3. 请求转换:
|
||||||
|
- Transforms?: string (请求/响应转换规则,JSON 格式)
|
||||||
|
|
||||||
|
保留现有字段:Id, TenantCode, ServiceName, ClusterId, PathPattern, Priority, Status, IsGlobal
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>GwTenantRoute 已扩展新字段</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 2: 删除 GwTenant 实体</name>
|
||||||
|
<files>
|
||||||
|
Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenant.cs
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
删除 GwTenant.cs 文件:
|
||||||
|
- 原因:使用 Platform.Tenant 通过 TenantCode 关联
|
||||||
|
- 确认无其他依赖引用此实体
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>GwTenant 实体已删除</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>任务 3: 删除 GwServiceInstance 实体</name>
|
||||||
|
<files>
|
||||||
|
Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwServiceInstance.cs
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
删除 GwServiceInstance.cs 文件:
|
||||||
|
- 原因:改用 GwCluster 聚合根内嵌 Destination
|
||||||
|
- 确认无其他依赖引用此实体
|
||||||
|
</action>
|
||||||
|
<verify>dotnet build 通过</verify>
|
||||||
|
<done>GwServiceInstance 实体已删除</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
- [ ] GwTenantRoute 扩展字段已添加
|
||||||
|
- [ ] GwTenant.cs 已删除
|
||||||
|
- [ ] GwServiceInstance.cs 已删除
|
||||||
|
- [ ] Build 无错误通过
|
||||||
|
|
||||||
|
## 成功标准
|
||||||
|
|
||||||
|
Domain 层实体重构完成,准备进行 Infrastructure 层更新。
|
||||||
Loading…
Reference in New Issue
Block a user