174 lines
6.9 KiB
Markdown
174 lines
6.9 KiB
Markdown
:::tip 菜单管理
|
||
菜单管理是 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)
|
||
```
|
||
|
||
:::tip 权限节点生命周期
|
||
新增/编辑菜单时,`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/list` | GET | `sys:menu:list` | 菜单列表 |
|
||
| `/api/v1/menu/detail/{id}` | GET | `sys:menu:detail` | 菜单详情(含权限节点 checkedList) |
|
||
| `/api/v1/menu/add` | POST | `sys:menu:add` | 新增菜单 |
|
||
| `/api/v1/menu/update` | PUT | `sys:menu:update` | 编辑菜单 |
|
||
| `/api/v1/menu/delete/{id}` | DELETE | `sys:menu:delete` | 删除菜单 |
|
||
|
||
:::warning 父级校验
|
||
编辑菜单时会校验父级:不能设为自身、不能设为自己的下级(沿父链向上走检测环路),避免菜单树成环导致渲染死循环。
|
||
:::
|
||
|
||
## 总结
|
||
|
||
菜单管理模块具备以下特点:
|
||
|
||
```
|
||
1. 树形结构:通过 parent_id 形成多级菜单树,非递归构建
|
||
2. 权限节点:type=1 按钮节点由 set_permission 自动生成和维护
|
||
3. 权限格式:sys:module:action 三段式,sort 值映射操作类型
|
||
4. 缓存联动:菜单增删改后通过 invalidate_menu_change 清理全量权限缓存
|
||
5. 环路防护:编辑时检测父级不能是自身或下级
|
||
6. 级联删除:删除菜单时一并软删其下权限节点和 role_menu 关联
|
||
```
|