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:
@@ -1,7 +1,7 @@
|
||||
use chrono::Utc;
|
||||
use sea_orm::{ActiveModelTrait, Set};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::sync::{broadcast, mpsc};
|
||||
use tracing::{error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -31,6 +31,32 @@ impl DomainEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// 过滤事件接收器 — 只接收匹配 `event_type_prefix` 的事件
|
||||
pub struct FilteredEventReceiver {
|
||||
receiver: mpsc::Receiver<DomainEvent>,
|
||||
}
|
||||
|
||||
impl FilteredEventReceiver {
|
||||
/// 接收下一个匹配的事件
|
||||
pub async fn recv(&mut self) -> Option<DomainEvent> {
|
||||
self.receiver.recv().await
|
||||
}
|
||||
}
|
||||
|
||||
/// 订阅句柄 — 用于取消过滤订阅
|
||||
pub struct SubscriptionHandle {
|
||||
cancel_tx: mpsc::Sender<()>,
|
||||
join_handle: tokio::task::JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl SubscriptionHandle {
|
||||
/// 取消订阅并等待后台任务结束
|
||||
pub async fn cancel(self) {
|
||||
let _ = self.cancel_tx.send(()).await;
|
||||
let _ = self.join_handle.await;
|
||||
}
|
||||
}
|
||||
|
||||
/// 进程内事件总线
|
||||
#[derive(Clone)]
|
||||
pub struct EventBus {
|
||||
@@ -84,4 +110,57 @@ impl EventBus {
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<DomainEvent> {
|
||||
self.sender.subscribe()
|
||||
}
|
||||
|
||||
/// 按事件类型前缀过滤订阅。
|
||||
///
|
||||
/// 为每次调用 spawn 一个 Tokio task 从 broadcast channel 读取,
|
||||
/// 只转发匹配 `event_type_prefix` 的事件到 mpsc channel(capacity 256)。
|
||||
pub fn subscribe_filtered(
|
||||
&self,
|
||||
event_type_prefix: String,
|
||||
) -> (FilteredEventReceiver, SubscriptionHandle) {
|
||||
let mut broadcast_rx = self.sender.subscribe();
|
||||
let (mpsc_tx, mpsc_rx) = mpsc::channel(256);
|
||||
let (cancel_tx, mut cancel_rx) = mpsc::channel::<()>(1);
|
||||
|
||||
let prefix = event_type_prefix.clone();
|
||||
let join_handle = tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = cancel_rx.recv() => {
|
||||
tracing::info!(prefix = %prefix, "Filtered subscription cancelled");
|
||||
break;
|
||||
}
|
||||
event = broadcast_rx.recv() => {
|
||||
match event {
|
||||
Ok(event) => {
|
||||
if event.event_type.starts_with(&prefix) {
|
||||
if mpsc_tx.send(event).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(broadcast::error::RecvError::Lagged(n)) => {
|
||||
tracing::warn!(prefix = %prefix, lagged = n, "Filtered subscriber lagged");
|
||||
}
|
||||
Err(broadcast::error::RecvError::Closed) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tracing::info!(prefix = %event_type_prefix, "Filtered subscription created");
|
||||
|
||||
(
|
||||
FilteredEventReceiver { receiver: mpsc_rx },
|
||||
SubscriptionHandle {
|
||||
cancel_tx,
|
||||
join_handle,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user