Files
raisfast/docs/plugin-system.md
T

843 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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<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 数据类型
```wit
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` 清单文件:
```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<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 调度实现骨架
```rust
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 层无需改动:
```rust
// 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 到插件路由系统:
```rust
// 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)
```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 插件编译
```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<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 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
✅ 零侵入 — 插件不存在时走默认逻辑
```