From ed762b2e61a5ee21fe62df5e816e487eb99a6fbe Mon Sep 17 00:00:00 2001 From: movingsam Date: Tue, 3 Mar 2026 11:37:16 +0800 Subject: [PATCH] docs(03): capture phase context for gateway adjustment --- .planning/phases/03-/03-CONTEXT.md | 199 ++++++++++++++++++++ docs/yarp-configuration-model.md | 293 +++++++++++++++++++++++++++++ 2 files changed, 492 insertions(+) create mode 100644 .planning/phases/03-/03-CONTEXT.md create mode 100644 docs/yarp-configuration-model.md diff --git a/.planning/phases/03-/03-CONTEXT.md b/.planning/phases/03-/03-CONTEXT.md new file mode 100644 index 0000000..ba9d2f3 --- /dev/null +++ b/.planning/phases/03-/03-CONTEXT.md @@ -0,0 +1,199 @@ +# Phase 3: 调整网关部分的需求 - Context + +**Gathered:** 2026-03-03 +**Status:** Ready for planning + + +## Phase Boundary + +调整 Gateway 模块的实体结构和 YARP 集成方式,包括: +- 重构实体模型(删除 GwTenant 和 GwServiceInstance,新增 GwCluster) +- 扩展 GwTenantRoute 的匹配和转换能力 +- 实现与 YARP 配置模型的完整对接 + + + + +## Implementation Decisions + +### 实体结构调整 + +#### 删除的实体 +- **GwTenant** - 删除,直接使用 Platform.Tenant 通过 TenantCode 关联 +- **GwServiceInstance** - 删除,改为 GwCluster 聚合根内嵌 Destination + +#### 新增的实体 + +**GwCluster(集群聚合根)** +- `string Id` - GUID,与 YARP ClusterId 类型一致 +- `string ClusterId` - 集群标识(业务 ID) +- `string Name` - 集群名称 +- `string? Description` - 描述 +- `List Destinations` - 目标端点列表(内嵌) +- `string LoadBalancingPolicy` - 负载均衡策略 (RoundRobin, WeightedRoundRobin, LeastRequests 等) +- `GwHealthCheckConfig? HealthCheck` - 健康检查配置 +- `GwSessionAffinityConfig? SessionAffinity` - 会话亲和配置 +- `int Status` - 状态 +- 审计字段 (CreatedBy, CreatedTime, UpdatedBy, UpdatedTime, IsDeleted, Version) + +**GwDestination(内嵌值对象)** +- `string DestinationId` - 目标标识 +- `string Address` - 后端地址 +- `string? Health` - 健康检查端点 +- `int Weight` - 权重(用于加权负载均衡) +- `int HealthStatus` - 健康状态 +- `int Status` - 状态 + +**GwHealthCheckConfig(内嵌值对象)** +- `bool Enabled` - 是否启用 +- `string? Path` - 健康检查路径 (默认 /health) +- `int IntervalSeconds` - 检查间隔(秒) +- `int TimeoutSeconds` - 超时时间(秒) + +**GwSessionAffinityConfig(内嵌值对象)** +- `bool Enabled` - 是否启用 +- `string Policy` - 策略 (Header) +- `string AffinityKeyName` - 亲和键名称 + +#### 修改的实体 + +**GwTenantRoute 扩展字段** +- `string? Methods` - HTTP 方法匹配 (GET,POST,PUT,DELETE 等) +- `string? Hosts` - Host 头匹配 (支持通配符) +- `string? Headers` - Header 匹配规则 (JSON 格式) +- `string? LoadBalancingPolicy` - 路由级别负载均衡策略覆盖 +- `string? AuthorizationPolicy` - 授权策略 +- `string? RateLimiterPolicy` - 限流策略 +- `string? CorsPolicy` - CORS 策略 +- `string? Transforms` - 请求/响应转换规则 (JSON 格式) + +保留现有字段: +- `string Id` - GUID v7 +- `string TenantCode` - 租户代码(关联 Platform.Tenant) +- `string ServiceName` - 服务名称 +- `string ClusterId` - 关联 GwCluster +- `string PathPattern` - 路径匹配模式 +- `int Priority` - 优先级 +- `int Status` - 状态 +- `bool IsGlobal` - 是否全局路由 + +### 路由匹配能力 + +- **Path**: 完整支持 YARP Path 模式 (如 `/api/{**catch-all}`) +- **Methods**: 支持 HTTP 方法过滤,逗号分隔 (如 `GET,POST`) +- **Hosts**: 支持 Host 头匹配,逗号分隔 (如 `api.example.com,*.api.com`) +- **Headers**: JSON 格式动态配置,如 `[{"Name":"X-Custom","Values":["value1"],"Mode":"ExactHeader"}]` + +### 负载均衡策略 + +- **集群级别配置**: GwCluster.LoadBalancingPolicy 存储默认策略 +- **路由级别覆盖**: GwTenantRoute.LoadBalancingPolicy 可覆盖集群默认策略 +- 支持策略: `RoundRobin`, `LeastRequests`, `Random`, `PowerOfTwoChoices`, `WeightedRoundRobin` + +### 会话亲和 (Session Affinity) + +- **策略**: Header 方式 +- **标识来源**: UserId 优先,TenantCode 兜底 +- **AffinityKeyName**: `X-Session-Key` +- **实现逻辑**: + 1. 已登录用户: 使用 `UserId` 作为会话键 + 2. 未登录用户: 使用 `TenantCode` 作为会话键 + 3. 同一会话键的请求路由到同一后端实例 + +### 健康检查 + +- **方式**: 主动健康检查 (Active Health Check) +- **配置位置**: GwCluster.HealthCheck +- **默认配置**: + - Path: `/health` + - Interval: 30 秒 + - Timeout: 10 秒 + +### 请求/响应转换 (Transforms) + +- **格式**: JSON 数组 +- **示例**: + ```json + [ + {"RequestHeader": "X-Forwarded-For", "Set": "true"}, + {"ResponseHeader": "X-Served-By", "Set": "gateway"} + ] + ``` +- 支持的转换类型: RequestHeader, ResponseHeader, PathPrefix, PathRemovePrefix 等 + +### 租户关联 + +- **GwTenant 删除**: 不再单独维护网关租户表 +- **关联方式**: 通过 `TenantCode` 直接关联 Platform.Tenant +- **TenantCode 来源**: + - 已登录用户: 从 User.TenantInfo.TenantCode 获取 + - 请求头: 从 `X-Tenant-Code` 获取 + +### ID 类型约定 + +| 实体 | ID 类型 | 原因 | +|------|---------|------| +| GwTenantRoute | `string` (GUID v7) | 与 YARP RouteId 一致 | +| GwCluster | `string` (GUID) | 与 YARP ClusterId 一致 | +| GwDestination | 无独立 ID | 内嵌值对象 | + +### Claude's Discretion + +- Transforms JSON 的具体结构和验证规则 +- HealthCheckConfig 和 SessionAffinityConfig 的详细字段设计 +- Header 匹配规则 JSON 的完整 Schema +- 错误处理和验证逻辑 + + + + +## Specific Ideas + +- 会话亲和 Header 名称: `X-Session-Key` +- 健康检查默认路径: `/health` +- 负载均衡默认策略: `PowerOfTwoChoices` (YARP 推荐) +- Header 匹配采用 JSON 格式,支持运行时动态配置 + + + + +## Existing Code Insights + +### 需要删除的文件 +- `Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenant.cs` +- `Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwServiceInstance.cs` + +### 需要修改的文件 +- `Fengling.Platform.Domain/AggregatesModel/GatewayAggregate/GwTenantRoute.cs` - 扩展字段 +- `Fengling.Platform.Infrastructure/PlatformDbContext.cs` - 更新 DbSet 和配置 +- `Fengling.Platform.Infrastructure/IInstanceStore.cs` - 删除或改为 IClusterStore +- `Fengling.Platform.Infrastructure/InstanceStore.cs` - 删除或改为 ClusterStore + +### 需要新增的文件 +- `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` +- `Fengling.Platform.Infrastructure/IClusterStore.cs` +- `Fengling.Platform.Infrastructure/ClusterStore.cs` + +### 参考资源 +- YARP 配置模型文档: `docs/yarp-configuration-model.md` +- YARP 官方仓库: https://github.com/microsoft/reverse-proxy + + + + +## Deferred Ideas + +- 被动健康检查 (Passive Health Check) - 可在后续版本添加 +- 限流策略配置 (RateLimiterPolicy) - 可在后续版本添加 +- 授权策略配置 (AuthorizationPolicy) - 可在后续版本添加 +- CORS 策略配置 (CorsPolicy) - 可在后续版本添加 + + + +--- + +*Phase: 03-* +*Context gathered: 2026-03-03* \ No newline at end of file diff --git a/docs/yarp-configuration-model.md b/docs/yarp-configuration-model.md new file mode 100644 index 0000000..eef1cca --- /dev/null +++ b/docs/yarp-configuration-model.md @@ -0,0 +1,293 @@ +# YARP 配置模型分析 + +**来源:** [microsoft/reverse-proxy](https://github.com/microsoft/reverse-proxy) +**日期:** 2026-03-03 + +--- + +## 核心概念 + +YARP (Yet Another Reverse Proxy) 的配置模型由三个核心接口/类组成: + +1. **IProxyConfigProvider** - 配置数据源接口 +2. **IProxyConfig** - 配置快照接口 +3. **RouteConfig / ClusterConfig** - 路由和集群配置 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ IProxyConfigProvider │ +│ GetConfig() → IProxyConfig │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ IProxyConfig │ +│ ┌─────────────────────┐ ┌─────────────────────┐ │ +│ │ RouteConfig[] │ │ ClusterConfig[] │ │ +│ │ (路由规则) │ │ (集群配置) │ │ +│ └─────────────────────┘ └─────────────────────┘ │ +│ ChangeToken: IChangeToken (配置变更通知) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 1. RouteConfig (路由配置) + +路由定义了如何匹配传入请求并将其代理到集群。 + +### 核心字段 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `RouteId` | `string` | ✓ | 全局唯一路由标识 | +| `Match` | `RouteMatch` | ✓ | 请求匹配规则 | +| `ClusterId` | `string` | ○ | 目标集群 ID | +| `Order` | `int?` | ○ | 路由优先级 (数值越小优先级越高) | +| `AuthorizationPolicy` | `string?` | ○ | 授权策略名称 | +| `RateLimiterPolicy` | `string?` | ○ | 限流策略名称 | +| `TimeoutPolicy` | `string?` | ○ | 超时策略名称 | +| `CorsPolicy` | `string?` | ○ | CORS 策略名称 | +| `MaxRequestBodySize` | `long?` | ○ | 请求体最大大小 | +| `Metadata` | `IReadOnlyDictionary?` | ○ | 自定义元数据 | +| `Transforms` | `IReadOnlyList>?` | ○ | 请求/响应转换规则 | + +### RouteMatch (匹配规则) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `Methods` | `IReadOnlyList?` | HTTP 方法列表 (GET, POST 等) | +| `Hosts` | `IReadOnlyList?` | Host 头匹配 (支持通配符) | +| `Path` | `string?` | 路径模式 (如 `/api/{**catch-all}`) | +| `Headers` | `IReadOnlyList?` | 请求头匹配规则 | +| `QueryParameters` | `IReadOnlyList?` | 查询参数匹配规则 | + +--- + +## 2. ClusterConfig (集群配置) + +集群是一组等价的后端服务端点及其相关策略。 + +### 核心字段 + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `ClusterId` | `string` | ✓ | 全局唯一集群标识 | +| `Destinations` | `IReadOnlyDictionary?` | ○ | 目标端点字典 | +| `LoadBalancingPolicy` | `string?` | ○ | 负载均衡策略 | +| `SessionAffinity` | `SessionAffinityConfig?` | ○ | 会话亲和配置 | +| `HealthCheck` | `HealthCheckConfig?` | ○ | 健康检查配置 | +| `HttpClient` | `HttpClientConfig?` | ○ | HTTP 客户端配置 | +| `HttpRequest` | `ForwarderRequestConfig?` | ○ | 出站请求配置 | +| `Metadata` | `IReadOnlyDictionary?` | ○ | 自定义元数据 | + +### DestinationConfig (目标端点) + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `Address` | `string` | ✓ | 后端地址 (如 `https://127.0.0.1:8080/`) | +| `Health` | `string?` | ○ | 主动健康检查端点 | +| `Host` | `string?` | ○ | Host 头值 | +| `Metadata` | `IReadOnlyDictionary?` | ○ | 自定义元数据 | + +--- + +## 3. 负载均衡策略 + +YARP 内置以下负载均衡策略: + +| 策略名称 | 说明 | +|----------|------| +| `RoundRobin` | 轮询 | +| `LeastRequests` | 最少请求 | +| `Random` | 随机 | +| `PowerOfTwoChoices` | 二选一 (推荐默认) | +| `First` | 第一个 | +| `WeightedRoundRobin` | 加权轮询 | + +> **注意:** `WeightedRoundRobin` 需要在 Destination 的 Metadata 中配置 `Weight` 字段。 + +--- + +## 4. 健康检查配置 + +### HealthCheckConfig + +```csharp +public sealed record HealthCheckConfig +{ + public PassiveHealthCheckConfig? Passive { get; init; } // 被动健康检查 + public ActiveHealthCheckConfig? Active { get; init; } // 主动健康检查 + public string? AvailableDestinationsPolicy { get; init; } // 可用目标策略 +} +``` + +### ActiveHealthCheckConfig (主动健康检查) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `Enabled` | `bool?` | 是否启用 | +| `Interval` | `TimeSpan?` | 检查间隔 | +| `Timeout` | `TimeSpan?` | 超时时间 | +| `Policy` | `string?` | 健康检查策略 | +| `Path` | `string?` | 健康检查路径 | + +### PassiveHealthCheckConfig (被动健康检查) + +| 字段 | 类型 | 说明 | +|------|------|------| +| `Enabled` | `bool?` | 是否启用 | +| `Policy` | `string?` | 策略名称 | +| `ReactivationPeriod` | `TimeSpan?` | 重新激活周期 | + +--- + +## 5. 会话亲和配置 + +### SessionAffinityConfig + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `Enabled` | `bool?` | ○ | 是否启用 | +| `Policy` | `string?` | ○ | 策略 (Cookie, Header) | +| `FailurePolicy` | `string?` | ○ | 失败处理策略 | +| `AffinityKeyName` | `string` | ✓ | 亲和键名称 | +| `Cookie` | `SessionAffinityCookieConfig?` | ○ | Cookie 配置 | + +--- + +## 6. 动态配置更新 + +### IProxyConfig 接口 + +```csharp +public interface IProxyConfig +{ + string RevisionId { get; } // 配置版本 ID + IReadOnlyList Routes { get; } + IReadOnlyList Clusters { get; } + IChangeToken ChangeToken { get; } // 配置变更通知 +} +``` + +### 配置更新机制 + +1. **ChangeToken**: 当配置发生变化时,`ChangeToken` 会触发通知 +2. **RevisionId**: 每次配置更新都会生成新的版本 ID +3. **YARP 内部**: 监听 `ChangeToken`,触发时重新加载配置 + +### 实现动态更新的方式 + +```csharp +public class CustomConfigProvider : IProxyConfigProvider +{ + private volatile CustomProxyConfig _config; + + public IProxyConfig GetConfig() => _config; + + public void UpdateConfig(List routes, List clusters) + { + var oldConfig = _config; + _config = new CustomProxyConfig(routes, clusters); + oldConfig.SignalChange(); // 触发 ChangeToken + } +} +``` + +--- + +## 7. 与现有实体的映射关系 + +### GwTenantRoute → RouteConfig + +| GwTenantRoute 字段 | RouteConfig 字段 | 映射说明 | +|-------------------|------------------|----------| +| `Id` | `RouteId` | 直接映射 | +| `PathPattern` | `Match.Path` | 路径匹配模式 | +| `ClusterId` | `ClusterId` | 目标集群 | +| `Priority` | `Order` | 路由优先级 | +| `TenantCode` | `Metadata["TenantCode"]` | 租户标识 (元数据) | +| `ServiceName` | `Metadata["ServiceName"]` | 服务名称 (元数据) | +| `IsGlobal` | `Metadata["IsGlobal"]` | 全局路由标记 | + +### GwServiceInstance → DestinationConfig + +| GwServiceInstance 字段 | DestinationConfig 字段 | 映射说明 | +|-----------------------|------------------------|----------| +| `Address` | `Address` | 后端地址 | +| `Health` | `Health` | 健康检查端点 | +| `Weight` | `Metadata["Weight"]` | 权重 (加权负载均衡) | +| `DestinationId` | Dictionary Key | 目标字典的 Key | + +### 多租户路由生成逻辑 + +``` +for each GwTenantRoute: + routeId = $"{TenantCode}_{ServiceName}" or Id + routeConfig = new RouteConfig { + RouteId = routeId, + Match = new RouteMatch { Path = PathPattern }, + ClusterId = ClusterId, + Order = Priority, + Metadata = { TenantCode, ServiceName, IsGlobal } + } +``` + +--- + +## 8. 实体结构对比与建议 + +### 当前实体 vs YARP 需求 + +| 方面 | 当前实体 | YARP 需求 | 差异 | +|------|----------|-----------|------| +| 路由匹配 | 只有 `PathPattern` | `Methods`, `Hosts`, `Headers`, `QueryParameters` | 缺少高级匹配 | +| 负载均衡 | 无配置 | `LoadBalancingPolicy` | 缺少策略配置 | +| 会话亲和 | 无配置 | `SessionAffinityConfig` | 缺少会话保持 | +| 转换规则 | 无配置 | `Transforms` | 缺少请求/响应转换 | +| 授权策略 | 无配置 | `AuthorizationPolicy` | 缺少授权配置 | +| 限流 | 无配置 | `RateLimiterPolicy` | 缺少限流配置 | + +### 建议新增字段 + +**GwTenantRoute 新增:** + +```csharp +// 匹配规则扩展 +public string? Methods { get; set; } // GET,POST,PUT +public string? Hosts { get; set; } // api.example.com + +// 策略配置 +public string? LoadBalancingPolicy { get; set; } // RoundRobin, WeightedRoundRobin +public string? AuthorizationPolicy { get; set; } // 授权策略 +public string? RateLimiterPolicy { get; set; } // 限流策略 +public string? CorsPolicy { get; set; } // CORS 策略 + +// 转换规则 (JSON) +public string? Transforms { get; set; } // 请求/响应转换 + +// 会话亲和 +public bool SessionAffinityEnabled { get; set; } +public string? SessionAffinityPolicy { get; set; } // Cookie, Header +public string? SessionAffinityKeyName { get; set; } +``` + +**GwServiceInstance 新增:** + +```csharp +// 健康检查 +public string? HealthCheckPath { get; set; } // /health +public int? HealthCheckInterval { get; set; } // 秒 + +// 主动健康检查端点 +public string? HealthEndpoint { get; set; } // http://host:port/health +``` + +--- + +## 9. 参考资源 + +- [YARP 官方文档](https://microsoft.github.io/reverse-proxy/) +- [YARP GitHub 仓库](https://github.com/microsoft/reverse-proxy) +- [配置提供程序示例](https://github.com/microsoft/reverse-proxy/tree/main/samples) +- [Yarp.EfCore.Configuration](https://github.com/microsoft/reverse-proxy/tree/main/samples/Yarp.EfCore.Configuration) - EF Core 数据库配置示例 \ No newline at end of file