--- title: WASM 插件系统 updated: 2026-04-23 status: stable tags: [wasm, plugin, wasmtime, wit] --- # WASM 插件系统 > 从 [[index]] 导航。关联: [[erp-core]] [[architecture]] [[erp-server]] ## 1. 设计决策 ### 为什么选 WASM 而非 Lua / gRPC / dylib? | 方案 | 安全性 | 隔离性 | 性能 | 复杂度 | |------|--------|--------|------|--------| | **WASM** | 高(沙箱) | 进程内隔离 | 接近原生 | 中 | | Lua 脚本 | 中 | 无隔离 | 快 | 低 | | 进程外 gRPC | 高 | 进程级隔离 | 网络开销 | 高 | | dylib | 低 | 无隔离 | 原生 | 低 | 核心权衡:WASM 在安全和性能间取得最佳平衡。Wasmtime v43 Component Model 提供类型安全的 Host-Plugin 接口,Fuel 防止无限循环。 ### 架构拓扑 ``` ┌─────────────────────────────────────────────┐ │ erp-server │ │ ┌───────────┐ ┌────────────────────────┐ │ │ │ EventBus │ │ PluginRuntime(Wasmtime) │ │ │ │(broadcast)│ │ ┌─────┐ ┌─────┐ │ │ │ └─────┬─────┘ │ │CRM │ │库存 │ │ │ │ │ │ └──┬──┘ └──┬──┘ │ │ │ │ │ Host Bridge(自动注入 │ │ │ │ │ tenant_id+权限检查) │ │ │ │ └─────┼──────────────────┘ │ │ ┌─────┴──────┐ │ │ │ DB(SeaORM) │ │ │ └────────────┘ │ └─────────────────────────────────────────────┘ ``` ### 原型验证 (V1-V6) 全部通过:WIT+bindgen 编译、Host 调用插件、插件回调 Host API、async 实例化、Fuel 限制、动态加载。 ## 2. 关键文件 + 数据流 ### 核心文件 | 文件 | 职责 | |------|------| | `crates/erp-plugin-prototype/wit/plugin.wit` | WIT 接口定义(Host API + Plugin API) | | `crates/erp-plugin-prototype/src/lib.rs` | Host 运行时:Engine/Store/Linker/HostState | | `crates/erp-plugin-prototype/tests/test_plugin_integration.rs` | V1-V6 集成测试 | ### WIT 接口概要 Host 暴露给插件(`host-api`):`db_insert` `db_query` `db_update` `db_delete` `event_publish` `config_get` `log_write` `current_user` `check_permission` 插件导出给 Host(`plugin-api`):`init` `on_tenant_created` `handle_event` ### 集成契约 | 方向 | 模块 | 接口 | 触发时机 | |------|------|------|---------| | 被调用 ← | [[erp-server]] | `PluginEngine` | 服务启动时恢复插件 | | 调用 → | [[erp-core]] | `EventBus` | 桥接领域事件到插件 | | 提供 → | 所有插件 | Host API (9 函数) | 插件运行时 | ## 3. 代码逻辑 ### 插件生命周期 ``` 安装(manifest+WASM) → 注册权限 → 实例化(Wasmtime) → init() → 日常: db_query/db_insert 通过 data_handler 自动注入 tenant_id + 权限校验 → 事件: EventBus → handle_event() → 升级: upload 新 WASM → 增量 DDL → 卸载旧实例 → 加载新实例 ``` ### 权限系统 权限码由 `data_handler.rs` 的 `compute_permission_code()` 自动生成: ``` {manifest_id}.{url_entity_name}.{action_suffix} 例: erp-crm.customer.list / erp-crm.customer.manage ``` ⚡ **权限命名铁律**: `plugin.toml` 中 `permissions[].code` 前缀必须与 `schema.entities[].name` 完全一致,每个实体必须声明 `.list` + `.manage` ⚡ **Host API 数据约定**: 所有数据参数用 `list` + JSON 序列化,Host 自动注入 id/tenant_id/timestamp ⚡ **同步调用**: bindgen 生成的 `call_init`/`call_handle_event` 是同步的,只有实例化可以 async ### 不变量 ⚡ Fuel 默认 100 万,耗尽时 WASM trap(`wasm trap: interrupt`) ⚡ `HasSelf` 是 Linker 注册的必要类型参数(`Data<'a> = &'a mut HostState`) ⚡ Core WASM 必须通过 `wasm-tools component new` 转为 Component 格式才能被 Host 加载 ## 4. 活跃问题 + 陷阱 ### 历史教训 - CRM 权限码 `tag.manage` vs 实体 `customer_tag` → 三个页面 403(迁移 m000038 修复) - CRM WASM 二进制错误存储了测试插件而非 CRM 插件(重新编译修复) - 权限未自动分配给 admin 角色 → 403(添加 `grant_permissions_to_admin()`) ### 注意事项 ⚠️ 插件 API 路由用 `Path<(Uuid, String)>` 解析 plugin_id,必须用数据库 UUID 而非 manifest_id ⚠️ 修改 WIT 后需重编译 Host crate 和所有插件 ## 5. 变更记录 | 日期 | 变更 | |------|------| | 2026-04-23 | 重构为 5 节结构,插件制作流程移至 `.claude/skills/plugin-development/` | | 2026-04-19 | CRM 权限码修复 (m000038) | | 2026-04-18 | 插件权限系统审计 | > **插件制作完整流程**: 详见 `.claude/skills/plugin-development/SKILL.md`(WIT 接口 → 创建 crate → 编译 WASM → 集成测试)