- Create MigrationTool console app for exporting DB config to K8s YAML - Support dry-run mode and validation - Add Npgsql and YamlDotNet dependencies
367 lines
10 KiB
Markdown
367 lines
10 KiB
Markdown
---
|
||
phase: 06-gateway-plugin-research
|
||
plan: 02
|
||
type: execute
|
||
wave: 1
|
||
depends_on: [006-01]
|
||
files_modified: []
|
||
autonomous: true
|
||
requirements: [PLUG-03]
|
||
must_haves:
|
||
truths:
|
||
- "插件通过路由 Metadata 启用"
|
||
- "请求 Transform 轻量处理请求"
|
||
- "目标选择在路由匹配后执行"
|
||
- "插件按配置顺序执行"
|
||
artifacts:
|
||
- path: "src/yarpgateway/Plugins/PluginTransformProvider.cs"
|
||
provides: "YARP Transform 提供者"
|
||
- path: "src/yarpgateway/Plugins/YarpPluginMiddleware.cs"
|
||
provides: "插件管道集成"
|
||
- path: "src/yarpgateway/Plugins/PluginConfigWatcher.cs"
|
||
provides: "Console DB 通知监听"
|
||
- path: "tests/YarpGateway.Tests/Unit/Plugins/YarpIntegrationTests.cs"
|
||
provides: "集成测试"
|
||
key_links:
|
||
- from: "PluginTransformProvider"
|
||
to: "PluginHost"
|
||
via: "获取已加载插件"
|
||
- from: "YarpPluginMiddleware"
|
||
to: "PluginTransformProvider"
|
||
via: "应用 Transform"
|
||
- from: "PluginConfigWatcher"
|
||
to: "PluginHost"
|
||
via: "触发重载"
|
||
---
|
||
|
||
# 计划 02:YARP 插件集成
|
||
|
||
<objective>
|
||
将插件系统集成到 YARP 反向代理管道,实现 Transform 方式的请求/响应处理,以及通过 Metadata 驱动的插件启用机制。
|
||
|
||
**目的:** 实现 PLUG-03 - 插件隔离与生命周期管理,完成网关插件化。
|
||
**产出:** 可工作的 YARP 插件集成系统,含单元测试。
|
||
</objective>
|
||
|
||
<execution_context>
|
||
@/Users/mac/.config/opencode/get-shit-done/workflows/execute-plan.md
|
||
@/Users/mac/.config/opencode/get-shit-done/templates/summary.md
|
||
</execution_context>
|
||
|
||
<context>
|
||
@.planning/PROJECT.md
|
||
@.planning/ROADMAP.md
|
||
@.planning/STATE.md
|
||
@.planning/phases/006-gateway-plugin-research/006-RESEARCH.md
|
||
@src/Fengling.Gateway.Plugin.Abstractions/IGatewayPlugin.cs
|
||
@src/yarpgateway/Plugins/PluginHost.cs
|
||
@src/yarpgateway/Program.cs
|
||
</context>
|
||
|
||
<interfaces>
|
||
## 现有插件接口
|
||
|
||
```csharp
|
||
// Fengling.Gateway.Plugin.Abstractions
|
||
public interface IGatewayPlugin
|
||
{
|
||
string Name { get; }
|
||
string Version { get; }
|
||
string? Description { get; }
|
||
Task OnLoadAsync();
|
||
Task OnUnloadAsync();
|
||
}
|
||
|
||
public interface IRequestPlugin : IGatewayPlugin
|
||
{
|
||
Task<HttpContext?> OnRequestAsync(HttpContext context);
|
||
Task<HttpContext?> OnRouteMatchedAsync(HttpContext context, RouteConfig route);
|
||
Task<HttpContext?> OnForwardingAsync(HttpContext context, HttpRequestMessage request);
|
||
}
|
||
|
||
public interface IResponsePlugin : IGatewayPlugin
|
||
{
|
||
Task OnBackendResponseAsync(HttpContext context, HttpResponseMessage response);
|
||
Task OnResponseFinalizingAsync(HttpContext context);
|
||
}
|
||
|
||
public interface IRouteTransformPlugin : IGatewayPlugin
|
||
{
|
||
Task<RouteConfig> TransformRouteAsync(RouteConfig original, HttpContext context);
|
||
}
|
||
```
|
||
|
||
## YARP Transform 接口
|
||
|
||
```csharp
|
||
// YARP Transform
|
||
public interface IRequestTransform
|
||
{
|
||
Task ApplyAsync(RequestTransformContext context);
|
||
}
|
||
|
||
public interface IResponseTransform
|
||
{
|
||
Task ApplyAsync(ResponseTransformContext context);
|
||
}
|
||
|
||
public interface IClusterDestinationsTransform
|
||
{
|
||
Task ApplyAsync(ClusterDestinationsContext context);
|
||
}
|
||
```
|
||
</context>
|
||
|
||
<tasks>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 1: 创建 PluginTransformProvider</name>
|
||
<files>
|
||
src/yarpgateway/Plugins/PluginTransformProvider.cs,
|
||
tests/YarpGateway.Tests/Unit/Plugins/PluginTransformProviderTests.cs
|
||
</files>
|
||
<behavior>
|
||
- Test 1: 从路由 Metadata 发现启用的插件
|
||
- Test 2: 按 PluginOrder 排序
|
||
- Test 3: 创建 RequestTransform
|
||
- Test 4: 创建 ResponseTransform
|
||
</behavior>
|
||
<action>
|
||
创建 YARP Transform 提供者:
|
||
|
||
1. 创建 `PluginTransformProvider.cs`:
|
||
- 实现 `IProxyConfigFilter` 或 `IDynamicRouteConfigProvider`
|
||
- 扫描路由 Metadata 中的 "Plugins" 键
|
||
- 从 PluginHost 获取已加载的插件
|
||
- 按 "PluginOrder" 排序
|
||
|
||
2. 核心逻辑:
|
||
```csharp
|
||
public class PluginTransformProvider : IProxyConfigFilter
|
||
{
|
||
private readonly PluginHost _pluginHost;
|
||
|
||
public async Task ApplyTransformAsync(HttpContext context, RouteConfig route)
|
||
{
|
||
var pluginIds = route.Metadata?["Plugins"]?.Split(',');
|
||
if (pluginIds == null) return;
|
||
|
||
var order = route.Metadata?["PluginOrder"]?.Split(',') ?? pluginIds;
|
||
var orderedPlugins = order.Select(id => pluginIds.IndexOf(id))
|
||
.Select(i => _pluginHost.GetPlugins().ElementAt(i));
|
||
|
||
foreach (var plugin in orderedPlugins.OfType<IRequestPlugin>())
|
||
{
|
||
await plugin.OnRouteMatchedAsync(context, route);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
3. 创建 Request/Response Transform 类:
|
||
- `PluginRequestTransform` - 调用 IRequestPlugin
|
||
- `PluginResponseTransform` - 调用 IResponsePlugin
|
||
- `PluginRouteTransform` - 调用 IRouteTransformPlugin
|
||
</action>
|
||
<verify>
|
||
<automated>dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginTransformProviderTests" --no-build</automated>
|
||
</verify>
|
||
<done>
|
||
- PluginTransformProvider 存在
|
||
- Transform 测试通过
|
||
</done>
|
||
</task>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 2: 创建目标选择器 (DestinationSelector)</name>
|
||
<files>
|
||
src/yarpgateway/Plugins/DestinationSelector.cs,
|
||
tests/YarpGateway.Tests/Unit/Plugins/DestinationSelectorTests.cs
|
||
</files>
|
||
<behavior>
|
||
- Test 1: OnRouteMatchedAsync 选择目标
|
||
- Test 2: 根据上下文修改目标列表
|
||
- Test 3: 特殊租户路由到特殊目标
|
||
</behavior>
|
||
<action>
|
||
创建目标选择器,用于 OnRouteMatchedAsync 阶段:
|
||
|
||
1. 创建 `DestinationSelector.cs`:
|
||
- 实现 IClusterDestinationsTransform
|
||
- 在路由匹配后、负载均衡前执行
|
||
- 可以根据 HttpContext 修改可用目标列表
|
||
|
||
```csharp
|
||
public class DestinationSelector : IClusterDestinationsTransform
|
||
{
|
||
private readonly PluginHost _pluginHost;
|
||
|
||
public async Task ApplyAsync(ClusterDestinationsContext context)
|
||
{
|
||
var httpContext = context.HttpContext;
|
||
|
||
// 获取路由上启用的插件
|
||
var routeConfig = httpContext.GetRouteConfig();
|
||
var pluginIds = routeConfig?.Metadata?["Plugins"]?.Split(',');
|
||
|
||
if (pluginIds == null) return;
|
||
|
||
foreach (var pluginId in pluginIds)
|
||
{
|
||
var plugin = _pluginHost.GetPlugin(pluginId);
|
||
if (plugin is IRequestPlugin requestPlugin)
|
||
{
|
||
// 让插件过滤/修改目标列表
|
||
var availableDestinations = context.Destinations.ToList();
|
||
// 插件逻辑可以修改 availableDestinations
|
||
context.Destinations = availableDestinations;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
2. 注册到 YARP:
|
||
```csharp
|
||
builder.Services.AddSingleton<IClusterDestinationsTransform, DestinationSelector>();
|
||
```
|
||
</action>
|
||
<verify>
|
||
<automated>dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~DestinationSelectorTests" --no-build</automated>
|
||
</verify>
|
||
<done>
|
||
- DestinationSelector 存在
|
||
- 目标选择逻辑工作正常
|
||
</done>
|
||
</task>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 3: 创建 PluginConfigWatcher (Console DB 通知)</name>
|
||
<files>
|
||
src/yarpgateway/Plugins/PluginConfigWatcher.cs,
|
||
tests/YarpGateway.Tests/Unit/Plugins/PluginConfigWatcherTests.cs
|
||
</files>
|
||
<behavior>
|
||
- Test 1: 监听配置变更通知
|
||
- Test 2: 触发插件重载
|
||
- Test 3: 处理通知失败
|
||
</behavior>
|
||
<action>
|
||
创建配置监听器,监听 Console DB 通知:
|
||
|
||
1. 创建 `PluginConfigWatcher.cs`:
|
||
- 实现 `IHostedService` 或使用现有的 `PgSqlConfigChangeListener`
|
||
- 监听插件配置变更频道(如 `plugin_config_changed`)
|
||
- 触发 PluginHost 重载
|
||
|
||
```csharp
|
||
public class PluginConfigWatcher : BackgroundService
|
||
{
|
||
private readonly PluginHost _pluginHost;
|
||
private readonly NpgsqlConnection _connection;
|
||
private const string Channel = "plugin_config_changed";
|
||
|
||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||
{
|
||
await _connection.OpenAsync(stoppingToken);
|
||
await _connection.ListenAsync(Channel, stoppingToken);
|
||
|
||
await foreach (var notification in _connection.Notifications(stoppingToken))
|
||
{
|
||
// 解析通知,获取需要重载的插件
|
||
var payload = JsonSerializer.Deserialize<PluginConfigPayload>(notification.Payload);
|
||
await _pluginHost.ReloadAsync(payload.PluginId);
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
2. 注册到 DI:
|
||
```csharp
|
||
builder.Services.AddHostedService<PluginConfigWatcher>();
|
||
```
|
||
</action>
|
||
<verify>
|
||
<automated>dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginConfigWatcherTests" --no-build</automated>
|
||
</verify>
|
||
<done>
|
||
- PluginConfigWatcher 存在
|
||
- 监听器正确响应通知
|
||
</done>
|
||
</task>
|
||
|
||
<task type="auto" tdd="true">
|
||
<name>Task 4: 更新 Program.cs 集成插件系统</name>
|
||
<files>
|
||
src/yarpgateway/Program.cs
|
||
</files>
|
||
<behavior>
|
||
- Test 1: 插件系统在启动时初始化
|
||
- Test 2: Transform 被正确应用
|
||
</behavior>
|
||
<action>
|
||
更新 Program.cs 集成插件系统:
|
||
|
||
1. 注册插件服务:
|
||
```csharp
|
||
// 插件目录
|
||
var pluginDirectory = configuration.GetValue<string>("Plugin:Directory") ?? "plugins";
|
||
|
||
// 插件主机
|
||
builder.Services.AddSingleton(sp => new PluginHost(pluginDirectory));
|
||
|
||
// Transform 提供者
|
||
builder.Services.AddSingleton<IProxyConfigFilter, PluginTransformProvider>();
|
||
|
||
// 目标选择器
|
||
builder.Services.AddSingleton<IClusterDestinationsTransform, DestinationSelector>();
|
||
|
||
// 配置监听器
|
||
builder.Services.AddHostedService<PluginConfigWatcher>();
|
||
```
|
||
|
||
2. 初始化插件:
|
||
```csharp
|
||
var app = builder.Build();
|
||
|
||
// 启动时加载插件
|
||
var pluginHost = app.Services.GetRequiredService<PluginHost>();
|
||
await pluginHost.LoadAllAsync();
|
||
```
|
||
|
||
3. 配置 YARP 使用 Transform:
|
||
```csharp
|
||
builder.Services.AddReverseProxy()
|
||
.AddTransforms<PluginTransformProvider>();
|
||
```
|
||
</action>
|
||
<verify>
|
||
<automated>dotnet build src/yarpgateway/YarpGateway.csproj</automated>
|
||
</verify>
|
||
<done>
|
||
- Program.cs 正确集成插件系统
|
||
- 构建通过
|
||
</done>
|
||
</task>
|
||
|
||
</tasks>
|
||
|
||
<verification>
|
||
1. dotnet build src/yarpgateway/YarpGateway.csproj 无错误
|
||
2. dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~Plugin" 通过
|
||
3. 插件可通过 Metadata 启用
|
||
4. Transform 正确应用到请求/响应
|
||
</verification>
|
||
|
||
<success_criteria>
|
||
- 插件通过 YARP Transform 管道处理请求
|
||
- 目标选择在路由匹配后执行
|
||
- 插件通过 Metadata 启用
|
||
- Console DB 通知可触发插件重载
|
||
- 所有单元测试通过
|
||
</success_criteria>
|
||
|
||
<output>
|
||
完成后创建 `.planning/phases/006-gateway-plugin-research/006-02-SUMMARY.md`
|
||
</output>
|