feat(plugin): SQL 构建支持行级数据范围条件
DynamicTableManager 新增 build_data_scope_condition_with_params 方法, 支持 all/self/department/department_tree 四种数据范围过滤。 部门成员为空时自动退化为 self 范围,支持 Generated Column 路由。 附带 6 个单元测试覆盖所有场景。
This commit is contained in:
@@ -600,6 +600,68 @@ impl DynamicTableManager {
|
||||
Ok((sql, values))
|
||||
}
|
||||
|
||||
/// 构建数据范围 SQL 条件 — 用于行级数据权限过滤
|
||||
///
|
||||
/// 根据权限范围级别 (scope_level) 生成对应的 WHERE 子句和参数:
|
||||
/// - "all": 无额外条件(空字符串)
|
||||
/// - "self": 只能看自己创建/拥有的数据
|
||||
/// - "department" / "department_tree": 能看部门成员的数据
|
||||
///
|
||||
/// 返回 (sql_fragment, params),sql_fragment 可直接拼接到 WHERE 子句中
|
||||
pub fn build_data_scope_condition_with_params(
|
||||
scope_level: &str,
|
||||
current_user_id: &Uuid,
|
||||
owner_field: &str,
|
||||
dept_member_ids: &[Uuid],
|
||||
start_param_idx: usize,
|
||||
generated_fields: &[String],
|
||||
) -> (String, Vec<Value>) {
|
||||
let ref_fn = Self::field_reference_fn(generated_fields);
|
||||
let owner_ref = ref_fn(owner_field);
|
||||
match scope_level {
|
||||
"self" => (
|
||||
format!(
|
||||
"({} = ${} OR \"created_by\" = ${})",
|
||||
owner_ref, start_param_idx, start_param_idx
|
||||
),
|
||||
vec![
|
||||
current_user_id.to_string().into(),
|
||||
(*current_user_id).into(),
|
||||
],
|
||||
),
|
||||
"department" | "department_tree" => {
|
||||
if dept_member_ids.is_empty() {
|
||||
// 部门成员为空时退化为 self 范围
|
||||
(
|
||||
format!(
|
||||
"({} = ${} OR \"created_by\" = ${})",
|
||||
owner_ref, start_param_idx, start_param_idx
|
||||
),
|
||||
vec![
|
||||
current_user_id.to_string().into(),
|
||||
(*current_user_id).into(),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
let placeholders: Vec<String> = dept_member_ids
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, _)| format!("${}", start_param_idx + i))
|
||||
.collect();
|
||||
let values: Vec<Value> = dept_member_ids
|
||||
.iter()
|
||||
.map(|id| id.to_string().into())
|
||||
.collect();
|
||||
(
|
||||
format!("{} IN ({})", owner_ref, placeholders.join(", ")),
|
||||
values,
|
||||
)
|
||||
}
|
||||
}
|
||||
"all" | _ => (String::new(), vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
/// 编码游标
|
||||
pub fn encode_cursor(values: &[String], id: &Uuid) -> String {
|
||||
let obj = serde_json::json!({
|
||||
@@ -1124,4 +1186,134 @@ mod tests {
|
||||
"应有 tenant_id + cursor_val + cursor_id + limit"
|
||||
);
|
||||
}
|
||||
|
||||
// ===== build_data_scope_condition_with_params 测试 =====
|
||||
|
||||
#[test]
|
||||
fn test_build_data_scope_condition_self() {
|
||||
let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
|
||||
"self",
|
||||
&Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
"owner_id",
|
||||
&[],
|
||||
1,
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
sql.contains("\"data\"->>'owner_id'"),
|
||||
"self 应包含 owner_id 条件, got: {}",
|
||||
sql
|
||||
);
|
||||
assert!(
|
||||
sql.contains("\"created_by\""),
|
||||
"self 应包含 created_by 条件, got: {}",
|
||||
sql
|
||||
);
|
||||
assert_eq!(values.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_data_scope_condition_department() {
|
||||
let dept_members = vec![
|
||||
Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
|
||||
];
|
||||
let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
|
||||
"department",
|
||||
&Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
"owner_id",
|
||||
&dept_members,
|
||||
2,
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
sql.contains("IN"),
|
||||
"department 应使用 IN 条件, got: {}",
|
||||
sql
|
||||
);
|
||||
assert!(
|
||||
sql.contains("$2"),
|
||||
"参数索引应从 2 开始, got: {}",
|
||||
sql
|
||||
);
|
||||
assert!(
|
||||
sql.contains("$3"),
|
||||
"第二个参数索引应为 3, got: {}",
|
||||
sql
|
||||
);
|
||||
assert_eq!(values.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_data_scope_condition_department_empty_degrades_to_self() {
|
||||
let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
|
||||
"department",
|
||||
&Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
"owner_id",
|
||||
&[],
|
||||
1,
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
sql.contains("\"created_by\""),
|
||||
"空部门应退化为 self 范围, got: {}",
|
||||
sql
|
||||
);
|
||||
assert_eq!(values.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_data_scope_condition_all() {
|
||||
let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
|
||||
"all",
|
||||
&Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
"owner_id",
|
||||
&[],
|
||||
1,
|
||||
&[],
|
||||
);
|
||||
assert!(sql.is_empty(), "all 应返回空条件");
|
||||
assert!(values.is_empty(), "all 应返回空参数");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_data_scope_condition_department_tree() {
|
||||
let dept_members = vec![
|
||||
Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
Uuid::parse_str("00000000-0000-0000-0000-000000000002").unwrap(),
|
||||
Uuid::parse_str("00000000-0000-0000-0000-000000000003").unwrap(),
|
||||
];
|
||||
let (sql, values) = DynamicTableManager::build_data_scope_condition_with_params(
|
||||
"department_tree",
|
||||
&Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
"owner_id",
|
||||
&dept_members,
|
||||
5,
|
||||
&[],
|
||||
);
|
||||
assert!(
|
||||
sql.contains("IN"),
|
||||
"department_tree 应使用 IN 条件, got: {}",
|
||||
sql
|
||||
);
|
||||
assert_eq!(values.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_data_scope_condition_with_generated_column() {
|
||||
let generated = vec!["owner_id".to_string()];
|
||||
let (sql, _) = DynamicTableManager::build_data_scope_condition_with_params(
|
||||
"self",
|
||||
&Uuid::parse_str("00000000-0000-0000-0000-000000000001").unwrap(),
|
||||
"owner_id",
|
||||
&[],
|
||||
1,
|
||||
&generated,
|
||||
);
|
||||
assert!(
|
||||
sql.contains("\"_f_owner_id\""),
|
||||
"generated column 应使用 _f_ 前缀, got: {}",
|
||||
sql
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user