feat(config): 角色权限控制菜单可见性 + 医疗业务角色
- 修复 menu_service 角色过滤 bug: ctx.roles 存的是角色 code 而非 UUID, 新增 resolve_role_ids() 方法通过 code 查找数据库中的角色 ID - 创建 4 个医疗业务角色: 医生/护士/健康管理师/运营人员 - 重组菜单目录结构: 基础模块→工作台、业务模块→系统管理、健康管理→健康业务 - 菜单排序按功能域分组(患者医护/随访咨询/积分运营/内容运营/AI分析) - 为各角色分配对应的菜单可见性和操作权限
This commit is contained in:
@@ -28,6 +28,12 @@ impl From<sea_orm::TransactionError<ConfigError>> for ConfigError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sea_orm::DbErr> for ConfigError {
|
||||
fn from(err: sea_orm::DbErr) -> Self {
|
||||
ConfigError::Validation(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigError> for AppError {
|
||||
fn from(err: ConfigError) -> Self {
|
||||
match err {
|
||||
|
||||
@@ -36,13 +36,7 @@ where
|
||||
{
|
||||
require_permission(&ctx, "menu.list")?;
|
||||
|
||||
let role_ids: Vec<Uuid> = ctx
|
||||
.roles
|
||||
.iter()
|
||||
.filter_map(|r| Uuid::parse_str(r).ok())
|
||||
.collect();
|
||||
|
||||
let menus = MenuService::get_menu_tree(ctx.tenant_id, &role_ids, &state.db).await?;
|
||||
let menus = MenuService::get_menu_tree(ctx.tenant_id, &ctx.roles, &state.db).await?;
|
||||
|
||||
Ok(JsonResponse(ApiResponse::ok(menus)))
|
||||
}
|
||||
@@ -257,14 +251,7 @@ where
|
||||
ConfigState: FromRef<S>,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let role_ids: Vec<Uuid> = ctx
|
||||
.roles
|
||||
.iter()
|
||||
.filter_map(|r| Uuid::parse_str(r).ok())
|
||||
.collect();
|
||||
|
||||
// 如果用户有角色关联菜单,按角色过滤;否则返回全部(admin 兜底)
|
||||
let menus = MenuService::get_menu_tree(ctx.tenant_id, &role_ids, &state.db).await?;
|
||||
let menus = MenuService::get_menu_tree(ctx.tenant_id, &ctx.roles, &state.db).await?;
|
||||
|
||||
Ok(JsonResponse(ApiResponse::ok(menus)))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use chrono::Utc;
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
|
||||
use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, QueryFilter, QueryOrder, Set};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::dto::{CreateMenuReq, MenuResp};
|
||||
@@ -17,15 +17,49 @@ use erp_core::events::EventBus;
|
||||
pub struct MenuService;
|
||||
|
||||
impl MenuService {
|
||||
/// 通过角色 code 列表查找对应的角色 ID 列表。
|
||||
async fn resolve_role_ids(
|
||||
tenant_id: Uuid,
|
||||
role_codes: &[String],
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> ConfigResult<Vec<Uuid>> {
|
||||
if role_codes.is_empty() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
let codes_csv: String = role_codes
|
||||
.iter()
|
||||
.map(|c| format!("'{}'", c.replace('\'', "''")))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
let sql = format!(
|
||||
"SELECT id FROM roles WHERE tenant_id = '{}' AND code IN ({}) AND deleted_at IS NULL",
|
||||
tenant_id, codes_csv
|
||||
);
|
||||
let stmt = sea_orm::Statement::from_string(sea_orm::DatabaseBackend::Postgres, sql);
|
||||
let rows = db.query_all(stmt).await?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.filter_map(|row| {
|
||||
let id: Uuid = row.try_get_by_index(0).ok()?;
|
||||
Some(id)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// 获取当前租户下指定角色可见的菜单树。
|
||||
///
|
||||
/// 如果 `role_ids` 非空,仅返回这些角色关联的菜单;
|
||||
/// 否则返回租户全部菜单。结果按 `sort_order` 排列并组装为树形结构。
|
||||
/// `role_codes` 为当前用户的角色 code 列表(如 ["admin"]、["doctor"])。
|
||||
/// 方法内部将 code 转换为 ID,再通过 menu_roles 表过滤。
|
||||
/// 如果角色没有任何菜单关联,返回全部菜单(admin 兜底)。
|
||||
pub async fn get_menu_tree(
|
||||
tenant_id: Uuid,
|
||||
role_ids: &[Uuid],
|
||||
role_codes: &[String],
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> ConfigResult<Vec<MenuResp>> {
|
||||
// 0. 将角色 code 转换为 UUID
|
||||
let role_ids = Self::resolve_role_ids(tenant_id, role_codes, db).await?;
|
||||
|
||||
// 1. 查询租户下所有未删除的菜单,按 sort_order 排序
|
||||
let all_menus = menu::Entity::find()
|
||||
.filter(menu::Column::TenantId.eq(tenant_id))
|
||||
|
||||
@@ -124,6 +124,7 @@ mod m20260505_000121_create_ai_knowledge_references;
|
||||
mod m20260505_000122_create_ai_knowledge_guides;
|
||||
mod m20260505_000123_update_ai_prompts_system_instruction;
|
||||
mod m20260505_000124_freeze_deferred_menus;
|
||||
mod m20260506_000125_restructure_menus_and_roles;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
@@ -255,6 +256,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260505_000122_create_ai_knowledge_guides::Migration),
|
||||
Box::new(m20260505_000123_update_ai_prompts_system_instruction::Migration),
|
||||
Box::new(m20260505_000124_freeze_deferred_menus::Migration),
|
||||
Box::new(m20260506_000125_restructure_menus_and_roles::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
//! 重组菜单结构 + 创建医疗业务角色 + 菜单-角色关联
|
||||
//!
|
||||
//! 1. 重命名顶级目录,使其更贴合健康管理机构使用习惯
|
||||
//! 2. 调整菜单项排序,按功能域分组
|
||||
//! 3. 创建 4 个医疗业务角色: doctor / nurse / health_manager / operator
|
||||
//! 4. 为各角色分配菜单可见性和操作权限
|
||||
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// ================================================================
|
||||
// Part 1: 重命名顶级目录
|
||||
// ================================================================
|
||||
let dir_renames: &[(&str, &str)] = &[
|
||||
("基础模块", "工作台"),
|
||||
("业务模块", "系统管理"),
|
||||
("健康管理", "健康业务"),
|
||||
("系统", "配置"),
|
||||
];
|
||||
for &(old, new) in dir_renames {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET title = '{new}' WHERE title = '{old}' AND menu_type = 'directory' AND deleted_at IS NULL"
|
||||
)).await?;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Part 2: 调整菜单排序和归属
|
||||
// ================================================================
|
||||
// 将"统计报表"移到"工作台"目录下
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET parent_id = (SELECT id FROM menus WHERE title = '工作台' AND menu_type = 'directory' AND deleted_at IS NULL LIMIT 1), sort_order = 1 WHERE path = '/health/statistics' AND deleted_at IS NULL"
|
||||
).await?;
|
||||
|
||||
// 将"工作流"和"消息中心"移到"系统管理"目录下
|
||||
// (它们已经是"业务模块"的子项,目录重命名后自动归到"系统管理")
|
||||
|
||||
// 将"系统设置"和"插件管理"移到"系统管理"目录下
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET parent_id = (SELECT id FROM menus WHERE title = '系统管理' AND menu_type = 'directory' AND deleted_at IS NULL LIMIT 1) WHERE path IN ('/settings', '/plugins/admin') AND deleted_at IS NULL"
|
||||
).await?;
|
||||
|
||||
// 删除空的"配置"目录(原"系统"目录,其子项已移走)
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET deleted_at = NOW() WHERE title = '配置' AND menu_type = 'directory' AND deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM menus c WHERE c.parent_id = menus.id AND c.deleted_at IS NULL)"
|
||||
).await?;
|
||||
|
||||
// 调整"系统管理"目录下的排序
|
||||
let sys_sort: &[(&str, i32)] = &[
|
||||
("/users", 0),
|
||||
("/roles", 1),
|
||||
("/organizations", 2),
|
||||
("/workflow", 3),
|
||||
("/messages", 4),
|
||||
("/settings", 5),
|
||||
("/plugins/admin", 6),
|
||||
];
|
||||
for &(path, sort) in sys_sort {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET sort_order = {sort} WHERE path = '{path}' AND deleted_at IS NULL"
|
||||
)).await?;
|
||||
}
|
||||
|
||||
// 调整"健康业务"目录下的排序 — 按功能域分组
|
||||
let health_sort: &[(&str, i32)] = &[
|
||||
// 患者与医护
|
||||
("/health/patients", 0),
|
||||
("/health/doctors", 1),
|
||||
("/health/tags", 2),
|
||||
("/health/diagnoses", 3),
|
||||
// 随访与咨询
|
||||
("/health/follow-up-tasks", 10),
|
||||
("/health/consultations", 11),
|
||||
("/health/action-inbox", 12),
|
||||
("/health/follow-up-templates", 13),
|
||||
// 监测与知情同意
|
||||
("/health/daily-monitoring", 20),
|
||||
("/health/consents", 21),
|
||||
("/health/realtime-monitor", 22),
|
||||
// 告警与设备
|
||||
("/health/alert-dashboard", 30),
|
||||
("/health/alerts", 31),
|
||||
("/health/alert-rules", 32),
|
||||
("/health/devices", 33),
|
||||
("/health/ble-gateways", 34),
|
||||
("/health/critical-value-thresholds", 35),
|
||||
// 运营
|
||||
("/health/articles", 40),
|
||||
("/health/points-rules", 41),
|
||||
("/health/points-products", 42),
|
||||
("/health/points-orders", 43),
|
||||
("/health/offline-events", 44),
|
||||
// AI
|
||||
("/health/ai-prompts", 50),
|
||||
("/health/ai-analysis", 51),
|
||||
("/health/ai-usage", 52),
|
||||
// 其他
|
||||
("/health/oauth-clients", 60),
|
||||
];
|
||||
for &(path, sort) in health_sort {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET sort_order = {sort} WHERE path = '{path}' AND deleted_at IS NULL"
|
||||
)).await?;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Part 3: 创建/更新医疗业务角色(幂等)
|
||||
// ================================================================
|
||||
let sys = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
let roles: &[(&str, &str, &str)] = &[
|
||||
("doctor", "医生", "负责患者诊疗、随访管理、AI辅助诊断"),
|
||||
("nurse", "护士", "负责患者护理、体征监测、用药管理"),
|
||||
("health_manager", "健康管理师", "负责健康管理计划、随访协调、运营统计"),
|
||||
("operator", "运营人员", "负责内容运营、积分商城、活动管理"),
|
||||
];
|
||||
|
||||
for &(code, name, desc) in roles {
|
||||
// 对已存在的角色:更新名称和描述;对新租户:创建角色
|
||||
db.execute_unprepared(&format!(
|
||||
"INSERT INTO roles (id, tenant_id, name, code, description, is_system, created_at, updated_at, created_by, updated_by, deleted_at, version) \
|
||||
SELECT gen_random_uuid(), t.id, '{name}', '{code}', '{desc}', false, NOW(), NOW(), '{sys}', '{sys}', NULL, 1 \
|
||||
FROM tenant t \
|
||||
WHERE NOT EXISTS (SELECT 1 FROM roles r WHERE r.tenant_id = t.id AND r.code = '{code}' AND r.deleted_at IS NULL)"
|
||||
)).await?;
|
||||
// 更新已存在的角色的名称和描述
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE roles SET name = '{name}', description = '{desc}' WHERE code = '{code}' AND deleted_at IS NULL AND name != '{name}'"
|
||||
)).await?;
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Part 4: 为新角色分配权限
|
||||
// ================================================================
|
||||
|
||||
// doctor 权限: 患者 + 医护 + 随访 + 咨询 + AI + 告警 + 日常监测 + 诊断 + 知情同意 + 行动收件箱 + 消息(只读)
|
||||
assign_perms_by_codes(db, "doctor", &[
|
||||
"health.patient.list", "health.patient.manage",
|
||||
"health.doctor.list", "health.doctor.manage",
|
||||
"health.follow-up.list", "health.follow-up.manage",
|
||||
"health.consultation.list", "health.consultation.manage",
|
||||
"health.action-inbox.list", "health.action-inbox.manage",
|
||||
"health.daily-monitoring.list", "health.daily-monitoring.manage",
|
||||
"health.alerts.list", "health.alerts.manage",
|
||||
"health.alert-rules.list",
|
||||
"health.critical-alerts.list",
|
||||
"health.diagnosis.list", "health.diagnosis.manage",
|
||||
"health.consent.list", "health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"ai.analysis.list", "ai.suggestion.list",
|
||||
"message.list",
|
||||
"workflow.list", "workflow.read",
|
||||
]).await?;
|
||||
|
||||
// nurse 权限: 患者 + 随访 + 咨询 + 告警 + 日常监测 + 诊断 + 知情同意 + 行动收件箱 + 消息(只读)
|
||||
assign_perms_by_codes(db, "nurse", &[
|
||||
"health.patient.list", "health.patient.manage",
|
||||
"health.follow-up.list", "health.follow-up.manage",
|
||||
"health.consultation.list",
|
||||
"health.action-inbox.list", "health.action-inbox.manage",
|
||||
"health.daily-monitoring.list", "health.daily-monitoring.manage",
|
||||
"health.alerts.list",
|
||||
"health.critical-alerts.list",
|
||||
"health.diagnosis.list",
|
||||
"health.consent.list", "health.consent.manage",
|
||||
"health.health-data.list",
|
||||
"health.device-readings.list",
|
||||
"message.list",
|
||||
]).await?;
|
||||
|
||||
// health_manager 权限: 统计 + 患者 + 医护 + 随访 + 咨询 + AI + 告警 + 设备 + 日常监测 + 标签 + 诊断 + 知情同意 + 行动收件箱 + 随访模板
|
||||
assign_perms_by_codes(db, "health_manager", &[
|
||||
"health.patient.list", "health.patient.manage",
|
||||
"health.doctor.list",
|
||||
"health.follow-up.list", "health.follow-up.manage",
|
||||
"health.consultation.list", "health.consultation.manage",
|
||||
"health.action-inbox.list", "health.action-inbox.manage", "health.action-inbox.team",
|
||||
"health.daily-monitoring.list", "health.daily-monitoring.manage",
|
||||
"health.alerts.list", "health.alerts.manage",
|
||||
"health.alert-rules.list", "health.alert-rules.manage",
|
||||
"health.critical-alerts.list",
|
||||
"health.critical-value-thresholds.list",
|
||||
"health.devices.list",
|
||||
"health.tags.list", "health.tags.manage",
|
||||
"health.diagnosis.list", "health.diagnosis.manage",
|
||||
"health.consent.list", "health.consent.manage",
|
||||
"health.health-data.list", "health.health-data.manage",
|
||||
"health.follow-up-templates.list", "health.follow-up-templates.manage",
|
||||
"health.dashboard.manage",
|
||||
"ai.analysis.list", "ai.analysis.manage",
|
||||
"ai.prompt.list",
|
||||
"ai.suggestion.list", "ai.suggestion.manage",
|
||||
"ai.usage.list",
|
||||
"message.list",
|
||||
"workflow.list", "workflow.read", "workflow.start",
|
||||
]).await?;
|
||||
|
||||
// operator 权限: 统计 + 标签 + 内容 + 积分 + 活动 + 设备 + 告警(只读)
|
||||
assign_perms_by_codes(db, "operator", &[
|
||||
"health.patient.list",
|
||||
"health.tags.list", "health.tags.manage",
|
||||
"health.articles.list", "health.articles.manage",
|
||||
"health.articles.review",
|
||||
"health.points.list", "health.points.manage",
|
||||
"health.offline-events.list", "health.offline-events.manage",
|
||||
"health.devices.list",
|
||||
"health.alerts.list",
|
||||
"health.dashboard.manage",
|
||||
"ai.usage.list",
|
||||
"message.list",
|
||||
]).await?;
|
||||
|
||||
// ================================================================
|
||||
// Part 5: 菜单-角色关联(menu_roles)
|
||||
// ================================================================
|
||||
// admin 角色自动看到所有 visible=true 菜单(service 层 fallback 逻辑)
|
||||
// 只需为新角色关联菜单
|
||||
|
||||
// doctor 可见菜单路径
|
||||
let doctor_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients", "/health/doctors",
|
||||
"/health/follow-up-tasks", "/health/consultations",
|
||||
"/health/action-inbox", "/health/follow-up-templates",
|
||||
"/health/daily-monitoring", "/health/consents", "/health/diagnoses",
|
||||
"/health/alert-dashboard", "/health/alerts",
|
||||
"/health/ai-analysis", "/health/ai-usage",
|
||||
"/messages",
|
||||
];
|
||||
assign_menus_for_role(db, "doctor", doctor_paths).await?;
|
||||
|
||||
// nurse 可见菜单路径
|
||||
let nurse_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients",
|
||||
"/health/follow-up-tasks", "/health/consultations",
|
||||
"/health/action-inbox",
|
||||
"/health/daily-monitoring", "/health/consents", "/health/diagnoses",
|
||||
"/health/alert-dashboard", "/health/alerts",
|
||||
"/messages",
|
||||
];
|
||||
assign_menus_for_role(db, "nurse", nurse_paths).await?;
|
||||
|
||||
// health_manager 可见菜单路径
|
||||
let hm_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients", "/health/doctors", "/health/tags",
|
||||
"/health/follow-up-tasks", "/health/consultations",
|
||||
"/health/action-inbox", "/health/follow-up-templates",
|
||||
"/health/daily-monitoring", "/health/consents", "/health/diagnoses",
|
||||
"/health/alert-dashboard", "/health/alerts", "/health/alert-rules",
|
||||
"/health/devices", "/health/critical-value-thresholds",
|
||||
"/health/ai-prompts", "/health/ai-analysis", "/health/ai-usage",
|
||||
"/health/realtime-monitor",
|
||||
"/messages",
|
||||
];
|
||||
assign_menus_for_role(db, "health_manager", hm_paths).await?;
|
||||
|
||||
// operator 可见菜单路径
|
||||
let op_paths: &[&str] = &[
|
||||
"/", "/health/statistics",
|
||||
"/health/patients", "/health/tags",
|
||||
"/health/articles",
|
||||
"/health/points-rules", "/health/points-products", "/health/points-orders",
|
||||
"/health/offline-events",
|
||||
"/health/devices",
|
||||
"/health/alert-dashboard", "/health/alerts",
|
||||
"/health/ai-usage",
|
||||
"/messages",
|
||||
];
|
||||
assign_menus_for_role(db, "operator", op_paths).await?;
|
||||
|
||||
// 也要为 admin 角色显式关联所有可见菜单
|
||||
assign_admin_all_menus(db).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let db = manager.get_connection();
|
||||
|
||||
// 删除新创建的角色及其关联
|
||||
for code in &["doctor", "nurse", "health_manager", "operator"] {
|
||||
// 删除 menu_roles 关联
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menu_roles SET deleted_at = NOW() WHERE role_id IN (SELECT id FROM roles WHERE code = '{code}')"
|
||||
)).await?;
|
||||
// 删除 role_permissions 关联
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE role_permissions SET deleted_at = NOW() WHERE role_id IN (SELECT id FROM roles WHERE code = '{code}')"
|
||||
)).await?;
|
||||
// 删除 user_roles 关联
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE user_roles SET deleted_at = NOW() WHERE role_id IN (SELECT id FROM roles WHERE code = '{code}')"
|
||||
)).await?;
|
||||
// 软删除角色
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE roles SET deleted_at = NOW() WHERE code = '{code}' AND is_system = false"
|
||||
)).await?;
|
||||
}
|
||||
|
||||
// 恢复目录名
|
||||
let dir_renames: &[(&str, &str)] = &[
|
||||
("工作台", "基础模块"),
|
||||
("系统管理", "业务模块"),
|
||||
("健康业务", "健康管理"),
|
||||
];
|
||||
for &(old, new) in dir_renames {
|
||||
db.execute_unprepared(&format!(
|
||||
"UPDATE menus SET title = '{new}' WHERE title = '{old}' AND menu_type = 'directory'"
|
||||
)).await?;
|
||||
}
|
||||
|
||||
// 恢复"系统"目录
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET deleted_at = NULL WHERE title = '配置' AND menu_type = 'directory'"
|
||||
).await?;
|
||||
// 恢复"系统"名称
|
||||
db.execute_unprepared(
|
||||
"UPDATE menus SET title = '系统' WHERE title = '配置' AND menu_type = 'directory'"
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// 为指定角色分配权限码
|
||||
async fn assign_perms_by_codes(
|
||||
db: &sea_orm_migration::SchemaManagerConnection<'_>,
|
||||
role_code: &str,
|
||||
perm_codes: &[&str],
|
||||
) -> Result<(), DbErr> {
|
||||
let codes_csv: String = perm_codes
|
||||
.iter()
|
||||
.map(|c| format!("'{}'", c))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
// 为所有租户的该角色分配这些权限
|
||||
// 使用 ON CONFLICT 处理已软删除的行(恢复它们而非报错)
|
||||
db.execute_unprepared(&format!(
|
||||
"INSERT INTO role_permissions (role_id, permission_id, tenant_id, data_scope, created_at, updated_at, created_by, updated_by, deleted_at, version) \
|
||||
SELECT r.id, p.id, r.tenant_id, 'all', NOW(), NOW(), r.id, r.id, NULL, 1 \
|
||||
FROM roles r \
|
||||
JOIN permissions p ON p.tenant_id = r.tenant_id AND p.code IN ({codes_csv}) AND p.deleted_at IS NULL \
|
||||
WHERE r.code = '{role_code}' AND r.deleted_at IS NULL \
|
||||
ON CONFLICT (role_id, permission_id) DO UPDATE SET \
|
||||
data_scope = 'all', deleted_at = NULL, updated_at = NOW(), version = role_permissions.version + 1"
|
||||
)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 为指定角色关联菜单
|
||||
async fn assign_menus_for_role(
|
||||
db: &sea_orm_migration::SchemaManagerConnection<'_>,
|
||||
role_code: &str,
|
||||
menu_paths: &[&str],
|
||||
) -> Result<(), DbErr> {
|
||||
let paths_csv: String = menu_paths
|
||||
.iter()
|
||||
.map(|p| format!("'{}'", p))
|
||||
.collect::<Vec<_>>()
|
||||
.join(",");
|
||||
|
||||
// 为所有租户的该角色关联菜单
|
||||
db.execute_unprepared(&format!(
|
||||
"INSERT INTO menu_roles (id, menu_id, role_id, tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version) \
|
||||
SELECT gen_random_uuid(), m.id, r.id, r.tenant_id, NOW(), NOW(), r.id, r.id, NULL, 1 \
|
||||
FROM roles r \
|
||||
JOIN menus m ON m.tenant_id = r.tenant_id AND m.path IN ({paths_csv}) AND m.deleted_at IS NULL AND m.visible = true \
|
||||
WHERE r.code = '{role_code}' AND r.deleted_at IS NULL \
|
||||
ON CONFLICT (id) DO NOTHING"
|
||||
)).await?;
|
||||
|
||||
// 同时关联所有顶级 directory(菜单组标题),确保目录可见
|
||||
db.execute_unprepared(&format!(
|
||||
"INSERT INTO menu_roles (id, menu_id, role_id, tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version) \
|
||||
SELECT gen_random_uuid(), m.id, r.id, r.tenant_id, NOW(), NOW(), r.id, r.id, NULL, 1 \
|
||||
FROM roles r \
|
||||
JOIN menus m ON m.tenant_id = r.tenant_id AND m.menu_type = 'directory' AND m.deleted_at IS NULL \
|
||||
WHERE r.code = '{role_code}' AND r.deleted_at IS NULL \
|
||||
ON CONFLICT (id) DO NOTHING"
|
||||
)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 为 admin 角色关联所有可见菜单
|
||||
async fn assign_admin_all_menus(
|
||||
db: &sea_orm_migration::SchemaManagerConnection<'_>,
|
||||
) -> Result<(), DbErr> {
|
||||
db.execute_unprepared(
|
||||
"INSERT INTO menu_roles (id, menu_id, role_id, tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version) \
|
||||
SELECT gen_random_uuid(), m.id, r.id, r.tenant_id, NOW(), NOW(), r.id, r.id, NULL, 1 \
|
||||
FROM roles r \
|
||||
JOIN menus m ON m.tenant_id = r.tenant_id AND m.deleted_at IS NULL AND m.visible = true \
|
||||
WHERE r.code = 'admin' AND r.deleted_at IS NULL \
|
||||
ON CONFLICT (id) DO NOTHING"
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user