feat(plugin): 集成 WASM 插件系统到主服务并修复链路问题
- 新增 erp-plugin crate:插件管理、WASM 运行时、动态表、数据 CRUD - 新增前端插件管理页面(PluginAdmin/PluginCRUDPage)和 API 层 - 新增插件数据迁移(plugins/plugin_entities/plugin_event_subscriptions) - 新增权限补充迁移(为已有租户补充 plugin.admin/plugin.list 权限) - 修复 PluginAdmin 页面 InstallOutlined 图标不存在的崩溃问题 - 修复 settings 唯一索引迁移顺序错误(先去重再建索引) - 更新 wiki 和 CLAUDE.md 反映插件系统集成状态 - 新增 dev.ps1 一键启动脚本
This commit is contained in:
170
crates/erp-plugin/src/host.rs
Normal file
170
crates/erp-plugin/src/host.rs
Normal file
@@ -0,0 +1,170 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use uuid::Uuid;
|
||||
use wasmtime::StoreLimits;
|
||||
|
||||
use crate::erp::plugin::host_api;
|
||||
|
||||
/// 待刷新的写操作
|
||||
#[derive(Debug)]
|
||||
pub enum PendingOp {
|
||||
Insert {
|
||||
id: String,
|
||||
entity: String,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
Update {
|
||||
entity: String,
|
||||
id: String,
|
||||
data: Vec<u8>,
|
||||
version: i64,
|
||||
},
|
||||
Delete {
|
||||
entity: String,
|
||||
id: String,
|
||||
},
|
||||
PublishEvent {
|
||||
event_type: String,
|
||||
payload: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Host 端状态 — 绑定到每个 WASM Store 实例
|
||||
///
|
||||
/// 采用延迟执行模式:
|
||||
/// - 读操作 (db_query, config_get, current_user) → 调用前预填充
|
||||
/// - 写操作 (db_insert, db_update, db_delete, event_publish) → 入队 pending_ops
|
||||
/// - WASM 调用结束后由 engine 刷新 pending_ops 执行真实 DB 操作
|
||||
pub struct HostState {
|
||||
pub(crate) limits: StoreLimits,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) tenant_id: Uuid,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) user_id: Uuid,
|
||||
pub(crate) permissions: Vec<String>,
|
||||
pub(crate) plugin_id: String,
|
||||
// 预填充的读取缓存
|
||||
pub(crate) query_results: HashMap<String, Vec<u8>>,
|
||||
pub(crate) config_cache: HashMap<String, Vec<u8>>,
|
||||
pub(crate) current_user_json: Vec<u8>,
|
||||
// 待刷新的写操作
|
||||
pub(crate) pending_ops: Vec<PendingOp>,
|
||||
// 日志
|
||||
pub(crate) logs: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl HostState {
|
||||
pub fn new(
|
||||
plugin_id: String,
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
permissions: Vec<String>,
|
||||
) -> Self {
|
||||
let current_user = serde_json::json!({
|
||||
"id": user_id.to_string(),
|
||||
"tenant_id": tenant_id.to_string(),
|
||||
});
|
||||
Self {
|
||||
limits: wasmtime::StoreLimitsBuilder::new().build(),
|
||||
tenant_id,
|
||||
user_id,
|
||||
permissions,
|
||||
plugin_id,
|
||||
query_results: HashMap::new(),
|
||||
config_cache: HashMap::new(),
|
||||
current_user_json: serde_json::to_vec(¤t_user).unwrap_or_default(),
|
||||
pending_ops: Vec::new(),
|
||||
logs: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 实现 bindgen 生成的 Host trait — 插件调用 Host API 的入口
|
||||
impl host_api::Host for HostState {
|
||||
fn db_insert(&mut self, entity: String, data: Vec<u8>) -> Result<Vec<u8>, String> {
|
||||
let id = Uuid::now_v7().to_string();
|
||||
let response = serde_json::json!({
|
||||
"id": id,
|
||||
"entity": entity,
|
||||
"status": "queued",
|
||||
});
|
||||
self.pending_ops.push(PendingOp::Insert {
|
||||
id: id.clone(),
|
||||
entity,
|
||||
data,
|
||||
});
|
||||
serde_json::to_vec(&response).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn db_query(
|
||||
&mut self,
|
||||
entity: String,
|
||||
_filter: Vec<u8>,
|
||||
_pagination: Vec<u8>,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
self.query_results
|
||||
.get(&entity)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("实体 '{}' 的查询结果未预填充", entity))
|
||||
}
|
||||
|
||||
fn db_update(
|
||||
&mut self,
|
||||
entity: String,
|
||||
id: String,
|
||||
data: Vec<u8>,
|
||||
version: i64,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let response = serde_json::json!({
|
||||
"id": id,
|
||||
"entity": entity,
|
||||
"version": version + 1,
|
||||
"status": "queued",
|
||||
});
|
||||
self.pending_ops.push(PendingOp::Update {
|
||||
entity,
|
||||
id,
|
||||
data,
|
||||
version,
|
||||
});
|
||||
serde_json::to_vec(&response).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn db_delete(&mut self, entity: String, id: String) -> Result<(), String> {
|
||||
self.pending_ops.push(PendingOp::Delete { entity, id });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn event_publish(&mut self, event_type: String, payload: Vec<u8>) -> Result<(), String> {
|
||||
self.pending_ops.push(PendingOp::PublishEvent {
|
||||
event_type,
|
||||
payload,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn config_get(&mut self, key: String) -> Result<Vec<u8>, String> {
|
||||
self.config_cache
|
||||
.get(&key)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("配置项 '{}' 未预填充", key))
|
||||
}
|
||||
|
||||
fn log_write(&mut self, level: String, message: String) {
|
||||
tracing::info!(
|
||||
plugin = %self.plugin_id,
|
||||
level = %level,
|
||||
"Plugin log: {}",
|
||||
message
|
||||
);
|
||||
self.logs.push((level, message));
|
||||
}
|
||||
|
||||
fn current_user(&mut self) -> Result<Vec<u8>, String> {
|
||||
Ok(self.current_user_json.clone())
|
||||
}
|
||||
|
||||
fn check_permission(&mut self, permission: String) -> Result<bool, String> {
|
||||
Ok(self.permissions.contains(&permission))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user