Skip to content

角色与菜单权限 ​

概述 ​

系统管理中的「角色与菜单权限」模块基于 RBAC(Role-Based Access Control,基于角色的访问控制)模型,统一解决了 谁能登录、能看到哪些菜单、能操作哪些按钮、能看哪些数据 四类问题。它位于后端 module_system(role / menu / permission 三个子模块),配合认证模块在登录时一次性下发权限快照给前端。

核心概念 ​

整个权限体系由三个实体与两组关联表组成:

  • 角色(Role):权限的集合载体,用户通过被赋予角色来获得权限。
  • 菜单(Menu):既承担「前端导航菜单 / 路由」的职责,也承载「功能权限标识(权限码)」。目录、菜单用于界面呈现,按钮用于标识一个具体可操作的功能点。
  • 权限分配(Permission):不是独立实体,而是把 角色↔菜单、角色↔数据范围、用户↔角色 三类关系打通的编排服务。

实体之间的关系如下:

  • system_user_role:用户与角色的多对多关联。
  • system_role_menu:角色与菜单的多对多关联,是「一个角色能访问哪些菜单」的最终落点。
  • 菜单通过 parent_id 自引用形成树形结构(目录 → 菜单 → 按钮)。

角色管理 ​

image-20260930112942113

角色信息存储在 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:导出 Excel
  • GET /simple-list:查询精简角色列表(仅启用状态,供前端下拉框选择)

删除角色时有两道保护:内置角色(type=1)不可删除;已分配给用户的角色必须先用解除用户关联再删除,避免静默剥夺用户权限。

菜单管理 ​

image-20260930113018923

菜单信息存储在 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 自动归一)
status0 启用、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-menusystem:permission:assign-role-menu给角色分配菜单(覆盖式写入 system_role_menu)
POST /assign-role-data-scopesystem:permission:assign-role-data-scope设置角色的数据权限范围与指定部门
GET /list-user-roles?userId=system:user:query查询某用户已有的角色 ID 列表
POST /assign-user-rolesystem: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生成可访问路由与侧边栏。
  • 权限码写入 access store 的 accessCodes,业务组件通过 useAccess().hasAccessByCodes(['system:user:create']) 或 v-access 指令做按钮级鉴权。

典型操作链路 ​

以一个新功能「用户管理」上线为例:

  1. 在菜单管理中新建目录「系统管理」→ 新建菜单「用户管理」(填写路由与组件)→ 在「用户管理」下新建若干个按钮菜单(如「新增」「导出」),并分别填写 permission(system:user:create、system:user:export)。
  2. 在角色管理中找到目标角色,打开「菜单权限」弹窗,勾选上述目录、菜单与按钮。
  3. 若需要限制该角色的数据范围,打开「数据权限」弹窗选择范围(如「本部门及以下」)。
  4. 在用户管理中把该角色分配给相应用户。
  5. 用户重新登录(或刷新权限信息)后,即可看到新菜单,并在界面上按权限码看到对应的操作按钮。