新建 erp-health 原生 Rust crate,覆盖设计规格中定义的 5 大业务域: - 16 个 SeaORM Entity(患者/家属/标签/医生/健康档案/体征/化验单/预约/排班/随访/咨询等) - 16 表数据库迁移(含索引、外键、默认值、可回滚) - 40+ API 路由骨架(患者管理/健康数据/预约排班/随访/咨询/医生管理) - 12 个权限声明(health.patient/health-data/appointment/follow-up/consultation/doctor 各 .list/.manage) - DTO / Service / Handler / Event 四层架构,Service 使用 todo!() 占位 - erp-server 集成:模块注册 + AppState FromRef 桥接 + 路由挂载 同步更新 CLAUDE.md 项目进度、wiki 知识库、设计规格文档。
5.2 KiB
title, updated, status, tags
| title | updated | status | tags | ||||
|---|---|---|---|---|---|---|---|
| WASM 插件系统 | 2026-04-23 | stable |
|
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<u8> + JSON 序列化,Host 自动注入 id/tenant_id/timestamp
⚡ 同步调用: bindgen 生成的 call_init/call_handle_event 是同步的,只有实例化可以 async
不变量
⚡ Fuel 默认 100 万,耗尽时 WASM trap(wasm trap: interrupt)
⚡ HasSelf<HostState> 是 Linker 注册的必要类型参数(Data<'a> = &'a mut HostState)
⚡ Core WASM 必须通过 wasm-tools component new 转为 Component 格式才能被 Host 加载
4. 活跃问题 + 陷阱
历史教训
- CRM 权限码
tag.managevs 实体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 → 集成测试)