feat(auth): data_scope 行级数据权限 — DataScope 枚举 + 中间件加载

- TenantContext 新增 permission_data_scopes: HashMap<String, DataScope>
- DataScope 枚举: All/SelfOnly/Department/DepartmentTree
- JWT 中间件查询 role_permissions.data_scope 填充到上下文
- rbac::get_data_scope() 供 service 层按权限获取数据范围
- 默认 All,完全向后兼容现有行为
This commit is contained in:
iven
2026-04-27 19:31:19 +08:00
parent d5c9654370
commit 633bf8c62d
3 changed files with 103 additions and 2 deletions

View File

@@ -5,7 +5,7 @@ use axum::response::Response;
use erp_core::error::AppError;
use erp_core::request_info::REQUEST_INFO;
use erp_core::request_info::RequestInfo;
use erp_core::types::TenantContext;
use erp_core::types::{DataScope, TenantContext};
use crate::service::token_service::TokenService;
@@ -63,6 +63,12 @@ pub async fn jwt_auth_middleware_fn(
None => vec![],
};
// 查询每个权限的数据范围
let permission_data_scopes = match &db {
Some(conn) => fetch_permission_data_scopes(claims.sub, claims.tid, conn).await,
None => std::collections::HashMap::new(),
};
// 提取请求来源信息IP + User-Agent用于审计日志
let request_info = RequestInfo::from_headers(req.headers());
@@ -72,6 +78,7 @@ pub async fn jwt_auth_middleware_fn(
roles: claims.roles,
permissions: claims.permissions,
department_ids,
permission_data_scopes,
};
// Reconstruct the request with the TenantContext injected into extensions.
@@ -105,3 +112,58 @@ async fn fetch_user_department_ids(
vec![]
})
}
/// 查询用户每个权限的数据范围(从 role_permissions 表)
async fn fetch_permission_data_scopes(
user_id: uuid::Uuid,
tenant_id: uuid::Uuid,
db: &sea_orm::DatabaseConnection,
) -> std::collections::HashMap<String, DataScope> {
use sea_orm::ConnectionTrait;
let sql = r#"
SELECT p.code, MIN(
CASE rp.data_scope
WHEN 'all' THEN 0
WHEN 'department_tree' THEN 1
WHEN 'department' THEN 2
WHEN 'self' THEN 3
ELSE 0
END
) AS scope_rank,
MIN(rp.data_scope) AS data_scope
FROM user_roles ur
JOIN role_permissions rp ON ur.role_id = rp.role_id AND ur.tenant_id = rp.tenant_id
JOIN permissions p ON rp.permission_id = p.id
WHERE ur.user_id = $1
AND ur.tenant_id = $2
AND ur.deleted_at IS NULL
AND rp.deleted_at IS NULL
GROUP BY p.code
"#;
let stmt = sea_orm::Statement::from_sql_and_values(
sea_orm::DatabaseBackend::Postgres,
sql,
[user_id.into(), tenant_id.into()],
);
match db.query_all(stmt).await {
Ok(rows) => {
let mut scopes = std::collections::HashMap::new();
for row in rows {
if let (Ok(code), Ok(scope)) = (
row.try_get_by_index::<String>(0),
row.try_get_by_index::<String>(2),
) {
scopes.insert(code, DataScope::from_str(&scope));
}
}
scopes
}
Err(e) => {
tracing::warn!(error = %e, "查询权限数据范围失败,默认全部 All");
std::collections::HashMap::new()
}
}
}