---
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 插件集成
将插件系统集成到 YARP 反向代理管道,实现 Transform 方式的请求/响应处理,以及通过 Metadata 驱动的插件启用机制。
**目的:** 实现 PLUG-03 - 插件隔离与生命周期管理,完成网关插件化。
**产出:** 可工作的 YARP 插件集成系统,含单元测试。
@/Users/mac/.config/opencode/get-shit-done/workflows/execute-plan.md
@/Users/mac/.config/opencode/get-shit-done/templates/summary.md
@.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
## 现有插件接口
```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 OnRequestAsync(HttpContext context);
Task OnRouteMatchedAsync(HttpContext context, RouteConfig route);
Task OnForwardingAsync(HttpContext context, HttpRequestMessage request);
}
public interface IResponsePlugin : IGatewayPlugin
{
Task OnBackendResponseAsync(HttpContext context, HttpResponseMessage response);
Task OnResponseFinalizingAsync(HttpContext context);
}
public interface IRouteTransformPlugin : IGatewayPlugin
{
Task 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);
}
```
Task 1: 创建 PluginTransformProvider
src/yarpgateway/Plugins/PluginTransformProvider.cs,
tests/YarpGateway.Tests/Unit/Plugins/PluginTransformProviderTests.cs
- Test 1: 从路由 Metadata 发现启用的插件
- Test 2: 按 PluginOrder 排序
- Test 3: 创建 RequestTransform
- Test 4: 创建 ResponseTransform
创建 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())
{
await plugin.OnRouteMatchedAsync(context, route);
}
}
}
```
3. 创建 Request/Response Transform 类:
- `PluginRequestTransform` - 调用 IRequestPlugin
- `PluginResponseTransform` - 调用 IResponsePlugin
- `PluginRouteTransform` - 调用 IRouteTransformPlugin
dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginTransformProviderTests" --no-build
- PluginTransformProvider 存在
- Transform 测试通过
Task 2: 创建目标选择器 (DestinationSelector)
src/yarpgateway/Plugins/DestinationSelector.cs,
tests/YarpGateway.Tests/Unit/Plugins/DestinationSelectorTests.cs
- Test 1: OnRouteMatchedAsync 选择目标
- Test 2: 根据上下文修改目标列表
- Test 3: 特殊租户路由到特殊目标
创建目标选择器,用于 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();
```
dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~DestinationSelectorTests" --no-build
- DestinationSelector 存在
- 目标选择逻辑工作正常
Task 3: 创建 PluginConfigWatcher (Console DB 通知)
src/yarpgateway/Plugins/PluginConfigWatcher.cs,
tests/YarpGateway.Tests/Unit/Plugins/PluginConfigWatcherTests.cs
- Test 1: 监听配置变更通知
- Test 2: 触发插件重载
- Test 3: 处理通知失败
创建配置监听器,监听 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(notification.Payload);
await _pluginHost.ReloadAsync(payload.PluginId);
}
}
}
```
2. 注册到 DI:
```csharp
builder.Services.AddHostedService();
```
dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginConfigWatcherTests" --no-build
- PluginConfigWatcher 存在
- 监听器正确响应通知
Task 4: 更新 Program.cs 集成插件系统
src/yarpgateway/Program.cs
- Test 1: 插件系统在启动时初始化
- Test 2: Transform 被正确应用
更新 Program.cs 集成插件系统:
1. 注册插件服务:
```csharp
// 插件目录
var pluginDirectory = configuration.GetValue("Plugin:Directory") ?? "plugins";
// 插件主机
builder.Services.AddSingleton(sp => new PluginHost(pluginDirectory));
// Transform 提供者
builder.Services.AddSingleton();
// 目标选择器
builder.Services.AddSingleton();
// 配置监听器
builder.Services.AddHostedService();
```
2. 初始化插件:
```csharp
var app = builder.Build();
// 启动时加载插件
var pluginHost = app.Services.GetRequiredService();
await pluginHost.LoadAllAsync();
```
3. 配置 YARP 使用 Transform:
```csharp
builder.Services.AddReverseProxy()
.AddTransforms();
```
dotnet build src/yarpgateway/YarpGateway.csproj
- Program.cs 正确集成插件系统
- 构建通过
1. dotnet build src/yarpgateway/YarpGateway.csproj 无错误
2. dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~Plugin" 通过
3. 插件可通过 Metadata 启用
4. Transform 正确应用到请求/响应
- 插件通过 YARP Transform 管道处理请求
- 目标选择在路由匹配后执行
- 插件通过 Metadata 启用
- Console DB 通知可触发插件重载
- 所有单元测试通过