from app.models.system import SysMenu, SysElement, SysRolePermission from app.extensions import db from sqlalchemy.exc import SQLAlchemyError class PermissionService: @staticmethod def get_permission_tree(): """ 获取完整的权限树(菜单嵌套菜单 + 菜单包含元素) 供前端权限配置页面展示 """ # 1. 获取所有菜单 (按 parent_id 和 sort_order 排序,保证父子处理顺序) menus = SysMenu.query.order_by(SysMenu.parent_id, SysMenu.sort_order).all() # 2. 获取所有元素 elements = SysElement.query.all() # --- 核心逻辑:构建树形结构 --- # 3. 创建一个 lookup 字典,方便通过 ID 查找菜单节点 # 同时将 SQLAlchemy 对象转为字典,方便后续操作 menu_map = {} for m in menus: m_dict = m.to_dict() m_dict['children'] = [] # 初始化 children menu_map[m.id] = m_dict # 4. 创建 code 到 id 的映射,用于把 element 挂载到 menu 上 # 因为 SysElement 关联的是 menu_code,而不是 menu_id code_to_id = {m.code: m.id for m in menus} # 5. 将元素 (Elements) 挂载到对应的菜单 (Menu) 下 for el in elements: # 找到该元素所属菜单的 ID parent_menu_id = code_to_id.get(el.menu_code) if parent_menu_id and parent_menu_id in menu_map: el_dict = el.to_dict() # 标记类型为 element,前端 transformData 需要用到 el_dict['type'] = 'element' menu_map[parent_menu_id]['children'].append(el_dict) # 6. 将子菜单挂载到父菜单下,并构建最终的树 tree_data = [] for m in menus: current_node = menu_map[m.id] if m.parent_id == 0 or m.parent_id is None: # 如果是顶级菜单,直接放入结果集 tree_data.append(current_node) else: # 如果是子菜单,找到它的父级,把它塞进父级的 children 里 if m.parent_id in menu_map: menu_map[m.parent_id]['children'].append(current_node) else: # 如果找不到父级(比如父级被删了),为了防止数据丢失,暂时作为顶级显示 tree_data.append(current_node) return tree_data @staticmethod def get_role_permissions(role_code): """获取指定角色拥有的所有权限Code""" try: # === 新增逻辑:超级管理员上帝模式 === if role_code == 'SUPER_ADMIN': # 直接获取所有菜单和元素,无视配置表 all_menus = [m.code for m in SysMenu.query.all()] all_elements = [e.code for e in SysElement.query.all()] return { 'menus': all_menus, 'elements': all_elements } # ================================= perms = SysRolePermission.query.filter_by(role_code=role_code).all() menu_codes = [] element_codes = [] for p in perms: # 这里假设你的数据库存的是 target_code if p.type == 'menu': menu_codes.append(p.target_code) else: element_codes.append(p.target_code) # 前端 handleRoleSelect 会合并这两个数组,所以分开返回没问题 return { 'menus': menu_codes, 'elements': element_codes } except Exception as e: # 记录日志或处理错误 print(f"Error fetching role permissions: {e}") return {'menus': [], 'elements': []} @staticmethod def assign_permissions(role_code, permissions): """ 保存角色的权限 permissions: 前端传来的 list,混合了 menu_code 和 element_code """ if not role_code: raise ValueError("角色代码不能为空") session = db.session try: # 1. 开启事务 (Flask-SQLAlchemy 自动管理,但明确逻辑更好) # 2. 删除该角色旧的所有权限 SysRolePermission.query.filter_by(role_code=role_code).delete() # 3. 准备新数据 if permissions: # 3.1 去重 unique_codes = set(permissions) # 3.2 预加载所有 Menu Code,用于区分是 Menu 还是 Element # 这一步很重要,因为 SysRolePermission 表需要 type 字段 all_menu_codes = {res[0] for res in session.query(SysMenu.code).all()} new_records = [] for code in unique_codes: if not code: continue # 判断类型:如果 code 存在于菜单表中,就是 menu,否则就是 element p_type = 'menu' if code in all_menu_codes else 'element' new_records.append(SysRolePermission( role_code=role_code, target_code=code, type=p_type )) # 3.3 批量插入 if new_records: session.add_all(new_records) # 4. 提交 session.commit() return True except SQLAlchemyError as e: session.rollback() raise e except Exception as e: session.rollback() raise e