签入版本
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
:::tip 菜单管理
|
||||
菜单管理是 RBAC 权限系统的基础,负责管理系统的导航菜单和权限节点。支持目录/菜单(type=0)和权限按钮节点(type=1)两种类型,通过树形结构组织。权限节点由系统根据前端勾选自动生成,采用 `sys:module:action` 三段式命名。
|
||||
:::
|
||||
|
||||
## 菜单类型
|
||||
|
||||
| type | 名称 | 说明 | 用途 |
|
||||
|------|------|------|------|
|
||||
| `0` | 目录/菜单 | 页面路由 | 前端侧边栏分组 + 路由生成 |
|
||||
| `1` | 节点 | 权限按钮 | 控制操作权限(由系统自动生成) |
|
||||
|
||||
## 菜单模型
|
||||
|
||||
```python
|
||||
# src/modules/menu/models.py
|
||||
# ============================================================
|
||||
# 菜单模型
|
||||
# ============================================================
|
||||
class Menu(BaseModel):
|
||||
"""菜单模型类"""
|
||||
# ============================================================
|
||||
# 表名配置
|
||||
# ============================================================
|
||||
__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/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/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/menu/service.py
|
||||
|
||||
def get_permissions_list_sync(user_id):
|
||||
"""同步查询用户权限节点"""
|
||||
if user_id == 1:
|
||||
menu_list = menu_repo.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 关联
|
||||
```
|
||||
Reference in New Issue
Block a user