feat(plugin): P1 跨插件数据引用系统 — 后端 Phase 1-3
实现跨插件实体引用的基础后端能力:
Phase 1 — Manifest 扩展 + Entity Registry 数据层:
- PluginField 新增 ref_plugin/ref_fallback_label 支持跨插件引用声明
- PluginRelation 新增 name/relation_type/display_field(CRM 已在用的字段)
- PluginEntity 新增 is_public 标记可被其他插件引用的实体
- 数据库迁移:plugin_entities 新增 manifest_id + is_public 列 + 索引
- SeaORM Entity 和 install 流程同步更新
Phase 2 — 后端跨插件引用解析 + 校验:
- data_service: 新增 resolve_cross_plugin_entity/is_plugin_active 函数
- validate_ref_entities: 支持 ref_plugin 字段,目标插件未安装时跳过校验(软警告)
- host.rs: HostState 新增 cross_plugin_entities 映射,db_query 支持点分记号
- engine.rs: execute_wasm 自动构建跨插件实体映射
Phase 3 — API 端点:
- POST /plugins/{id}/{entity}/resolve-labels 批量标签解析
- GET /plugin-registry/entities 公开实体注册表查询
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use sea_orm::{ConnectionTrait, DatabaseConnection, Statement, TransactionTrait};
|
||||
use sea_orm::{ColumnTrait, ConnectionTrait, DatabaseConnection, EntityTrait, QueryFilter, Statement, TransactionTrait};
|
||||
use serde_json::json;
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
@@ -315,6 +316,18 @@ impl PluginEngine {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 将插件从一个 key 重命名为另一个 key(用于热更新的原子替换)
|
||||
pub async fn rename_plugin(&self, old_id: &str, new_id: &str) -> PluginResult<()> {
|
||||
let (_, loaded) = self.plugins.remove(old_id)
|
||||
.ok_or_else(|| PluginError::NotFound(old_id.to_string()))?;
|
||||
let mut loaded = Arc::try_unwrap(loaded)
|
||||
.map_err(|_| PluginError::ExecutionError("插件仍被引用,无法重命名".to_string()))?;
|
||||
loaded.id = new_id.to_string();
|
||||
self.plugins.insert(new_id.to_string(), Arc::new(loaded));
|
||||
tracing::info!(old_id, new_id, "Plugin renamed");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 健康检查
|
||||
pub async fn health_check(&self, plugin_id: &str) -> PluginResult<serde_json::Value> {
|
||||
let loaded = self.get_loaded(plugin_id)?;
|
||||
@@ -456,13 +469,20 @@ impl PluginEngine {
|
||||
{
|
||||
let loaded = self.get_loaded(plugin_id)?;
|
||||
|
||||
// 构建跨插件实体映射(从 manifest 的 ref_plugin 字段提取)
|
||||
let cross_plugin_entities = Self::build_cross_plugin_map(&loaded.manifest, &self.db, exec_ctx.tenant_id).await;
|
||||
|
||||
// 创建新的 Store + HostState,使用真实的租户/用户上下文
|
||||
let state = HostState::new(
|
||||
// 传入 db 和 event_bus 启用混合执行模式(插件可自主查询数据)
|
||||
let mut state = HostState::new_with_db(
|
||||
plugin_id.to_string(),
|
||||
exec_ctx.tenant_id,
|
||||
exec_ctx.user_id,
|
||||
exec_ctx.permissions.clone(),
|
||||
self.db.clone(),
|
||||
self.event_bus.clone(),
|
||||
);
|
||||
state.cross_plugin_entities = cross_plugin_entities;
|
||||
let mut store = Store::new(&self.engine, state);
|
||||
store
|
||||
.set_fuel(self.config.default_fuel)
|
||||
@@ -521,6 +541,42 @@ impl PluginEngine {
|
||||
result
|
||||
}
|
||||
|
||||
/// 从 manifest 的 ref_plugin 字段构建跨插件实体映射
|
||||
/// 返回: { "erp-crm.customer" → "plugin_erp_crm__customer", ... }
|
||||
async fn build_cross_plugin_map(
|
||||
manifest: &crate::manifest::PluginManifest,
|
||||
db: &DatabaseConnection,
|
||||
tenant_id: Uuid,
|
||||
) -> HashMap<String, String> {
|
||||
let mut map = HashMap::new();
|
||||
let Some(schema) = &manifest.schema else { return map };
|
||||
|
||||
for entity in &schema.entities {
|
||||
for field in &entity.fields {
|
||||
if let (Some(target_plugin), Some(ref_entity)) = (&field.ref_plugin, &field.ref_entity) {
|
||||
let key = format!("{}.{}", target_plugin, ref_entity);
|
||||
// 从 plugin_entities 表查找目标表名
|
||||
let table_name = crate::entity::plugin_entity::Entity::find()
|
||||
.filter(crate::entity::plugin_entity::Column::ManifestId.eq(target_plugin.as_str()))
|
||||
.filter(crate::entity::plugin_entity::Column::EntityName.eq(ref_entity.as_str()))
|
||||
.filter(crate::entity::plugin_entity::Column::TenantId.eq(tenant_id))
|
||||
.filter(crate::entity::plugin_entity::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|e| e.table_name);
|
||||
|
||||
if let Some(tn) = table_name {
|
||||
map.insert(key, tn);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// 刷新 HostState 中的 pending_ops 到数据库。
|
||||
///
|
||||
/// 使用事务包裹所有数据库操作确保原子性。
|
||||
|
||||
Reference in New Issue
Block a user