# 阶段 6 研究:网关插件技术调研与实现 **研究日期:** 2026-03-04 **状态:** 已完成 --- ## 1. 现有基础设施分析 ### 1.1 已有插件抽象层 项目已创建 `Fengling.Gateway.Plugin.Abstractions` 程序集,定义了核心插件接口: ```csharp // 已定义的接口 public interface IGatewayPlugin { string Name { get; } string Version { get; } string? Description { get; } Task OnLoadAsync(); Task OnUnloadAsync(); } public interface IRequestPlugin : IGatewayPlugin { ... } public interface IResponsePlugin : IGatewayPlugin { ... } public interface IRouteTransformPlugin : IGatewayPlugin { ... } public interface ILoadBalancePlugin : IGatewayPlugin { ... } ``` ### 1.2 缺失的组件 - ❌ **插件加载器** - 动态加载程序集的机制 - ❌ **插件生命周期管理** - 加载/卸载/热重载 - ❌ **插件隔离** - AssemblyLoadContext 隔离 - ❌ **YARP 集成** - 将插件集成到 YARP 管道 --- ## 2. YARP 扩展点 ### 2.1 中间件管道 YARP 使用 ASP.NET Core 中间件管道,允许自定义注入点: ```csharp app.MapReverseProxy(proxyPipeline => { proxyPipeline.Use(async (context, next) => { // 插件前置执行 await pluginFeature.ExecutePreProxyAsync(context); await next(); // 插件后置执行 await pluginFeature.ExecutePostProxyAsync(context); }); }); ``` ### 2.2 Transform 管道(推荐) Transform 是修改请求/响应的推荐方式: ```csharp builder.Services.AddReverseProxy() .AddTransforms(context => { var plugins = PluginManager.GetTransformPlugins(context.Route); foreach (var plugin in plugins) { context.AddRequestTransform(plugin.ApplyAsync); } }); ``` ### 2.3 路由扩展 YARP 2.0+ 支持自定义路由元数据,插件可消费: ```json { "Routes": { "api-route": { "Extensions": { "PluginConfig": { "PluginId": "rate-limiter", "MaxRequests": 100 } } } } } ``` --- ## 3. .NET 插件加载最佳实践 ### 3.1 AssemblyLoadContext 隔离 使用 **可卸载的 AssemblyLoadContext** 配合 **AssemblyDependencyResolver**: ```csharp public sealed class PluginLoadContext : AssemblyLoadContext { private readonly AssemblyDependencyResolver _resolver; private readonly string _sharedAssemblyName = "Fengling.Gateway.Plugin.Abstractions"; public PluginLoadContext(string pluginPath) : base(isCollectible: true) { _resolver = new AssemblyDependencyResolver(pluginPath); } protected override Assembly? Load(AssemblyName assemblyName) { // 共享契约程序集(防止类型身份问题) if (assemblyName.Name == _sharedAssemblyName) return null; // 回退到默认 ALC var path = _resolver.ResolveAssemblyToPath(assemblyName); return path != null ? LoadFromAssemblyPath(path) : null; } } ``` ### 3.2 影子复制(热重载必需) Windows 锁定加载的 DLL,需要影子复制: ```csharp public static string CreateShadowCopy(string pluginDirectory) { var shadowDir = Path.Combine( Path.GetTempPath(), "PluginShadows", $"{Path.GetFileName(pluginDirectory)}_{Guid.NewGuid():N}" ); CopyDirectory(pluginDirectory, shadowDir); return shadowDir; } ``` ### 3.3 插件句柄模式 ```csharp public sealed class PluginHandle : IAsyncDisposable { private readonly PluginLoadContext _alc; private readonly IGatewayPlugin _plugin; public async ValueTask DisposeAsync() { if (_plugin is IAsyncDisposable ad) await ad.DisposeAsync(); _alc.Unload(); // 计划回收 } } ``` ### 3.4 卸载验证 ```csharp public static bool VerifyUnload(WeakReference weakRef, int maxAttempts = 10) { for (var i = 0; i < maxAttempts && weakRef.IsAlive; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } return !weakRef.IsAlive; } ``` --- ## 4. 插件隔离模式 ### 4.1 契约边界(最重要的设计决策) **规则**:契约必须驻留在 **默认 ALC**,绝不在插件 ALC 中。 插件项目配置: ```xml false runtime ``` ### 4.2 静态缓存问题 **问题**:主机单例缓存插件类型会阻止卸载。 **解决方案**:只使用 DTO 跨越边界,不传递插件类型。 --- ## 5. 实现架构 ### 5.1 组件结构 ``` src/ ├── Fengling.Gateway.Plugin.Abstractions/ # 已存在 │ └── IGatewayPlugin.cs ├── yarpgateway/ │ ├── Plugins/ │ │ ├── PluginLoadContext.cs # ALC 隔离 │ │ ├── PluginLoader.cs # 加载逻辑 │ │ ├── PluginHost.cs # 生命周期管理 │ │ └── PluginMiddleware.cs # YARP 集成 │ └── Program.cs └── plugins/ # 插件目录 └── sample-plugin/ └── SamplePlugin.csproj ``` ### 5.2 插件目录结构 ``` plugins/ ├── rate-limiter/ │ ├── RateLimiterPlugin.dll │ ├── RateLimiterPlugin.deps.json │ └── plugin.json # 元数据 └── jwt-transform/ └── ... ``` ### 5.3 插件元数据 (plugin.json) ```json { "id": "rate-limiter", "name": "Rate Limiter Plugin", "version": "1.0.0", "entryPoint": "RateLimiterPlugin.RateLimiterPlugin", "interfaces": ["IRequestPlugin"], "dependencies": [] } ``` --- ## 6. 验证架构 ### 6.1 测试策略 1. **单元测试**:PluginLoadContext 隔离验证 2. **集成测试**:插件加载/卸载/热重载 3. **性能测试**:插件执行开销 ### 6.2 成功标准验证 | 标准 | 验证方法 | |------|---------| | 动态加载插件 | 单元测试:从目录加载并执行 | | 插件相互隔离 | 单元测试:异常不传播到其他插件 | | 热加载/卸载 | 集成测试:WeakReference 验证卸载 | --- ## 7. 推荐库 | 用途 | 库 | |------|---| | 网关核心 | Yarp.ReverseProxy 2.3.0+ | | 插件加载 | 自定义 AssemblyLoadContext | | 元数据读取 | System.Reflection.Metadata | | 依赖注入 | Microsoft.Extensions.DependencyInjection | --- ## 8. 关键注意事项 1. **不要在主机单例中缓存插件类型** 2. **始终用 WeakReference 测试卸载** 3. **Windows 上必须影子复制以支持热重载** 4. **使用仅元数据发现避免仅扫描而加载程序集** 5. **谨慎处理原生依赖**(它们不能干净卸载) --- *研究完成:2026-03-04*