外观
角色与菜单权限
概述
系统管理中的「角色与菜单权限」模块基于 RBAC(Role-Based Access Control,基于角色的访问控制)模型,统一解决了 谁能登录、能看到哪些菜单、能操作哪些按钮、能看哪些数据 四类问题。它位于后端 module_system(role / menu / permission 三个子模块),配合认证模块在登录时一次性下发权限快照给前端。
核心概念
整个权限体系由三个实体与两组关联表组成:
- 角色(Role):权限的集合载体,用户通过被赋予角色来获得权限。
- 菜单(Menu):既承担「前端导航菜单 / 路由」的职责,也承载「功能权限标识(权限码)」。目录、菜单用于界面呈现,按钮用于标识一个具体可操作的功能点。
- 权限分配(Permission):不是独立实体,而是把 角色↔菜单、角色↔数据范围、用户↔角色 三类关系打通的编排服务。
实体之间的关系如下:
system_user_role:用户与角色的多对多关联。system_role_menu:角色与菜单的多对多关联,是「一个角色能访问哪些菜单」的最终落点。- 菜单通过
parent_id自引用形成树形结构(目录 → 菜单 → 按钮)。
角色管理

角色信息存储在 system_role 表,关键字段含义:
| 字段 | 说明 |
|---|---|
name | 角色名称 |
code | 角色标识,字母开头、仅含字母/数字/下划线,全局唯一 |
sort | 显示顺序 |
status | 状态:0 正常、1 停用(停用的角色不会被加载进用户权限) |
type | 角色类型:1 内置角色、2 自定义角色(内置角色禁止删除) |
data_scope | 数据范围,见下文「数据权限」 |
data_scope_dept_ids | 当数据范围为「指定部门」时,关联的部门 ID 集合 |
角色管理提供以下接口(统一前缀 /system/role,功能权限码 system:role:*):
GET /page:分页查询角色列表GET /get:查询角色详情POST /create、PUT /update:新建、修改角色DELETE /delete、DELETE /delete-list:删除 / 批量删除角色GET /export-excel:导出 ExcelGET /simple-list:查询精简角色列表(仅启用状态,供前端下拉框选择)
删除角色时有两道保护:内置角色(type=1)不可删除;已分配给用户的角色必须先用解除用户关联再删除,避免静默剥夺用户权限。
菜单管理

菜单信息存储在 system_menu 表,它同时承担「界面菜单」和「功能权限点」两种身份:
| 字段 | 说明 |
|---|---|
name | 菜单名称 |
type | 菜单类型:1 目录、2 菜单、3 按钮、4 外链/内嵌 |
permission | 功能权限标识,如 system:user:query(通常挂在按钮类型上) |
path component component_name | 路由地址、组件路径、组件名(用于前端生成路由) |
icon visible keep_alive always_show | 图标、是否可见、是否缓存、是否始终显示 |
parent_id | 父菜单 ID,顶级菜单为 null(前端传 0 自动归一) |
status | 0 启用、1 停用 |
菜单类型与层级约束
菜单按类型存在严格的父子约束,后端在新增/修改时校验,避免结构错乱:
- 目录、菜单必须有
path;菜单还必须有component_name与component。 - 按钮(type=3)本身不进入导航树,它只是用来承载一个
permission权限码。 - 删除菜单会递归删除其全部子孙节点。
菜单管理接口(前缀 /system/menu,功能权限码 system:menu:*):
GET /list:扁平菜单列表(前端自行组装树)GET /tree:嵌套菜单树(后端直接组装好 children)GET /simple-list:精简列表(仅id/name/parent_id/type,用于分配菜单时的勾选树)GET /get、POST /create、PUT /update、DELETE /delete、DELETE /delete-list
权限分配
/system/permission 模块本身不存储数据,它编排上述关联关系,提供「分配」与「查询」两类能力:
| 接口 | 权限码 | 作用 |
|---|---|---|
GET /list-role-menus?roleId= | system:role:query | 查询某角色已分配的菜单 ID 列表 |
POST /assign-role-menu | system:permission:assign-role-menu | 给角色分配菜单(覆盖式写入 system_role_menu) |
POST /assign-role-data-scope | system:permission:assign-role-data-scope | 设置角色的数据权限范围与指定部门 |
GET /list-user-roles?userId= | system:user:query | 查询某用户已有的角色 ID 列表 |
POST /assign-user-role | system:user:update | 给用户分配角色(覆盖式写入 system_user_role) |
「分配角色菜单」与「分配用户角色」都是覆盖式写入:传入的 ID 列表即为该角色/用户最终拥有的全部菜单或角色,调用后会清除旧关联再建立新关联。
数据权限
除了「功能权限」(能不能点某个按钮),系统还支持数据权限:同一界面下,不同角色能看到的数据范围不同。角色上的 data_scope 字段控制该范围,取值如下:
1 全部数据:不加任何过滤条件。2 指定部门:结合data_scope_dept_ids指定的部门集合(可访问这些部门的数据)。3 本部门/4 本部门及以下:基于当前用户所属部门动态计算,其中「及以下」会递归包含子部门。5 仅本人数据:只能看到自己创建的数据。
数据权限在框架层(app/framework/security/permission.py)由 Permission 过滤器统一处理:当业务模型声明了 DATA_SCOPE 策略时,查询会自动按角色并集拼接部门 IN 条件或归属人 = 条件;若解析不出任何权限则按最小权限原则拒绝(返回空集),避免越权泄露。平台级公共数据(如字典)声明为 NONE 策略,不做过滤。
注意:多个角色的数据权限按「并集」合并,但每个角色独立计算自身范围后再合并,不会让「A 角色的自定义部门」与「B 角色的本部门」互相放大。
前端集成
前端代码位于 apps/web-ele/src/views/system/{role,menu},配套 API 封装在 apps/web-ele/src/api/system/{role,menu,permission}。
管理页面
- 角色管理页(
views/system/role/index.vue):表格展示角色,行内操作支持编辑、删除,下拉操作支持「数据权限」「菜单权限」两个分配弹窗。 - 菜单管理页(
views/system/menu/index.vue):以树形表格展示菜单,支持新增下级、编辑、删除。 - 分配菜单弹窗(
views/system/role/modules/assign-menu-form.vue):拉取/menu/simple-list生成勾选树,回显/permission/list-role-menus已选菜单,提交时调用/permission/assign-role-menu,并支持「全选 / 全部展开」。 - 分配数据权限弹窗(
views/system/role/modules/assign-data-permission-form.vue):选择数据范围,选择「指定部门」时联动展示部门范围字段。
页面中的按钮显隐直接通过 auth: ['system:role:create'] 等权限码声明,由前端 v-access 指令统一控制。
登录后的权限下发
用户登录成功后,前端会调用 GET /auth/get-permission-info(PermissionInfoService),后端聚合并返回权限快照:
json
{
"user": { "id": 1, "username": "admin", ... },
"roles": ["admin"],
"menus": [ { "id": 1, "parentId": 0, "name": "...", "path": "...", "component": "...", "children": [...] } ],
"permissions": ["system:user:query", "system:user:create", ...]
}其处理流程如下:
要点:
- 菜单树只保留启用状态、且非按钮类型的菜单(按钮无路由信息,不进入导航)。
- 权限码(permissions) 单独从角色的全部菜单中收集按钮类型(type=3)上的
permission字段——因为菜单树已排除按钮,所以必须从原始菜单集合里另外提取,否则权限码永远为空。 - 前端将菜单树交给
generate-routes-backend生成可访问路由与侧边栏。 - 权限码写入
accessstore 的accessCodes,业务组件通过useAccess().hasAccessByCodes(['system:user:create'])或v-access指令做按钮级鉴权。
典型操作链路
以一个新功能「用户管理」上线为例:
- 在菜单管理中新建目录「系统管理」→ 新建菜单「用户管理」(填写路由与组件)→ 在「用户管理」下新建若干个按钮菜单(如「新增」「导出」),并分别填写
permission(system:user:create、system:user:export)。 - 在角色管理中找到目标角色,打开「菜单权限」弹窗,勾选上述目录、菜单与按钮。
- 若需要限制该角色的数据范围,打开「数据权限」弹窗选择范围(如「本部门及以下」)。
- 在用户管理中把该角色分配给相应用户。
- 用户重新登录(或刷新权限信息)后,即可看到新菜单,并在界面上按权限码看到对应的操作按钮。