# WebAssembly 插件系统设计文档 ## 1. 概述 ### 1.1 目标 为博客系统引入基于 **WebAssembly (WASM)** 的插件机制,使第三方开发者无需修改/重新编译宿主程序即可扩展博客功能。 ### 1.2 设计原则 | 原则 | 说明 | |------|------| | **沙箱隔离** | 插件运行在 WASM 沙箱中,无法直接访问宿主文件系统/网络/数据库 | | **热加载** | 运行时扫描插件目录,新增/更新 `.wasm` 文件后自动加载,无需重启 | | **零侵入** | 宿主代码通过 Hook 点调用插件,插件不存在时走默认逻辑 | | **类型安全** | 宿主-插件之间通过 WIT (WebAssembly Interface Types) 定义强类型接口 | | **多语言支持** | 插件可用 Rust / AssemblyScript / Go / C 等任何可编译为 WASI 的语言编写 | ### 1.3 技术选型 | 组件 | 选择 | 理由 | |------|------|------| | WASM 运行时 | **wasmtime** (Cranelift) | Bytecode Alliance 官方、快启动、成熟安全模型、WASI preview1/2 | | 接口定义 | **wit-bindgen** + WIT 文件 | wasm-component-model 标准方案,自动生成 Rust/AS/Go 绑定 | | 组件模型 | wasm-component-model | 类型安全的跨语言调用,替代原始 export/import 函数 | | 插件语言 | Rust (主推) + AssemblyScript | Rust 零成本 ABI、AS 作为轻量替代 | --- ## 2. 架构总览 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 宿主 (raisfast) │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │ handlers/ │ │ services/ │ │ models/ │ │ middleware/│ │ │ └─────┬─────┘ └─────┬─────┘ └───────────┘ └───────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌─────────────────────────────────────┐ │ │ │ PluginManager │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ Hook Dispatcher │ │ │ │ │ │ on_post_create / on_comment │ │ │ │ │ │ render_markdown / filter │ │ │ │ │ └──────────┬───────────────────┘ │ │ │ │ │ │ │ │ │ ┌──────────▼───────────────────┐ │ │ │ │ │ Engine (wasmtime::Engine) │ │ │ │ │ │ Store (per-plugin) │ │ │ │ │ │ Linker (host functions) │ │ │ │ │ └──────────────────────────────┘ │ │ │ └─────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ Host Functions (暴露给插件的 API) │ │ │ │ host_log() / host_get_config() │ │ │ │ host_http_get() / host_db_query() │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ │ │ WASM 调用 (组件模型 / wit-bindgen) ▼ ┌─────────────────────────────────────────────────────────────────┐ │ 插件实例 (WASM 沙箱) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ plugin-a.wasm│ │ plugin-b.wasm│ │ plugin-c.wasm│ │ │ │ (Rust 编译) │ │ (AS 编译) │ │ (Go 编译) │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` --- ## 3. 插件接口定义 (WIT) ### 3.1 WIT 文件 放在 `wit/plugin.wit`,作为宿主和插件之间的契约: ```wit package raisfast:plugin; /// 插件元数据,由插件导出。 interface metadata { /// 插件唯一标识(反向域名格式,如 "com.example.my-plugin") resource plugin-id { /// 插件名称(人类可读) name: func() -> string; /// 插件版本(semver) version: func() -> string; /// 插件描述 description: func() -> string; } } /// 宿主暴露给插件的能力。 interface host-api { /// 写日志到宿主的 tracing 系统。 /// level: "trace" | "debug" | "info" | "warn" | "error" log: func(level: string, message: string); /// 获取宿主配置项。 get-config: func(key: string) -> option; /// 发起 HTTP GET 请求(受白名单限制)。 http-get: func(url: string) -> result; /// 获取文章内容(只读)。 get-post: func(slug: string) -> option; /// 获取当前请求的上下文信息。 get-request-context: func() -> option; } /// 请求上下文,传递给插件的当前 HTTP 请求信息。 resource request-context { method: func() -> string; path: func() -> string; client-ip: func() -> string; user-id: func() -> option; user-role: func() -> option; } /// Hook 接口 — 插件可选实现的部分。 world blog-plugin { import host-api; export metadata; /// 文章生命周期钩子 export on-post-creating: func(post: post-input) -> post-input; export on-post-created: func(post: post-output) -> void; export on-post-updating: func(old: post-output, new: post-input) -> post-input; export on-post-deleted: func(post-id: string) -> void; /// 评论生命周期钩子 export on-comment-creating: func(comment: comment-input) -> result; export on-comment-created: func(comment-id: string) -> void; /// 内容渲染钩子 export render-markdown: func(content: string) -> option; export filter-html: func(html: string) -> string; /// 自定义路由钩子 export handle-route: func(path: string, method: string, body: option) -> option; /// 认证钩子 export on-login: func(email: string, success: bool) -> void; } ``` ### 3.2 数据类型 ```wit record post-input { title: string, content: string, slug: option, excerpt: option, category-id: option, tag-ids: list, status: string, } record post-output { id: string, title: string, slug: string, content: string, excerpt: option, status: string, author-id: string, category-id: option, view-count: u64, created-at: string, updated-at: string, published-at: option, } record comment-input { content: string, nickname: option, email: option, parent-id: option, } record route-response { status: u16, headers: list>, body: string, } ``` --- ## 4. Hook 系统 ### 4.1 Hook 分类 | 类型 | Hook 名称 | 触发时机 | 返回值影响 | |------|-----------|---------|-----------| | **过滤器 (Filter)** | `on-post-creating` | 创建文章前 | 可修改文章字段 | | **过滤器** | `on-post-updating` | 更新文章前 | 可修改更新字段 | | **过滤器** | `render-markdown` | Markdown 渲染 | 替换渲染结果 | | **过滤器** | `filter-html` | HTML 净化后 | 可修改输出 HTML | | **过滤器** | `on-comment-creating` | 发表评论前 | 可修改/拒绝评论 | | **动作 (Action)** | `on-post-created` | 文章创建后 | 无返回值,执行副作用 | | **动作** | `on-post-deleted` | 文章删除后 | 无返回值 | | **动作** | `on-comment-created` | 评论发表后 | 无返回值 | | **动作** | `on-login` | 登录后 | 无返回值 | | **路由 (Route)** | `handle-route` | 未匹配路由时 | 可返回自定义响应 | ### 4.2 调度流程 ``` ┌──────────────────────┐ │ 业务操作触发 Hook │ │ e.g. service::create │ └──────────┬───────────┘ │ ▼ ┌──────────────────────┐ │ PluginManager │ │ .dispatch(hook, ctx)│ └──────────┬───────────┘ │ ┌──────────▼───────────┐ │ 遍历注册了此 Hook │ │ 的插件(按优先级) │ └──────────┬───────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Plugin A │ │ Plugin B │ │ Plugin C │ │ priority │ │ priority │ │ priority │ │ = 10 │ │ = 20 │ │ = 30 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ ▼ ▼ ▼ ┌─────────────────────────────────────────┐ │ Filter 类型: 链式传递,每个插件 │ │ 接收上一个插件的输出作为输入 │ │ │ │ input → A.filter → B.filter → C.filter │ │ → 最终输出 │ │ │ │ Action 类型: 顺序执行,忽略返回值 │ │ input → A.action → B.action → C.action │ └──────────────────────────────────────────┘ ``` ### 4.3 Filter 链式调用示例 ``` on-post-creating 链: 原始输入: { title: "Hello World", content: "# Hello..." } │ ▼ Plugin A (SEO 优化插件): → 自动生成 excerpt → 输出: { title: "Hello World", content: "# Hello...", excerpt: "Hello..." } │ ▼ Plugin B (内容审核插件): → 检查内容合规,不做修改 → 输出: { title: "Hello World", content: "# Hello...", excerpt: "Hello..." } │ ▼ 最终结果传入 service::create_post() ``` --- ## 5. 插件清单 (Manifest) 每个插件目录包含一个 `plugin.toml` 清单文件: ```toml [plugin] id = "com.example.seo-optimizer" name = "SEO Optimizer" version = "1.0.0" description = "自动优化文章 SEO:生成 meta description、OG 标签" author = "Example Corp" license = "MIT" runtime = "wasi" # 或 "reactor" (无命令行入口) language = "rust" # rust | assemblyscript | go | c [permissions] http = ["cdn.example.com/*"] # 允许访问的域名白名单 config = ["seo.*"] # 允许读取的配置项前缀 db-read = ["posts"] # 允许只读访问的表 db-write = [] # 允许写入的表(默认空) max_memory_mb = 32 # 内存上限 timeout_ms = 5000 # 单次 Hook 执行超时 [hooks] on-post-creating = { priority = 10 } render-markdown = { priority = 20 } filter-html = { priority = 5 } handle-route = { match = "/api/v1/plugins/seo/*" } ``` --- ## 6. 目录结构 ``` raisfast/ ├── plugins/ # 插件根目录 │ ├── seo-optimizer/ # 插件目录 = 插件 ID (kebab-case) │ │ ├── plugin.toml # 清单文件 │ │ └── plugin.wasm # 编译后的 WASM 文件 │ ├── content-filter/ │ │ ├── plugin.toml │ │ └── plugin.wasm │ └── webhook-notify/ │ ├── plugin.toml │ └── plugin.wasm ├── plugins-sdk/ # 插件开发 SDK (Rust crate) │ ├── Cargo.toml │ └── src/ │ ├── lib.rs # SDK 入口,重导出所有类型 │ ├── host.rs # 宿主函数绑定 (自动生成) │ ├── types.rs # 数据类型 (post, comment, etc.) │ └── hooks.rs # Hook 注册宏 ├── plugins-examples/ # 示例插件源码 │ ├── seo-optimizer/ │ │ ├── Cargo.toml │ │ └── src/lib.rs │ └── content-filter/ │ ├── Cargo.toml │ └── src/lib.rs └── wit/ └── plugin.wit # 接口定义文件 ``` --- ## 7. 宿主侧实现设计 ### 7.1 新增 Cargo 依赖 ```toml # Cargo.toml 新增 [dependencies] wasmtime = "28" wasmtime-wasi = "28" wit-bindgen = "0.36" toml = "0.8" notify = { version = "7", features = ["macos_kqueue"] } # 文件监听 ``` ### 7.2 核心数据结构 ```rust // src/plugins/mod.rs /// 插件系统核心管理器 pub struct PluginManager { engine: wasmtime::Engine, linker: wasmtime::Linker, plugins: HashMap, hook_registry: HashMap>, watcher: Option, } struct LoadedPlugin { id: String, manifest: PluginManifest, instance: wasmtime::component::Instance, store: wasmtime::Store, metadata: PluginMetadata, } struct HookEntry { plugin_id: String, priority: i32, hook_fn: HookFn, } enum HookFn { PostCreating(/* typed closure */), PostCreated(/* ... */), CommentCreating(/* ... */), RenderMarkdown(/* ... */), FilterHtml(/* ... */), HandleRoute(/* ... */), // ... } struct PluginState { config: Arc, pool: SqlitePool, request_ctx: Option, } #[derive(Debug, Clone)] struct PluginManifest { id: String, name: String, version: String, description: String, language: String, permissions: Permissions, hooks: HashMap, } ``` ### 7.3 PluginManager 生命周期 ``` ┌─────────────────────────────────────────────────────────────────┐ │ PluginManager 启动流程 │ │ │ │ ┌────────────────────────┐ │ │ │ 1. 创建 wasmtime::Engine│ │ │ │ 配置: 内存上限/指令计数│ │ │ └────────────┬───────────┘ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 2. 创建 Linker │ │ │ │ 注册 host functions │ │ │ │ (log/config/http) │ │ │ └────────────┬───────────┘ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 3. 扫描 plugins/ 目录 │ │ │ │ 解析 plugin.toml │ │ │ │ 加载 plugin.wasm │ │ │ │ 实例化 + 注册 hooks │ │ │ └────────────┬───────────┘ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 4. 启动文件监听器 │ │ │ │ 监控 plugins/ 目录 │ │ │ │ 检测 .wasm 文件变更 │ │ │ │ 触发热重载 │ │ │ └────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 7.4 Hook 调度实现骨架 ```rust impl PluginManager { /// 调度 Filter 类型 Hook(链式调用) pub async fn dispatch_filter Deserialize<'de>>( &self, hook: HookPoint, input: T, ) -> AppResult { let entries = self.hook_registry.get(&hook); if let Some(entries) = entries { // 按优先级排序 let mut sorted = entries.to_vec(); sorted.sort_by_key(|e| e.priority); let mut current = serde_json::to_value(input)?; for entry in sorted { let plugin = self.plugins.get(&entry.plugin_id) .ok_or_else(|| AppError::Internal(anyhow::anyhow!( "plugin {} not found", entry.plugin_id )))?; // 调用插件的 WASM 函数,带超时 current = tokio::time::timeout( Duration::from_millis(plugin.manifest.permissions.timeout_ms), plugin.call_filter(&entry.hook_fn, current), ).await .map_err(|_| AppError::Internal(anyhow::anyhow!( "plugin {} timed out on {:?}", entry.plugin_id, hook )))??; } Ok(serde_json::from_value(current)?) } else { Ok(input) } } /// 调度 Action 类型 Hook(顺序执行,忽略返回值) pub async fn dispatch_action( &self, hook: HookPoint, data: &impl Serialize, ) -> AppResult<()> { // 类似 dispatch_filter,但无链式传递 // ... } } ``` ### 7.5 与现有代码的集成点 在 `services/` 层注入 Hook 调用,Handler 层无需改动: ```rust // services/post.rs — create_post 中的 Hook 调用示例 pub async fn create_post( pool: &sqlx::SqlitePool, plugins: &PluginManager, author_id: &str, mut req: CreatePostRequest, ) -> AppResult { // ── Hook: on_post_creating ── req = plugins.dispatch_filter(HookPoint::PostCreating, req).await?; // ... 原有创建逻辑 ... let p = post::create(pool, &req.title, &slug, ...).await?; post::sync_tags(pool, &p.id, tag_ids).await?; // ── Hook: on_post_created ── let resp = build_post_response_from_id(pool, &p.id).await?; plugins.dispatch_action(HookPoint::PostCreated, &resp).await?; Ok(resp) } ``` ### 7.6 自定义路由 Hook 当 axum 路由未匹配时,fallback 到插件路由系统: ```rust // server/mod.rs — 添加 fallback 路由 let api_v1 = axum::Router::new() // ... 现有路由 ... .fallback(|State(state): State, req: Request| async move { // 尝试匹配插件注册的自定义路由 match state.plugins.dispatch_route(&req).await { Some(response) => response, None => (StatusCode::NOT_FOUND, "not found").into_response(), } }); ``` --- ## 8. 插件开发 (SDK 侧) ### 8.1 示例:SEO 优化插件 (Rust) ```rust // plugins-examples/seo-optimizer/src/lib.rs use raisfast_plugin_sdk::*; guest_bindgen!(); // wit-bindgen 生成宏 struct SeoOptimizer; impl Guest for SeoOptimizer { type PluginId = SeoOptimizerMetadata; fn on_post_creating(mut post: PostInput) -> PostInput { // 自动从内容提取 excerpt(如果未提供) if post.excerpt.is_none() { let plain = strip_markdown(&post.content); post.excerpt = Some(truncate(&plain, 200)); } post } fn filter_html(html: String) -> String { // 注入 Open Graph meta 标签 inject_og_tags(html) } } struct SeoOptimizerMetadata; impl GuestPluginId for SeoOptimizerMetadata { fn name() -> String { "SEO Optimizer".into() } fn version() -> String { "1.0.0".into() } fn description() -> String { "Auto-generate meta descriptions and OG tags".into() } } fn strip_markdown(md: &str) -> String { md.chars() .filter(|c| !matches!(c, '#' | '*' | '_' | '`' | '[' | ']' | '(' | ')')) .collect() } fn truncate(s: &str, max: usize) -> String { if s.len() <= max { s.to_string() } else { format!("{}...", &s[..max]) } } fn inject_og_tags(html: String) -> String { // 在 中注入 OG 标签 let og = r#""#; html.replacen("", &format!("{}", og), 1) } ``` ### 8.2 插件编译 ```bash # 编译 Rust 插件为 WASM 组件 cd plugins-examples/seo-optimizer cargo build --target wasm32-wasip1 --release # 或使用 wasm-component-builder wasm-tools component new target/wasm32-wasip1/release/seo_optimizer.wasm \ -o ../../plugins/seo-optimizer/plugin.wasm # AssemblyScript 插件 asc assembly/index.ts --outFile ../../plugins/my-plugin/plugin.wasm \ --use abort=wasi:cli/run ``` --- ## 9. 安全模型 ### 9.1 沙箱限制 ``` ┌───────────────────────────────────────────────────────┐ │ WASM 沙箱边界 │ │ │ │ ┌─────────────────────────────────────────────┐ │ │ │ 内存限制: 32MB (可配置) │ │ │ │ CPU 限制: 指令计数器 + timeout │ │ │ │ 文件系统: 无直接访问 │ │ │ │ 网络: 仅通过 host-http-get (白名单) │ │ │ │ 数据库: 仅通过 host-db-query (权限控制) │ │ │ │ 环境: 无法读取宿主环境变量 │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ 宿主控制的所有外部交互: │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │host_log() │ │host_http │ │host_db │ │ │ │ │ │ _get() │ │ _query() │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ ▼ ▼ │ │ 域名白名单 表权限检查 │ └───────────────────────────────────────────────────────┘ ``` ### 9.2 权限声明与执行 ```rust // 权限检查在 host function 实现中执行 fn host_http_get(mut cx: FunctionContext, url_ptr: u32, url_len: u32) -> u32 { let url = cx.read_string(url_ptr, url_len); let plugin_id = cx.data().current_plugin_id(); // 检查 URL 是否在白名单中 if !cx.data().is_url_allowed(plugin_id, &url) { return cx.write_error("URL not in whitelist"); } // 执行请求 match blocking_http_get(&url) { Ok(body) => cx.write_string(&body), Err(e) => cx.write_error(&e.to_string()), } } ``` ### 9.3 资源限制 | 资源 | 默认限制 | 可配置 | |------|---------|-------| | 内存 | 32 MB | `max_memory_mb` in manifest | | 单次 Hook 执行时间 | 5s | `timeout_ms` in manifest | | HTTP 请求大小 | 1 MB | 硬编码上限 | | 并发插件数 | 无限制 | 可在宿主配置限制 | | HTTP 外发请求频率 | 10/min/plugin | 运行时令牌桶 | --- ## 10. 配置集成 ### 10.1 环境变量新增 ```env # .env 新增 PLUGIN_DIR=./plugins # 插件目录 PLUGIN_HOT_RELOAD=true # 是否启用热加载 PLUGIN_MAX_MEMORY_MB=32 # 全局默认内存限制 PLUGIN_DEFAULT_TIMEOUT_MS=5000 # 全局默认超时 PLUGIN_DISABLED= # 禁用的插件列表(逗号分隔) ``` ### 10.2 AppConfig 扩展 ```rust pub struct AppConfig { // ... 现有字段 ... pub plugin_dir: String, pub plugin_hot_reload: bool, pub plugin_max_memory_mb: u32, pub plugin_default_timeout_ms: u64, pub plugin_disabled: Vec, } ``` --- ## 11. 热加载机制 ``` ┌──────────────────────────────────────────────────────────────┐ │ 热加载流程 │ │ │ │ 文件系统监听器 (notify crate) │ │ │ │ │ │ 检测到 plugins/xxx/plugin.wasm 变更 │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 1. 读取 plugin.toml │ │ │ │ 验证清单完整性 │ │ │ └────────────┬───────────┘ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 2. 卸载旧版本插件 │ │ │ │ - 从 hook_registry │ │ │ │ 移除所有 Hook 条目│ │ │ │ - Drop 旧 Store │ │ │ └────────────┬───────────┘ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 3. 加载新版本 │ │ │ │ - 编译为新 Instance │ │ │ │ - 注册所有 Hook │ │ │ │ - 验证权限声明 │ │ │ └────────────┬───────────┘ │ │ ▼ │ │ ┌────────────────────────┐ │ │ │ 4. 打印加载日志 │ │ │ │ tracing::info!( │ │ │ │ "reloaded plugin" │ │ │ │ ) │ │ │ └────────────────────────┘ │ │ │ │ 注意:热加载期间正在执行的 Hook 会完成后再切换 │ │ 使用 Arc> 保证并发安全 │ └──────────────────────────────────────────────────────────────┘ ``` --- ## 12. 实施计划 ### Phase 1:基础设施 (3-4 天) - [ ] 添加 wasmtime + wit-bindgen 依赖 - [ ] 定义 `wit/plugin.wit` 接口文件 - [ ] 实现 `PluginManifest` 解析 (plugin.toml) - [ ] 实现 `PluginManager` 基本骨架(加载/卸载/实例化) - [ ] 编写 `plugins-sdk` crate 骨架 - [ ] 单元测试:插件加载/卸载 ### Phase 2:Hook 系统 (3-4 天) - [ ] 实现 Hook 注册表和优先级排序 - [ ] 实现 `dispatch_filter` / `dispatch_action` 调度器 - [ ] 注册 Host Functions(log / config) - [ ] 在 `services/post.rs` 注入 `on_post_creating` / `on_post_created` - [ ] 在 `services/comment.rs` 注入 `on_comment_creating` - [ ] 编写示例插件 `seo-optimizer` 作为端到端验证 ### Phase 3:Host API + 权限 (3-4 天) - [ ] 实现 `host_http_get`(带域名白名单) - [ ] 实现 `host_get_config`(带 key 前缀过滤) - [ ] 实现指令计数器 + timeout 机制 - [ ] 实现内存限制 - [ ] 编写 `content-filter` 示例插件(调用 host API) ### Phase 4:高级功能 (3-4 天) - [ ] 实现 `render_markdown` Hook(可替换 Markdown 渲染器) - [ ] 实现 `handle_route` 自定义路由 - [ ] 实现热加载(notify 文件监听) - [ ] AppConfig 扩展(plugin 配置项) - [ ] 性能基准测试 ### Phase 5:文档与生态 (2-3 天) - [ ] 编写插件开发指南(中文) - [ ] 提供更多示例插件模板 - [ ] 集成测试覆盖所有 Hook 点 - [ ] CI 中添加 WASM 目标构建 --- ## 13. 性能考量 | 操作 | 预期开销 | 说明 | |------|---------|------| | 首次加载插件 | ~10-50ms | Cranelift JIT 编译 | | 调用 Hook(无插件注册) | <1μs | HashMap 查找,空列表直接返回 | | 调用 Hook(有插件) | ~10-100μs | WASM → Host 上下文切换 + 执行 | | 热加载 | ~50-100ms | 编译 + 替换实例 | | 内存(每插件) | 1-32MB | WASM 线性内存 | 优化策略: - 无插件注册的 Hook 点零开销(编译期内联检查) - 使用 `wasmtime::component::InstancePre` 预编译,加速热加载 - 高频 Hook(如 `render_markdown`)可缓存结果 - 插件调用使用 `tokio::task::spawn_blocking` 避免阻塞异步运行时 --- ## 14. 总结 ``` 宿主 (raisfast) 插件 (.wasm) ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ │ │ Handler → Service │ Hook │ 自定义逻辑 │ │ ↓ │───────►│ (任意 WASM 语言) │ │ PluginManager │◄───────│ │ │ ↓ │ Host │ 可调用: │ │ Model / DB │ API │ log / config / http│ │ │ │ │ └─────────────────────┘ └─────────────────────┘ │ ▲ │ wasmtime (沙箱) │ └──────────────────────────────┘ 关键特性: ✅ 沙箱隔离 — 插件无法访问宿主资源 ✅ 热加载 — 替换 .wasm 文件即时生效 ✅ 类型安全 — WIT 定义强类型接口 ✅ 多语言 — Rust / AS / Go / C ✅ 零侵入 — 插件不存在时走默认逻辑 ```