- Create MigrationTool console app for exporting DB config to K8s YAML - Support dry-run mode and validation - Add Npgsql and YamlDotNet dependencies
8.5 KiB
8.5 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 06-gateway-plugin-research | 01 | execute | 1 | true |
|
|
计划 01:插件加载基础设施
实现插件动态加载基础设施,包括 AssemblyLoadContext 隔离、插件发现和生命周期管理。目的: 为网关提供安全的插件加载机制,支持隔离和热重载。 产出: 可工作的插件加载系统,含单元测试。
<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>
@.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/Program.cs
```csharp // Fengling.Gateway.Plugin.Abstractions public interface IGatewayPlugin { string Name { get; } string Version { get; } string? Description { get; } Task OnLoadAsync(); Task OnUnloadAsync(); } ``` Task 1: 创建 PluginLoadContext 隔离机制 src/yarpgateway/Plugins/PluginLoadContext.cs, tests/YarpGateway.Tests/Unit/Plugins/PluginLoadContextTests.cs - Test 1: 加载插件程序集到独立 ALC - Test 2: 共享契约程序集使用默认 ALC - Test 3: 卸载 ALC 后内存被回收 创建可卸载的 AssemblyLoadContext:- 创建
src/yarpgateway/Plugins/PluginLoadContext.cs - 继承 AssemblyLoadContext,设置 isCollectible: true
- 使用 AssemblyDependencyResolver 解析依赖
- 关键:共享
Fengling.Gateway.Plugin.Abstractions到默认 ALC
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;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var path = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
return path != null ? LoadUnmanagedDllFromPath(path) : IntPtr.Zero;
}
}
注意:先写测试,确保卸载验证通过。 dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginLoadContextTests" --no-build - PluginLoadContext 类存在 - 测试验证隔离和卸载 - 构建通过
Task 2: 创建 PluginLoader 发现和加载逻辑 src/yarpgateway/Plugins/PluginLoader.cs, src/yarpgateway/Plugins/DiscoveredPlugin.cs, tests/YarpGateway.Tests/Unit/Plugins/PluginLoaderTests.cs - Test 1: 从目录发现插件程序集 - Test 2: 加载插件并返回 IGatewayPlugin 实例 - Test 3: 处理无效插件(返回 null 或异常) 创建插件发现和加载器:-
创建
DiscoveredPlugin.cs记录:- Id, Name, Version, AssemblyPath, EntryPoint
-
创建
PluginLoader.cs:DiscoverPlugins(string directory)- 扫描目录发现插件LoadPlugin(DiscoveredPlugin discovered)- 加载并实例化- 使用 System.Reflection.Metadata 或简单扫描
public class PluginLoader
{
public IEnumerable<DiscoveredPlugin> DiscoverPlugins(string pluginDirectory)
{
// 扫描 plugin.json 或程序集属性
}
public IGatewayPlugin? LoadPlugin(DiscoveredPlugin discovered)
{
var shadowPath = CreateShadowCopy(discovered.AssemblyPath);
var alc = new PluginLoadContext(shadowPath);
var assembly = alc.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(discovered.AssemblyPath)));
var type = assembly.GetType(discovered.EntryPoint);
return Activator.CreateInstance(type) as IGatewayPlugin;
}
}
影子复制:创建 CreateShadowCopy 方法,将插件复制到临时目录。
dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginLoaderTests" --no-build
- PluginLoader 类存在
- 可发现和加载插件
- 测试通过
- 创建
PluginHandle.cs:- 封装 PluginLoadContext 和 IGatewayPlugin
- 实现 IAsyncDisposable
- 提供 TrackUnloadability() 返回 WeakReference
public sealed class PluginHandle : IAsyncDisposable
{
private readonly PluginLoadContext _alc;
private readonly IGatewayPlugin _plugin;
private readonly string _shadowDirectory;
private bool _disposed;
public IGatewayPlugin Plugin => _plugin;
public WeakReference TrackUnloadability() => new(_alc, trackResurrection: true);
public async ValueTask DisposeAsync()
{
if (_disposed) return;
_disposed = true;
await _plugin.OnUnloadAsync();
_alc.Unload();
}
}
- 创建
PluginHost.cs:- 注入 PluginLoader
- 管理插件字典 (name -> PluginHandle)
- 提供 LoadAllAsync, UnloadAsync, GetPlugins
public class PluginHost
{
private readonly ConcurrentDictionary<string, PluginHandle> _plugins = new();
private readonly PluginLoader _loader;
public async Task LoadAllAsync(string pluginDirectory, CancellationToken ct = default)
{
var discovered = _loader.DiscoverPlugins(pluginDirectory);
foreach (var d in discovered)
{
var handle = _loader.LoadPlugin(d);
if (handle != null)
{
await handle.Plugin.OnLoadAsync();
_plugins[d.Id] = handle;
}
}
}
public async Task UnloadAsync(string pluginId)
{
if (_plugins.TryRemove(pluginId, out var handle))
{
await handle.DisposeAsync();
}
}
}
dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginHostTests" --no-build
- PluginHost 和 PluginHandle 类存在
- 可加载/卸载插件
- 卸载验证测试通过
1. dotnet build src/yarpgateway/YarpGateway.csproj 无错误
2. dotnet test tests/YarpGateway.Tests --filter "FullyQualifiedName~PluginLoad" 通过
3. 插件加载/卸载功能可用
<success_criteria>
- 插件可在独立 AssemblyLoadContext 中加载
- 插件可通过 WeakReference 验证卸载
- 所有单元测试通过 </success_criteria>