Files
raisfast/docs/plugin-system.md
T

34 KiB
Raw Blame History

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,作为宿主和插件之间的契约:

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<string>;

    /// 发起 HTTP GET 请求(受白名单限制)。
    http-get: func(url: string) -> result<string, string>;

    /// 获取文章内容(只读)。
    get-post: func(slug: string) -> option<string>;

    /// 获取当前请求的上下文信息。
    get-request-context: func() -> option<request-context>;
}

/// 请求上下文,传递给插件的当前 HTTP 请求信息。
resource request-context {
    method: func() -> string;
    path: func() -> string;
    client-ip: func() -> string;
    user-id: func() -> option<string>;
    user-role: func() -> option<string>;
}

/// 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<comment-input, string>;
    export on-comment-created: func(comment-id: string) -> void;

    /// 内容渲染钩子
    export render-markdown: func(content: string) -> option<string>;
    export filter-html: func(html: string) -> string;

    /// 自定义路由钩子
    export handle-route: func(path: string, method: string, body: option<string>) -> option<route-response>;

    /// 认证钩子
    export on-login: func(email: string, success: bool) -> void;
}

3.2 数据类型

record post-input {
    title: string,
    content: string,
    slug: option<string>,
    excerpt: option<string>,
    category-id: option<string>,
    tag-ids: list<string>,
    status: string,
}

record post-output {
    id: string,
    title: string,
    slug: string,
    content: string,
    excerpt: option<string>,
    status: string,
    author-id: string,
    category-id: option<string>,
    view-count: u64,
    created-at: string,
    updated-at: string,
    published-at: option<string>,
}

record comment-input {
    content: string,
    nickname: option<string>,
    email: option<string>,
    parent-id: option<string>,
}

record route-response {
    status: u16,
    headers: list<tuple<string, string>>,
    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 清单文件:

[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 依赖

# Cargo.toml 新增
[dependencies]
wasmtime = "28"
wasmtime-wasi = "28"
wit-bindgen = "0.36"
toml = "0.8"
notify = { version = "7", features = ["macos_kqueue"] }  # 文件监听

7.2 核心数据结构

// src/plugins/mod.rs

/// 插件系统核心管理器
pub struct PluginManager {
    engine: wasmtime::Engine,
    linker: wasmtime::Linker<PluginState>,
    plugins: HashMap<String, LoadedPlugin>,
    hook_registry: HashMap<HookPoint, Vec<HookEntry>>,
    watcher: Option<notify::RecommendedWatcher>,
}

struct LoadedPlugin {
    id: String,
    manifest: PluginManifest,
    instance: wasmtime::component::Instance,
    store: wasmtime::Store<PluginState>,
    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<AppConfig>,
    pool: SqlitePool,
    request_ctx: Option<RequestContext>,
}

#[derive(Debug, Clone)]
struct PluginManifest {
    id: String,
    name: String,
    version: String,
    description: String,
    language: String,
    permissions: Permissions,
    hooks: HashMap<String, HookConfig>,
}

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 调度实现骨架

impl PluginManager {
    /// 调度 Filter 类型 Hook(链式调用)
    pub async fn dispatch_filter<T: Clone + Serialize + for<'de> Deserialize<'de>>(
        &self,
        hook: HookPoint,
        input: T,
    ) -> AppResult<T> {
        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 层无需改动:

// services/post.rs — create_post 中的 Hook 调用示例

pub async fn create_post(
    pool: &sqlx::SqlitePool,
    plugins: &PluginManager,
    author_id: &str,
    mut req: CreatePostRequest,
) -> AppResult<PostResponse> {
    // ── 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 到插件路由系统:

// server/mod.rs — 添加 fallback 路由

let api_v1 = axum::Router::new()
    // ... 现有路由 ...
    .fallback(|State(state): State<AppState>, 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)

// 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 {
    // 在 <head> 中注入 OG 标签
    let og = r#"<meta property="og:type" content="article">"#;
    html.replacen("<head>", &format!("<head>{}", og), 1)
}

8.2 插件编译

# 编译 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 权限声明与执行

// 权限检查在 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 新增
PLUGIN_DIR=./plugins              # 插件目录
PLUGIN_HOT_RELOAD=true            # 是否启用热加载
PLUGIN_MAX_MEMORY_MB=32           # 全局默认内存限制
PLUGIN_DEFAULT_TIMEOUT_MS=5000    # 全局默认超时
PLUGIN_DISABLED=                  # 禁用的插件列表(逗号分隔)

10.2 AppConfig 扩展

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<String>,
}

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<RwLock<PluginManager>> 保证并发安全                 │
└──────────────────────────────────────────────────────────────┘

12. 实施计划

Phase 1:基础设施 (3-4 天)

  • 添加 wasmtime + wit-bindgen 依赖
  • 定义 wit/plugin.wit 接口文件
  • 实现 PluginManifest 解析 (plugin.toml)
  • 实现 PluginManager 基本骨架(加载/卸载/实例化)
  • 编写 plugins-sdk crate 骨架
  • 单元测试:插件加载/卸载

Phase 2Hook 系统 (3-4 天)

  • 实现 Hook 注册表和优先级排序
  • 实现 dispatch_filter / dispatch_action 调度器
  • 注册 Host Functionslog / config
  • services/post.rs 注入 on_post_creating / on_post_created
  • services/comment.rs 注入 on_comment_creating
  • 编写示例插件 seo-optimizer 作为端到端验证

Phase 3Host 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
  ✅ 零侵入 — 插件不存在时走默认逻辑