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:
@@ -177,7 +177,7 @@ use tracing_subscriber::EnvFilter;
|
||||
use utoipa::OpenApi;
|
||||
|
||||
use erp_core::events::EventBus;
|
||||
use erp_core::module::{ErpModule, ModuleRegistry};
|
||||
use erp_core::module::{ErpModule, ModuleContext, ModuleRegistry};
|
||||
use erp_server_migration::MigratorTrait;
|
||||
use sea_orm::{ConnectionTrait, FromQueryResult};
|
||||
|
||||
@@ -310,9 +310,40 @@ async fn main() -> anyhow::Result<()> {
|
||||
"Modules registered"
|
||||
);
|
||||
|
||||
// Initialize plugin engine
|
||||
let plugin_config = erp_plugin::engine::PluginEngineConfig::default();
|
||||
let plugin_engine = erp_plugin::engine::PluginEngine::new(
|
||||
db.clone(),
|
||||
event_bus.clone(),
|
||||
plugin_config,
|
||||
)?;
|
||||
tracing::info!("Plugin engine initialized");
|
||||
|
||||
// Register plugin module
|
||||
let plugin_module = erp_plugin::module::PluginModule;
|
||||
let registry = registry.register(plugin_module);
|
||||
|
||||
// Register event handlers
|
||||
registry.register_handlers(&event_bus);
|
||||
|
||||
// Startup all modules (按拓扑顺序调用 on_startup)
|
||||
let module_ctx = ModuleContext {
|
||||
db: db.clone(),
|
||||
event_bus: event_bus.clone(),
|
||||
};
|
||||
registry.startup_all(&module_ctx).await?;
|
||||
tracing::info!("All modules started");
|
||||
|
||||
// 恢复运行中的插件(服务器重启后自动重新加载)
|
||||
match plugin_engine.recover_plugins(&db).await {
|
||||
Ok(recovered) => {
|
||||
tracing::info!(count = recovered.len(), "Plugins recovered");
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(error = %e, "Failed to recover plugins");
|
||||
}
|
||||
}
|
||||
|
||||
// Start message event listener (workflow events → message notifications)
|
||||
erp_message::MessageModule::start_event_listener(db.clone(), event_bus.clone());
|
||||
tracing::info!("Message event listener started");
|
||||
@@ -339,6 +370,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
module_registry: registry,
|
||||
redis: redis_client.clone(),
|
||||
default_tenant_id,
|
||||
plugin_engine,
|
||||
};
|
||||
|
||||
// --- Build the router ---
|
||||
@@ -370,6 +402,7 @@ async fn main() -> anyhow::Result<()> {
|
||||
.merge(erp_config::ConfigModule::protected_routes())
|
||||
.merge(erp_workflow::WorkflowModule::protected_routes())
|
||||
.merge(erp_message::MessageModule::protected_routes())
|
||||
.merge(erp_plugin::module::PluginModule::protected_routes())
|
||||
.merge(handlers::audit_log::audit_log_router())
|
||||
.layer(axum::middleware::from_fn_with_state(
|
||||
state.clone(),
|
||||
@@ -397,6 +430,8 @@ async fn main() -> anyhow::Result<()> {
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await?;
|
||||
|
||||
// 优雅关闭所有模块(按拓扑逆序)
|
||||
state.module_registry.shutdown_all().await?;
|
||||
tracing::info!("Server shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ pub struct AppState {
|
||||
pub redis: redis::Client,
|
||||
/// 实际的默认租户 ID,从数据库种子数据中获取。
|
||||
pub default_tenant_id: uuid::Uuid,
|
||||
/// 插件引擎
|
||||
pub plugin_engine: erp_plugin::engine::PluginEngine,
|
||||
}
|
||||
|
||||
/// Allow handlers to extract `DatabaseConnection` directly from `State<AppState>`.
|
||||
@@ -80,3 +82,14 @@ impl FromRef<AppState> for erp_message::MessageState {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow erp-plugin handlers to extract their required state.
|
||||
impl FromRef<AppState> for erp_plugin::state::PluginState {
|
||||
fn from_ref(state: &AppState) -> Self {
|
||||
Self {
|
||||
db: state.db.clone(),
|
||||
event_bus: state.event_bus.clone(),
|
||||
engine: state.plugin_engine.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user