要拆,但需按业务动作粒度决定:固定操作(如查看/编辑)可合并在菜单表加action字段;若需支持“导出excel”等专属动作,则必须拆出permission表并用permission_code唯一标识。

权限表要不要拆成「角色-菜单-操作」三张表?
要拆,但不是无脑三张表。核心矛盾在于:菜单和操作的粒度是否需要独立管理。如果后台只有「查看/编辑/删除」这类固定动作,menu 和 permission 合并在一张表里加个 action 字段更轻量;但如果要支持「导出Excel」「审核通过」「撤回发布」等业务专属动作,就必须把操作单独抽成 permission 表,并用 permission_code(如 order:export、article:revoke)做唯一标识——否则后期改权限逻辑时,光靠菜单ID根本没法区分同菜单下的不同行为。
常见错误现象:Undefined index: action 或查不到某项操作权限,往往是因为前端传了 menu_id=5 就去查,没带上 action 值;或者后端写死只查 menu_id,忽略 permission_code 的复合判断。
- 菜单表
menu存路径、名称、排序、是否显示,不存权限逻辑 - 权限表
permission必须有code(字符串,建议带冒号分隔)、name、module(可选,用于模块级过滤) - 角色-权限中间表
role_permission只存role_id和permission_id,别冗余字段
ThinkPHP 6 的多对多关联怎么写才不漏权限?
别直接用 belongsToMany 查角色所有权限再 in 查询,容易漏掉「用户直连权限」或「部门继承权限」这类扩展场景。正确做法是把权限来源抽象成统一接口,数据库层用 UNION 或视图合并不同来源的数据;框架层用 withCount 或 exists 判断时,必须显式指定 permission_code 而非仅靠 ID。
使用场景举例:管理员既属于「运营组」角色,又被单独授予了 user:lock 权限。此时若只查角色关联,会漏掉锁定用户的权限。
- 定义模型关系时,
Role模型的permissions()方法返回belongsToMany(Permission::class),但实际查询走自定义方法getPermissionsList() -
getPermissionsList()内部 union 查询:角色绑定的权限 + 用户直授权限 + 部门默认权限(如有) - 关联预载入时禁用
with('permissions'),改用load('permissions', function ($query) { $query->where('status', 1); })避免查出已禁用权限
RBAC 权限验证中间件里,Auth::check() 为什么总返回 false?
因为 ThinkPHP 默认的 Auth::check() 是基于「用户-角色」单层关系做的,它查的是 auth_group_access 这种老式结构,而你用的是角色-权限分离模型。直接调用它,等于绕过了整个 permission_code 校验链路。
性能影响明显:有人把全部权限 select * from permission 全查出来塞进 session,用户一多,session 文件暴涨,Redis 内存吃紧。
- 删掉对
think-auth或旧版Auth类的依赖,自己写PermissionMiddleware - 中间件里用
Cache::remember("user_{$uid}_perms", 3600, fn() => $this->buildPermCodeList($uid))缓存 code 列表,不是权限记录对象 - 校验时用
in_array($needCode, $permCodes),别用Db::table('permission')->where('code', $needCode)->count()每次都查库
菜单路由自动匹配权限时,route_name 和 permission_code 怎么对齐?
不能靠约定俗成,比如认为 admin/article/edit 对应 article:edit。ThinkPHP 的路由名(route_name)是开发时手动 bind 的,而 permission_code 是权限系统里的业务标识,二者语义层级不同。硬对齐会导致路由重构时权限全崩。
容易踩的坑:在控制器构造函数里写 $this->middleware->add(PermissionMiddleware::class, ['only' => ['edit', 'delete']]),结果增删方法后忘记同步中间件,权限就失效了。
- 菜单表
menu加一个permission_code字段,由后台人工填写或下拉选择,和路由名解耦 - 前端渲染菜单时,从
menu表读permission_code控制按钮显隐;后端接口拦截时,也查这个字段做校验 - 路由定义保持干净:
Route::get('article/edit/:id', 'ArticleController.edit')->name('admin.article.edit'),名字只用于生成 URL,不参与权限判定
最麻烦的其实是权限变更后的实时生效问题——缓存清不干净、中间件跳过、菜单没刷新,这三处只要有一处没对齐,用户就会遇到「明明开了权限却点不动」的情况。得把 permission_code 当作唯一信标,在菜单、接口、缓存、日志四个地方都盯住它。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











