feat(plugin): 外键校验 — ref_entity 字段验证引用记录存在性
新增 validate_ref_entities 异步函数,在 create/update 时检查 ref_entity 字段指向的记录是否存在于对应动态表中。自引用 场景下 create 跳过校验,update 跳过自身引用。
This commit is contained in:
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user