一、 核心架构:RBAC 三巨头
本系统采用经典的 RBAC 模型,其核心思想是:用户不直接关联权限,而是通过"角色"作为中间桥梁。
1. 实体关系图 (ER Diagram)
整个权限体系由 三张实体表 和 两张关联表 支撑:
- 用户关联角色 关联
- 角色分配菜单 关联
system_user
bigint
id
我是谁
string
username
USER_ROLE
system_role
bigint
id
我是什么身份
string
code
如: admin, hr
ROLE_MENU
system_menu
bigint
id
我能干什么
string
permission
如: system:user:add
string
component
Vue组件路径
2. 表结构解析
system_user(用户表):代表"自然人"。只负责登录,不直接记录权限。system_role(角色表):代表"身份/职位"(如:超级管理员、HR、财务)。它是连接用户和资源的纽带。system_menu(菜单/权限表):代表"资源"。它不仅包含左侧可见的菜单,还包含页面内不可见的按钮(API 权限)。
二、 核心资产:深入理解 system_menu 表
system_menu 是权限控制中最复杂的表,它通过 type 字段将三种资源统一管理:
1. 三种形态 (Type)
| 类型 | 值 | 含义 | 作用 |
|---|---|---|---|
| 目录 | 1 | 左侧菜单的父节点 | 归纳子菜单,通常无实际页面(如"系统管理")。 |
| 菜单 | 2 | 具体的业务页面 | 点击后跳转路由,加载 Vue 组件(如"用户管理")。 |
| 按钮 | 3 | 页面内的操作/API | 不可见,仅用于控制按钮显示和后端接口鉴权(如"用户新增")。 |
2. 关键字段详解
-
path(路由地址): -
浏览器地址栏显示的 URL 后缀。
-
拼接规则 :
父级 path+子级 path= 最终访问地址。 -
例如 :
/system(目录) +user(菜单) =/system/user。 -
component(组件路径): -
对应前端 Vue 项目中的文件物理路径。
-
告诉前端路由去哪里加载代码。
-
例如 :
system/user/index对应src/views/system/user/index.vue。 -
permission(权限标识) :【核心】 -
权限的唯一身份证,通常采用
模块:资源:操作的格式。 -
例如 :
system:user:create(允许在系统模块下创建用户)。 -
它是连接后端
@PreAuthorize和前端v-hasPermi的唯一暗号。
三、 运行流程:从登录到鉴权
当一个用户登录并进行操作时,权限数据经历了以下流转:
阶段 1:登录 (后端计算与发放)
- 用户登录成功。
- 后端根据
User -> Role -> Menu的关联关系,查询出该用户拥有的所有permission字符串。 - 后端将这个 权限字符串列表 (如
['system:user:query', 'system:user:create']) 返回给前端。
阶段 2:渲染 (前端存储与展示)
- 前端将权限列表存入 Pinia/Vuex 全局 Store 中。
- 按钮级控制 :在加载页面时,Vue 自定义指令
v-hasPermi会工作。
- 代码 :
<el-button v-hasPermi="['system:user:create']">新增</el-button> - 逻辑 :去 Store 里找,如果没找到
system:user:create,直接从 DOM 中移除该按钮。
阶段 3:请求 (后端 AOP 拦截与裁决)
- 用户点击按钮,发起 API 请求(如
/system/user/create)。 - 请求到达 Controller 之前,被 Spring Security 的 AOP 切面 拦截。
- 注解生效 :
@PreAuthorize("@ss.hasPermission('system:user:create')")。
- 解析 :调用名为
ss的 Bean (PermissionService),执行hasPermission方法。 - 判断:再次检查当前用户的权限列表(通常缓存于 Redis 或 SecurityContext)是否包含该字符串。
- 结果:
- 包含 -> 放行(执行业务代码)。
- 不包含 -> 抛出 403 Forbidden 异常。
四、 代码实战示例
1. 后端 Controller 写法
利用 SpEL 表达式实现细粒度控制:
java
@RestController
@RequestMapping("/system/user")
public class UserController {
@Resource
private UserService userService;
// 只有拥有 'system:user:query' 权限才能访问
@GetMapping("/page")
@PreAuthorize("@ss.hasPermission('system:user:query')")
public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO reqVO) {
return success(userService.getUserPage(reqVO));
}
// 只有拥有 'system:user:delete' 权限才能访问
@DeleteMapping("/delete")
@PreAuthorize("@ss.hasPermission('system:user:delete')")
public CommonResult<Boolean> deleteUser(@RequestParam("id") Long id) {
userService.deleteUser(id);
return success(true);
}
}
2. 前端 Vue 写法
利用自定义指令控制显隐:
html
<template>
<el-button
type="primary"
plain
icon="el-icon-plus"
v-hasPermi="['system:user:create']"
@click="handleAdd"
>
新增
</el-button>
</template>
菜单表 (system_menu) 的三大"脊梁"字段
在 RBAC 权限体系中,system_menu 表不仅存储了权限数据,更承载了 前端路由生成 与 页面渲染 的重任。其中,parent_id、path、component 这三个字段构成了菜单系统的核心逻辑闭环。
1. parent_id:构建无限层级 (The Skeleton)
这是实现树形结构 的关键字段,采用经典的 邻接表 (Adjacency List) 设计模式。
-
含义:记录当前菜单的父节点 ID。
-
逻辑:
-
0:代表根节点(一级菜单/目录)。 -
非0:代表子节点,挂载在对应的父 ID 下。 -
作用 :后端通过递归算法将扁平的数据库记录转化为树形结构(Tree),前端据此渲染出层级分明的侧边栏菜单。
2. path:定义访问地址 (The URL)
这是 Vue Router 的路由标识,决定了浏览器地址栏显示什么 URL。
-
含义:路由访问路径。
-
拼接规则 :前端路由通常采用嵌套模式,最终访问地址 = 父级 Path + 子级 Path。
-
目录 (Type=1) :通常以
/开头,作为模块前缀(如/system)。 -
菜单 (Type=2) :通常是纯字符串,作为具体页面的后缀(如
user)。 -
示例 :当用户点击"用户管理"时,浏览器地址栏变为
/system/user。
3. component:映射代码文件 (The Content)
这是连接路由 与代码的桥梁,决定了页面具体加载哪个 Vue 文件。
- 含义 :前端 Vue 组件的物理文件路径(相对于
src/views/)。 - 关键用法:
- 目录 :通常配置为
Layout。这代表加载系统的标准布局框架(包含侧边栏、顶栏、面包屑),子菜单内容将渲染在 Layout 的"坑位" (<router-view>) 中。 - 菜单 :配置为具体的 Vue 文件路径。例如
system/user/index,对应前端工程中的src/views/system/user/index.vue文件。