文档目录
glen
· 预计阅读 4 分钟

第 5 章 菜单与角色授权:从零完成一次授权

本文导航
第 5 章 菜单与角色授权:从零完成一次授权

第 5 章 菜单与角色授权:从零完成一次授权

这一章按后台实际操作顺序走一遍:建角色 → 分配菜单 → 建用户 → 分配角色 → 用普通用户验证。每一步都给出接口,方便脚本化。

授权全流程

① 创建角色
② 查询可分配菜单,拿到菜单 ID
③ 给角色分配菜单(页面 + 按钮)
   └── 事件监听器把按钮权限写入 Casbin p 行
④ 创建用户
⑤ 给用户分配角色
   └── 事件监听器把用户-角色写入 Casbin g 行
⑥ 普通用户登录,验证菜单与权限

① 创建角色

POST /api/v1/roles
{
  "name": "运营",
  "code": "operator",
  "status": 1,
  "remark": "日常运营"
}

创建接口不返回 ID,需要再查列表:

GET /api/v1/roles?page=1&pageSize=20

返回的 list 里有角色 ID(十进制字符串)。

② 查询可分配菜单

GET /api/v1/menus/assignable

返回包含按钮的菜单树。节点 ID 是「逗号路径」,例如:

节点 ID
用户管理页面 SystemRbac,SystemUser
用户管理下的「创建用户」按钮 SystemRbac,SystemUser,Create
用户管理下的「分页获取用户」按钮 SystemRbac,SystemUser,Retrieve
角色管理页面 SystemRbac,SystemRole
角色管理下的「分配菜单给角色」按钮 SystemRbac,SystemRole,AssignMenusToRole

分配菜单时把这些 ID 放进数组。只给页面 ID 不够,按钮 ID 才携带接口权限。

③ 给角色分配菜单

PUT /api/v1/roles/{roleID}/menus
{
  "menuIds": [
    "SystemRbac,SystemUser",
    "SystemRbac,SystemUser,Create",
    "SystemRbac,SystemUser,Retrieve",
    "SystemRbac,SystemUser,Update",
    "SystemRbac,SystemUser,Detail"
  ]
}

处理过程:

  1. RoleService.AssignMenusToRole 把菜单关联保存到 admin_role_menu;
  2. 发布 AssignMenuToRole 事件;
  3. 监听器用 menuRepo.GetPermissionsByMenuIDs(menuIDs...) 收集权限;
  4. 先删除该角色的旧 policy,再写入新 policy。

写入后 casbin_rule 中会出现:

p, <roleID>, admin:user:Create
p, <roleID>, admin:user:Retrieve
p, <roleID>, admin:user:Update
p, <roleID>, admin:user:Detail

注意:分配是整份替换。第二次调用时没带的按钮会被取消授权。

④ 创建用户

POST /api/v1/users
{
  "username": "demo",
  "nickName": "演示用户",
  "password": "P@ssw0rd",
  "status": 1,
  "sex": 1
}

同样不返回 ID,用列表接口查:

GET /api/v1/users?page=1&pageSize=20&username=demo

⑤ 给用户分配角色

POST /api/v1/users/{userID}/roles
{
  "roleIDs": ["<roleID>"]
}

处理过程:

  1. 保存用户与角色的多对多关联;
  2. 发布 AssignRoleToUser 事件;
  3. 监听器先 RemoveFilteredGroupingPolicy(0, userID) 清掉该用户的旧分组;
  4. 再写入新的 g, userID, roleID。

casbin_rule 中出现:

g, <userID>, <roleID>

同样是整份替换:不传的角色会被取消。

⑥ 验证

用 demo 登录:

curl -X POST "http://127.0.0.1:8080/api/v1/users/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"demo","password":"P@ssw0rd"}'

拿返回的 accessToken 查权限:

curl "http://127.0.0.1:8080/api/v1/users/me/permissions" \
  -H "Authorization: Bearer <demo-token>"

期望包含刚分配的权限标识。再访问一个有权限的接口:

curl "http://127.0.0.1:8080/api/v1/users?page=1&pageSize=10" \
  -H "Authorization: Bearer <demo-token>"

再访问一个没分配的接口,期望 403:

curl -X DELETE "http://127.0.0.1:8080/api/v1/users/1" \
  -H "Authorization: Bearer <demo-token>"

菜单 ID 是怎么拼出来的

MenuRepository.GetMenusWithBtn() 会递归设置:

menu.ID = menu.Name                     // 根节点
child.ID = menu.ID + "," + child.Name   // 子节点

所以:

SystemRbac
SystemRbac,SystemUser
SystemRbac,SystemUser,Create

前端勾选树时拿到的就是这些字符串。它们也是 admin_role_menu.menu_id 里存的值。

只分配页面、不分配按钮会怎样

  • 用户能看到页面菜单;
  • 页面里的接口调用会 403,因为按钮权限没有写入 Casbin;
  • 前端如果做了按钮隐藏,按钮也不会显示。

正确做法是页面 ID 和需要的按钮 ID 一起勾选。

删除角色的注意事项

RoleService.DeleteRole 当前只删除角色记录,不会自动清理:

  • admin_user_role 关联;
  • admin_role_menu 关联;
  • casbin_rule 里该角色的 policy 和用户分组。

删除前应先解除用户关联、清空角色菜单,或手工清理策略,避免留下悬空权限。

自检

  • [ ] 能独立完成「建角色 → 分配菜单 → 建用户 → 分配角色」;
  • [ ] 知道菜单 ID 是逗号路径;
  • [ ] 知道页面菜单和按钮菜单要一起分配;
  • [ ] 知道分配是整份替换;
  • [ ] 会用普通用户验证,而不是只看超管。

评论

加载评论中…

上一篇 第 4 章 权限标识与路由:app:module:方法名 下一篇 第 6 章 权限计算与生效:菜单、策略与缓存

BitLabs © 2026 · 论坛讨论 · 博客长文 · 文档手册