refactor: rebuild permission tree and improve assignment with error handling
This commit is contained in:
@ -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. 组装树结构
|
||||
tree_data = []
|
||||
# --- 核心逻辑:构建树形结构 ---
|
||||
|
||||
for menu in menus:
|
||||
menu_dict = menu.to_dict()
|
||||
# 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
|
||||
|
||||
# 找该菜单下的所有元素
|
||||
children = []
|
||||
# 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:
|
||||
if el.menu_code == menu.code:
|
||||
children.append(el.to_dict())
|
||||
# 找到该元素所属菜单的 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)
|
||||
|
||||
# 如果有子元素,加到 children
|
||||
if children:
|
||||
menu_dict['children'] = children
|
||||
# 6. 将子菜单挂载到父菜单下,并构建最终的树
|
||||
tree_data = []
|
||||
for m in menus:
|
||||
current_node = menu_map[m.id]
|
||||
|
||||
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"""
|
||||
try:
|
||||
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, 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 Exception as e:
|
||||
# 发生异常时回滚,防止脏事务
|
||||
db.session.rollback()
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
raise e
|
||||
except Exception as e:
|
||||
session.rollback()
|
||||
raise e
|
||||
Reference in New Issue
Block a user