feat(plugin): 外键校验 — ref_entity 字段验证引用记录存在性

新增 validate_ref_entities 异步函数,在 create/update 时检查
ref_entity 字段指向的记录是否存在于对应动态表中。自引用
场景下 create 跳过校验,update 跳过自身引用。
This commit is contained in:
iven
2026-04-17 10:35:46 +08:00
parent f4b1a06d53
commit 649334e862

View File

@@ -28,6 +28,7 @@ impl PluginDataService {
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
let fields = info.fields()?;
validate_data(&data, &fields)?;
validate_ref_entities(&data, &fields, entity_name, plugin_id, tenant_id, db, true, None).await?;
let (sql, values) =
DynamicTableManager::build_insert_sql(&info.table_name, tenant_id, operator_id, &data);
@@ -206,6 +207,7 @@ impl PluginDataService {
let info = resolve_entity_info(plugin_id, entity_name, tenant_id, db).await?;
let fields = info.fields()?;
validate_data(&data, &fields)?;
validate_ref_entities(&data, &fields, entity_name, plugin_id, tenant_id, db, false, Some(id)).await?;
let (sql, values) = DynamicTableManager::build_update_sql(
&info.table_name,
@@ -482,3 +484,71 @@ fn validate_data(data: &serde_json::Value, fields: &[PluginField]) -> AppResult<
}
Ok(())
}
/// 校验外键引用 — 检查 ref_entity 字段指向的记录是否存在
async fn validate_ref_entities(
data: &serde_json::Value,
fields: &[PluginField],
current_entity: &str,
plugin_id: Uuid,
tenant_id: Uuid,
db: &sea_orm::DatabaseConnection,
is_create: bool,
record_id: Option<Uuid>,
) -> AppResult<()> {
let obj = data.as_object().ok_or_else(|| {
AppError::Validation("data 必须是 JSON 对象".to_string())
})?;
for field in fields {
let Some(ref_entity_name) = &field.ref_entity else { continue };
let Some(val) = obj.get(&field.name) else { continue };
let str_val = val.as_str().unwrap_or("").trim().to_string();
if str_val.is_empty() && !field.required { continue; }
if str_val.is_empty() { continue; }
let ref_id = Uuid::parse_str(&str_val).map_err(|_| {
AppError::Validation(format!(
"字段 '{}' 的值 '{}' 不是有效的 UUID",
field.display_name.as_deref().unwrap_or(&field.name),
str_val
))
})?;
// 自引用 + create跳过记录尚未存在
if ref_entity_name == current_entity && is_create {
continue;
}
// 自引用 + update检查是否引用自身
if ref_entity_name == current_entity && !is_create {
if let Some(rid) = record_id {
if ref_id == rid { continue; }
}
}
// 查询被引用记录是否存在
let manifest_id = resolve_manifest_id(plugin_id, tenant_id, db).await?;
let ref_table = DynamicTableManager::table_name(&manifest_id, ref_entity_name);
let check_sql = format!(
"SELECT 1 as check_result FROM \"{}\" WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL LIMIT 1",
ref_table
);
#[derive(FromQueryResult)]
struct ExistsCheck { check_result: Option<i32> }
let result = ExistsCheck::find_by_statement(Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
check_sql,
[ref_id.into(), tenant_id.into()],
)).one(db).await?;
if result.is_none() {
return Err(AppError::Validation(format!(
"引用的 {} 记录不存在ID: {}",
ref_entity_name, ref_id
)));
}
}
Ok(())
}