Skip to content

菜单管理

菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。支持目录/菜单(type=0)和权限按钮节点(type=1)两种类型,通过树形结构组织。权限节点由系统根据前端勾选自动生成,采用 sys:module:action 三段式命名。

菜单类型

type名称说明用途
0目录/菜单页面路由前端侧边栏分组 + 路由生成
1节点权限按钮控制操作权限(由系统自动生成)

菜单模型

python
# src/modules/system/menu/models.py
# ============================================================
# 菜单模型
# ============================================================
class Menu(base_model, base_db):
    """菜单模型类"""
    # ============================================================
    # 表名配置
    # ============================================================
    __tablename__ = DB_PREFIX + "menu"
    __table_comment__ = "菜单表"

    # ============================================================
    # 字段定义
    # ============================================================
    # 菜单名称
    name = Column(String(150), nullable=False, index=True, comment="菜单名称")
    # 菜单图标
    icon = Column(String(50), nullable=True, comment="菜单图标")
    # 菜单图标(备用)
    icon2 = Column(String(50), nullable=True, comment="菜单图标")
    # 上级ID
    parent_id = Column(Integer, default=0, server_default=text('0'), index=True, comment="上级ID")
    # 菜单路径
    path = Column(String(255), nullable=True, comment="菜单路径")
    # 菜单组件
    component = Column(String(255), nullable=True, comment="菜单组件")
    # 权限节点
    permission = Column(String(150), nullable=True, comment="权限节点")
    # 打开方式:0-组件 1-内链 2-外链
    target = Column(Integer, default=1, server_default=text('1'), comment="打开方式:0-组件 1-内链 2-外链")
    # 菜单类型:0-菜单 1-节点
    type = Column(Integer, default=0, server_default=text('0'), index=True, comment="菜单类型:0-菜单 1-节点")
    # 状态选项
    status = Column(Integer, default=0, server_default=text('0'), index=True, comment="菜单状态:0-正常 1-停用")
    # 是否可见:0-可见 1-不可见
    hide = Column(Integer, default=1, server_default=text('1'), comment="是否可见:0-可见 1-不可见")
    # 排序
    sort = Column(Integer, default=0, server_default=text('0'), comment="排序")
    # 备注
    note = Column(String(255), nullable=True, comment="备注")

    # ============================================================
    # 内置方法
    # ============================================================
    def __str__(self):
        """返回菜单ID作为字符串表示"""
        return "菜单{}".format(self.id)

权限节点自动生成

当菜单类型为目录(type=0)且勾选了权限按钮时,系统自动在该菜单下生成 type=1 的权限节点。按钮 sort 值与权限操作的映射关系:

python
# src/modules/system/menu/service.py
_BUTTON_SORT_VALUES = (1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65)

# sort 值与权限操作的映射
1 → 查询(sys:{module}:page)
5 → 添加(sys:{module}:add)
10 → 修改(sys:{module}:update)
15 → 删除(sys:{module}:delete)
20 → 详情(sys:{module}:detail)
25 → 设置状态(sys:{module}:status)
30 → 批量删除(sys:{module}:batchDelete)
50 → 导出(sys:{module}:export)
55 → 导入(sys:{module}:import
60 → 分配权限(sys:{module}:permission)
65 → 重置密码(sys:{module}:resetPwd)

权限节点生命周期

新增/编辑菜单时,set_permission 函数根据前端传入的 checkedList 数组,复用已有节点(保持 id 稳定,role_menu 授权不失效)或新建节点。本次未勾选的历史节点自动软删。删除菜单时,其下所有 type=1 权限节点一并软删,并清理 role_menu 关联。

菜单树构建

使用 children_map 字典实现非递归树构建,避免深层递归性能问题:

python
# src/modules/system/menu/service.py
def get_tree(data, parent_id):
    """获取树状结构(非递归,children_map 分组)"""
    children_map = {}
    for item in data:
        pid = item["parentId"]
        if pid not in children_map:
            children_map[pid] = []
        children_map[pid].append(item)

    def build(pid):
        result = []
        for item in children_map.get(pid, []):
            item["children"] = build(item["id"])
            result.append(item)
        return result

    return build(parent_id)

用户权限查询

权限查询通过 ORM join 实现,仅有效角色(未删除、未禁用)的权限生效:

python
# src/modules/system/menu/service.py

def get_permissions_list_sync(user_id):
    """同步查询用户权限节点"""
    if user_id == 1:
        menu_list = menu_repo.get_all(type=1)  # 超管拥有全部权限
    else:
        menu_list = (
            menu_repo.filter()
            .join(RoleMenu, RoleMenu.menu_id == Menu.id)
            .join(UserRole, UserRole.role_id == RoleMenu.role_id)
            .join(Role, Role.id == UserRole.role_id)
            .filter(
                and_(
                    UserRole.user_id == user_id,
                    or_(Menu.type == 1, and_(Menu.type == 0, Menu.permission != "")),
                    Menu.status == 0,
                    Menu.is_delete == 0,
                    Role.is_delete == 0,
                )
            )
            .distinct()
            .all()
        )
    return [item.permission for item in (menu_list or [])]

API 接口

接口方法权限节点说明
/api/v1/menu/listGETsys:menu:list菜单列表
/api/v1/menu/detail/{id}GETsys:menu:detail菜单详情(含权限节点 checkedList)
/api/v1/menu/addPOSTsys:menu:add新增菜单
/api/v1/menu/updatePUTsys:menu:update编辑菜单
/api/v1/menu/delete/{id}DELETEsys:menu:delete删除菜单

父级校验

编辑菜单时会校验父级:不能设为自身、不能设为自己的下级(沿父链向上走检测环路),避免菜单树成环导致渲染死循环。

总结

菜单管理模块具备以下特点:

1. 树形结构:通过 parent_id 形成多级菜单树,非递归构建
2. 权限节点:type=1 按钮节点由 set_permission 自动生成和维护
3. 权限格式:sys:module:action 三段式,sort 值映射操作类型
4. 缓存联动:菜单增删改后通过 invalidate_menu_change 清理全量权限缓存
5. 环路防护:编辑时检测父级不能是自身或下级
6. 级联删除:删除菜单时一并软删其下权限节点和 role_menu 关联

小蚂蚁云团队 · 提供技术支持