RBAC 菜单查询的“标准写法”

一、先看完整 SQL(原样)

csharp 复制代码
select m.*
from menu m
left join role_menu rm on rm.menu_id = m.id
where rm.role_id in (
  select role_id from user_role where user_id = ?
)
order by m.parent_id, m.sort;

二、先搞清楚 4 张表在干什么(非常关键)

1️⃣ user(用户)

bash 复制代码
user
┌────┬──────────┐
│ id │ username │
└────┴──────────┘

2️⃣ role(角色)

bash 复制代码
role
┌────┬──────────┐
│ id │ role_code│
└────┴──────────┘

3️⃣ user_role(用户-角色关系)

一个用户可以有多个角色

复制代码
user_role
┌────────┬────────┐
│ user_id│ role_id│
└────────┴────────┘

bash 复制代码
menu
┌────┬────────┬──────────┬─────────┬──────┐
│ id │parent_id│ name     │ path    │ sort │
└────┴────────┴──────────┴─────────┴──────┘

parent_id 用来表示菜单层级(父子菜单)


5️⃣ role_menu(角色-菜单关系)

角色能访问哪些菜单

复制代码
role_menu
┌────────┬────────┐
│ role_id│ menu_id│
└────────┴────────┘

三、整体一句话解释这条 SQL 在干嘛

"查出某个用户 → 所有角色 → 能访问的所有菜单,并按菜单层级排序"


四、逐句拆解(重点)


🔹 ① 子查询:先查用户有哪些角色

csharp 复制代码
select role_id from user_role where user_id = ?

假设:

ini 复制代码
user_id = 10

user_role 表:

user_id role_id
10 1
10 2

子查询结果:

scss 复制代码
(1, 2)

👉 意思:这个用户有 2 个角色


🔹 ② where rm.role_id in (...)

bash 复制代码
where rm.role_id in (1, 2)

👉 只要是角色 1 或 角色 2 拥有的菜单,都要


🔹 ③ left join role_menu

bash 复制代码
left join role_menu rm on rm.menu_id = m.id

作用一句话:

把"菜单"和"角色-菜单关系"连起来

示例 role_menu 表:

role_id menu_id
1 101
1 102
2 103

🔹 ④ select m.*

csharp 复制代码
select m.*

👉 只要菜单字段,不关心 role_menu 的字段

最终你得到的,是:

css 复制代码
menu 表中的数据

五、整体执行流程(一步一步)

🧠 数据库真实执行逻辑是这样的:

Step 1️⃣:查用户角色

ini 复制代码
user_id = 10
↓
角色 = [1, 2]

Step 2️⃣:查这些角色能访问的菜单

css 复制代码
role 1 → menu 101, 102
role 2 → menu 103

Step 3️⃣:合并去重后菜单

css 复制代码
menu 101
menu 102
menu 103

Step 4️⃣:排序

vbnet 复制代码
order by m.parent_id, m.sort

排序目的:

  • 先按父菜单分组
  • 再按菜单顺序排列

👉 方便前端直接生成树形菜单


六、为什么要用 LEFT JOIN 而不是 INNER JOIN

实际上:

sql 复制代码
LEFT JOIN + where rm.role_id in (...)

⬇️
效果等同于 INNER JOIN

那为什么很多人还写 LEFT JOIN?

1️⃣ 写法更通用

2️⃣ 以后可以扩展(比如允许空角色)

3️⃣ 历史代码习惯

👉 如果你愿意,完全可以改成:

csharp 复制代码
select m.*
from menu m
inner join role_menu rm on rm.menu_id = m.id
where rm.role_id in (
  select role_id from user_role where user_id = ?
)
order by m.parent_id, m.sort;

七、为什么不用三表直接 join?

你也可以写成:

vbnet 复制代码
select distinct m.*
from user_role ur
join role_menu rm on rm.role_id = ur.role_id
join menu m on m.id = rm.menu_id
where ur.user_id = ?
order by m.parent_id, m.sort;

两种写法对比

写法 优点
子查询 in 易读、好理解
多表 join 性能稍好、专业

👉 中小项目两种都完全没问题


八、最终返回的数据长什么样?

css 复制代码
[  {    "id": 1,    "parent_id": 0,    "name": "系统管理",    "path": "/system",    "sort": 1  },  {    "id": 2,    "parent_id": 1,    "name": "用户管理",    "path": "/system/user",    "sort": 1  }]

前端可以 直接递归生成菜单树


九、这条 SQL 在 RBAC 中的"地位"

👉 90% 的后台系统,菜单查询就是这一类 SQL

你学会这一条,就已经:

  • ✔ 理解了 RBAC 的核心
  • ✔ 能独立设计菜单权限
  • ✔ 看得懂大多数后台源码
相关推荐
乘风!2 小时前
服务器上部署的Mysql,服务器上可登录成功,远程电脑无法连接的
mysql
梓沂3 小时前
解决项目容器启动时MySQL端口检测的问题
数据库·mysql
soft20015253 小时前
MySQL 8.0.39 Rocky Linux 一键安装脚本(完整可直接运行)
linux·mysql·adb
木风小助理3 小时前
子查询与 JOIN 查询性能比较:执行机制与适用场景解析
数据库·sql·mysql
九章-4 小时前
智慧文旅信创落地新标杆:四川省文旅厅完成MySQL 5.7平滑替换,筑牢省级管理平台自主可控底座
数据库·mysql
蟹至之4 小时前
【MySQL】事务
数据库·mysql·事务
ao_lang4 小时前
数据库范式
数据库·mysql
子超兄4 小时前
MVCC机制简介
数据库·mysql
yuguo.im4 小时前
如何查看 Mysql 版本
数据库·mysql