diff --git a/inventory-backend/app/services/permission_service.py b/inventory-backend/app/services/permission_service.py index 10faf36..7988941 100644 --- a/inventory-backend/app/services/permission_service.py +++ b/inventory-backend/app/services/permission_service.py @@ -1,7 +1,6 @@ -# inventory-backend/app/services/permission_service.py - from app.models.system import SysMenu, SysElement, SysRolePermission from app.extensions import db +from sqlalchemy.exc import SQLAlchemyError class PermissionService: @@ -9,84 +8,112 @@ class PermissionService: @staticmethod def get_permission_tree(): """ - 获取完整的权限树(菜单 -> 元素) + 获取完整的权限树(菜单嵌套菜单 + 菜单包含元素) 供前端权限配置页面展示 """ - # 1. 获取所有菜单 - menus = SysMenu.query.order_by(SysMenu.sort_order).all() - + # 1. 获取所有菜单 (按 parent_id 和 sort_order 排序,保证父子处理顺序) + menus = SysMenu.query.order_by(SysMenu.parent_id, SysMenu.sort_order).all() # 2. 获取所有元素 elements = SysElement.query.all() - # 3. 组装树结构 + # --- 核心逻辑:构建树形结构 --- + + # 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] - for menu in menus: - menu_dict = menu.to_dict() - - # 找该菜单下的所有元素 - children = [] - for el in elements: - if el.menu_code == menu.code: - children.append(el.to_dict()) - - # 如果有子元素,加到 children - if children: - menu_dict['children'] = children - - tree_data.append(menu_dict) + 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""" - perms = SysRolePermission.query.filter_by(role_code=role_code).all() + try: + perms = SysRolePermission.query.filter_by(role_code=role_code).all() - menu_codes = [] - element_codes = [] + menu_codes = [] + element_codes = [] - for p in perms: - if p.type == 'menu': - menu_codes.append(p.target_code) - else: - element_codes.append(p.target_code) + for p in perms: + # 这里假设你的数据库存的是 target_code + if p.type == 'menu': + menu_codes.append(p.target_code) + else: + element_codes.append(p.target_code) - return { - 'menus': menu_codes, - 'elements': element_codes - } + # 前端 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, permission_codes): + def assign_permissions(role_code, permissions): """ 保存角色的权限 - permission_codes: 前端传来的 list,包含 menu_code 和 element_code + permissions: 前端传来的 list,混合了 menu_code 和 element_code """ if not role_code: raise ValueError("角色代码不能为空") + session = db.session try: - # ========= 1️⃣ 先删除旧权限 ========= - SysRolePermission.query.filter_by(role_code=role_code) \ - .delete(synchronize_session=False) + # 1. 开启事务 (Flask-SQLAlchemy 自动管理,但明确逻辑更好) - # ========= 2️⃣ 去重(关键修复点) ========= - # 防止前端传来重复 code 导致 UNIQUE 冲突 - unique_codes = set(permission_codes) if permission_codes else set() + # 2. 删除该角色旧的所有权限 + SysRolePermission.query.filter_by(role_code=role_code).delete() - # ========= 3️⃣ 批量添加新权限 ========= - if unique_codes: - # 预先获取所有菜单代码,用于判断类型 - all_menu_codes = {m.code for m in SysMenu.query.all()} + # 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 + if not code: continue - # 判断类型 + # 判断类型:如果 code 存在于菜单表中,就是 menu,否则就是 element p_type = 'menu' if code in all_menu_codes else 'element' new_records.append(SysRolePermission( @@ -95,14 +122,17 @@ class PermissionService: type=p_type )) + # 3.3 批量插入 if new_records: - db.session.add_all(new_records) + session.add_all(new_records) - # ========= 4️⃣ 提交事务 ========= - db.session.commit() + # 4. 提交 + session.commit() return True + except SQLAlchemyError as e: + session.rollback() + raise e except Exception as e: - # 发生异常时回滚,防止脏事务 - db.session.rollback() + session.rollback() raise e \ No newline at end of file