fix(plugin): P1 跨插件引用修复 — DateTime generated column + resolve-labels UUID 类型 + EntitySelect manifest→UUID 映射
Some checks failed
CI / rust-check (push) Has been cancelled
CI / frontend-build (push) Has been cancelled
CI / security-audit (push) Has been cancelled
CI / rust-test (push) Has been cancelled

- manifest.rs: DateTime 类型 generated column 改为 TEXT 存储(PostgreSQL TIMESTAMPTZ cast 非 immutable)
- data_handler.rs: resolve-labels 查询参数从 String 改为 UUID 类型避免类型不匹配
- data_dto.rs: PublicEntityResp 新增 plugin_id 字段
- EntitySelect.tsx: 跨插件查询先通过 registry 解析 manifest_id→plugin UUID
- pluginData.ts: PublicEntity 接口增加 plugin_id
- plugin_tests.rs: 适配 PluginField/PluginEntity 新增字段
This commit is contained in:
iven
2026-04-19 08:44:45 +08:00
parent 08252c10f1
commit 0ee9d22634
8 changed files with 160 additions and 26 deletions

View File

@@ -117,6 +117,8 @@ impl PluginService {
entity_name: Set(entity_def.name.clone()),
table_name: Set(table_name.clone()),
schema_json: Set(serde_json::to_value(entity_def).unwrap_or_default()),
manifest_id: Set(manifest.metadata.id.clone()),
is_public: Set(entity_def.is_public.unwrap_or(false)),
created_at: Set(now),
updated_at: Set(now),
created_by: Set(Some(operator_id)),
@@ -166,6 +168,10 @@ impl PluginService {
})?;
}
// 将插件权限自动分配给 admin 角色
tracing::info!("Granting plugin permissions to admin role");
grant_permissions_to_admin(db, tenant_id, &manifest.metadata.id).await?;
// 加载到内存
tracing::info!(manifest_id = %manifest.metadata.id, "Loading plugin into engine");
engine
@@ -204,6 +210,9 @@ impl PluginService {
let plugin_manifest_id = &manifest.metadata.id;
// 确保插件权限已分配给 admin 角色(幂等操作)
grant_permissions_to_admin(db, tenant_id, plugin_manifest_id).await?;
// 如果之前是 disabled 状态,需要先卸载再重新加载到内存
// disable 只改内存状态但不从 DashMap 移除)
if model.status == "disabled" {
@@ -551,32 +560,51 @@ impl PluginService {
let plugin_manifest_id = &new_manifest.metadata.id;
// 对比 schema — 为新增实体创建动态表
// 对比 schema — 为新增实体创建动态表 + 已有实体字段演进
if let Some(new_schema) = &new_manifest.schema {
let old_entities: Vec<&str> = old_manifest
.schema
.as_ref()
.map(|s| s.entities.iter().map(|e| e.name.as_str()).collect())
.unwrap_or_default();
let old_schema = old_manifest.schema.as_ref();
for entity in &new_schema.entities {
if !old_entities.contains(&entity.name.as_str()) {
tracing::info!(entity = %entity.name, "创建新增实体表");
DynamicTableManager::create_table(db, plugin_manifest_id, entity).await?;
let old_entity = old_schema
.and_then(|s| s.entities.iter().find(|e| e.name == entity.name));
match old_entity {
None => {
tracing::info!(entity = %entity.name, "创建新增实体表");
DynamicTableManager::create_table(db, plugin_manifest_id, entity).await?;
}
Some(old) => {
let diff = DynamicTableManager::diff_entity_fields(old, entity);
if !diff.new_filterable.is_empty() || !diff.new_searchable.is_empty() {
tracing::info!(
entity = %entity.name,
new_cols = diff.new_filterable.len(),
new_search = diff.new_searchable.len(),
"Schema 演进:新增 Generated Column"
);
DynamicTableManager::alter_add_generated_columns(
db, plugin_manifest_id, entity, &diff
).await?;
}
}
}
}
}
// 卸载旧 WASM 并加载新 WASM
engine.unload(plugin_manifest_id).await.ok();
// 先加载新版本到临时 key确保成功后再替换旧版本原子回滚
let temp_id = format!("{}__upgrade_{}", plugin_manifest_id, Uuid::now_v7());
engine
.load(plugin_manifest_id, &new_wasm, new_manifest.clone())
.load(&temp_id, &new_wasm, new_manifest.clone())
.await
.map_err(|e| {
tracing::error!(error = %e, "新版本 WASM 加载失败");
tracing::error!(error = %e, "新版本 WASM 加载失败,旧版本仍在运行");
e
})?;
// 新版本加载成功,卸载旧版本并重命名新版本为正式 key
engine.unload(plugin_manifest_id).await.ok();
engine.rename_plugin(&temp_id, plugin_manifest_id).await?;
// 更新数据库记录
let wasm_hash = {
let mut hasher = Sha256::new();
@@ -822,6 +850,76 @@ async fn register_plugin_permissions(
Ok(())
}
/// 将插件的所有权限分配给 admin 角色。
///
/// 使用 raw SQL 按 manifest_id 前缀匹配权限INSERT 到 role_permissions。
/// ON CONFLICT DO NOTHING 保证幂等。
pub async fn grant_permissions_to_admin(
db: &sea_orm::DatabaseConnection,
tenant_id: Uuid,
plugin_manifest_id: &str,
) -> AppResult<()> {
let prefix = format!("{}.%", plugin_manifest_id);
let sql = r#"
INSERT INTO role_permissions (tenant_id, role_id, permission_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version)
SELECT
p.tenant_id,
r.id,
p.id,
'all',
NOW(), NOW(),
r.id, r.id,
NULL, 1
FROM permissions p
CROSS JOIN roles r
WHERE p.tenant_id = $1
AND r.tenant_id = $1
AND r.code = 'admin'
AND r.deleted_at IS NULL
AND p.code LIKE $2
AND p.deleted_at IS NULL
AND NOT EXISTS (
SELECT 1 FROM role_permissions rp
WHERE rp.permission_id = p.id
AND rp.role_id = r.id
AND rp.deleted_at IS NULL
)
ON CONFLICT (role_id, permission_id) DO NOTHING
"#;
let result = db
.execute(sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
sql,
vec![
sea_orm::Value::from(tenant_id),
sea_orm::Value::from(prefix.clone()),
],
))
.await
.map_err(|e| {
tracing::error!(
plugin = plugin_manifest_id,
error = %e,
"分配插件权限给 admin 角色失败"
);
PluginError::DatabaseError(format!(
"分配插件权限给 admin 角色失败: {}",
e
))
})?;
let rows = result.rows_affected();
tracing::info!(
plugin = plugin_manifest_id,
rows_affected = rows,
tenant_id = %tenant_id,
"插件权限已分配给 admin 角色"
);
Ok(())
}
/// 清理插件注册的权限(软删除)。
///
/// 使用 raw SQL 按前缀匹配清理:`{plugin_manifest_id}.%`。