应分menu、permission、role_permission三张表,menu存节点,permission存resource+action,role_permission关联角色与权限,且menu.url须与permission.resource严格对齐。

菜单权限数据模型怎么建才不翻车
菜单权限必须支持树形结构和细粒度操作控制,否则后期加个“导出按钮权限”就得改表结构。别用单张表硬扛所有字段,Beego 项目里常见错误是把 menu 和 permission 合并在一张表里,结果 Type 字段膨胀到 5 种取值,查询逻辑越来越绕。
推荐分三张表:
- menu:只存菜单节点(id、name、parent_id、url、sort、status)
- permission:只存操作行为(如 "user:export"、"order:audit"),带 resource(对应 URL 路径)和 action(GET/POST/DELETE)
- role_permission:角色与权限的中间表,不要冗余存菜单 ID
注意:menu.url 和 permission.resource 必须对齐。比如菜单项跳转到 /admin/user/list,那对应权限的 resource 就得是 /admin/user/list,不能写成 /user/list —— Beego 的 beego.Router() 是按注册路径匹配的,前端菜单渲染和后端鉴权必须用同一套路径标识。
Beego 中如何拦截菜单级路由并校验权限
不能只靠前端隐藏菜单就认为安全了。Beego 没有内置 RBAC 中间件,得自己在 Controller.Prepare() 里做统一拦截。关键点不是“有没有权限”,而是“当前用户能访问这个 URL + HTTP 方法吗”。
实操建议:
- 在 BaseController.Prepare() 中调用自定义函数 checkPermission(r *http.Request, userRoleIDs []int)
- 先从 r.URL.Path 和 r.Method 构造 key,例如 "GET:/admin/user/list"
- 查询 role_permission 表,看任一 role_id 是否关联该 key 对应的 permission.id
- 如果没查到,直接 this.Abort("403"),别返回重定向或空数据 —— 那会掩盖真实权限问题
容易踩的坑:Prepare() 执行时机早于 URLRouter 解析,所以不能依赖 this.Ctx.Input.Param(":id");路径必须用原始 r.URL.Path,否则带参数的路由(如 /user/:id)会匹配失败。
Casbin 集成时为什么 getPermissionsForUser 返回为空
GetPermissionsForUser() 只返回直接赋予用户的权限,不递归查角色继承的权限。这是 Casbin 的设计特性,不是 bug。你在 Beego 里看到用户没菜单,大概率是因为只调了这一个方法,漏掉了角色链路。
正确做法是组合调用:
- e.GetRolesForUser("u123") 获取用户所有角色
- 对每个角色循环调用 e.GetPermissionsForUser("r_admin")
- 合并去重后,再比对当前请求的 resource+action
性能提示:如果角色数多、权限项多,别在每次请求里反复查数据库。建议启动时预加载全部 role → permissions 映射到内存 map,Beego 的 app.conf 可配 runmode = dev 下热重载,但权限映射这种静态数据,初始化一次就够了。
前端菜单渲染时如何避免“有权限却看不到菜单”
后端返回菜单列表时,不能只查 menu 表然后硬塞给前端。用户可能有“用户管理”菜单权限,但没“用户导出”按钮权限 —— 这时候菜单要显示,但按钮得禁用。所以菜单接口必须返回两层信息:菜单节点 + 节点下各操作的权限状态。
建议返回结构示例:
{"id": 5, "name": "用户管理", "url": "/admin/user/list", "children": [{"action": "export", "has_perm": true}, {"action": "reset_pwd", "has_perm": false}]}
生成逻辑:先查出用户可见的所有 menu(通过角色关联的 permission 反推),再对每个菜单,查它下属的操作权限(比如 menu_id=5 对应的 permission 记录中 resource="/admin/user/list" 的那些)。
特别注意:Beego 的 JSON 输出默认不忽略零值字段,如果用 struct 直接序列化,has_perm 是 bool 类型,false 也会透出。建议用 map[string]interface{} 动态构造响应,或者给 struct 字段加 json:",omitempty" 标签。
权限系统最麻烦的从来不是“怎么存”,而是“什么时候查、查几层、查完怎么用”。菜单渲染、路由拦截、按钮显隐,表面是三个地方,背后共用一套权限判定逻辑。一旦拆开维护,很快就会出现“菜单能点但报 403”或者“按钮一直灰着点不了”这类问题。保持 resource 字符串格式统一、权限判定入口唯一、前端只消费后端明确返回的状态,比堆功能重要得多。











