fengling-gateway/.planning/phases/006-gateway-plugin-research/006-02-PLAN.md
movingsam 52eba07097 feat: add MigrationTool for gateway config migration (IMPL-7)
- Create MigrationTool console app for exporting DB config to K8s YAML
- Support dry-run mode and validation
- Add Npgsql and YamlDotNet dependencies
2026-03-08 00:35:04 +08:00

367 lines
10 KiB
Markdown
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.

---
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: "触发重载"
---
# 计划 02YARP 插件集成
<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>