--- 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 通知可触发插件重载 - 所有单元测试通过 完成后创建 `.planning/phases/006-gateway-plugin-research/006-02-SUMMARY.md`