应采用标准RBAC模型设计五张表:users、roles、permissions、user_roles、role_permissions;permissions.name用语义化字符串如edit_article;关联表加联合唯一索引和ON DELETE CASCADE;权限缓存用Redis按perm:uid:123存储,管理员改权限时需同步清理对应缓存;路由级绑定权限(如middleware('permission:view_users')),前端仅隐藏按钮但后端必须校验。

怎么设计用户-角色-权限三张表及关联表
直接用 RBAC 模型,别搞单字段 role 或 permission_level 这种硬编码字段——改个权限就得动代码、加字段、改逻辑,后期根本没法维护。
必须拆成五张表:users、roles、permissions、user_roles、role_permissions。其中后两张是纯关联表,无业务字段。
-
permissions.name要用语义化字符串,比如edit_article、delete_comment,别用数字 ID 做权限标识——前端传参、日志排查、缓存键生成全靠它可读 -
user_roles和role_permissions必须加联合唯一索引,防止重复绑定;外键要设ON DELETE CASCADE,删角色时自动清理权限关系 - 别在
users表里冗余存role_id或permission_list字段——这会破坏多角色能力,也违背第三范式
登录后权限数据该存在 session 还是 Redis
session 可以起步,但上线前必须切 Redis。session 本质是文件或数据库存储,高并发下 I/O 成瓶颈,且无法跨服务器共享权限状态。
推荐方案:用户登录成功后,执行一次 JOIN 查询,拿到所有 permission.name,序列化为 JSON 存入 Redis,key 形如 perm:uid:123,TTL 设 30 分钟。
立即学习“PHP免费学习笔记(深入)”;
- 每次请求校验前,先查
Redis::get('perm:uid:' . $uid),命中就跳过数据库;不命中再查库并回写 - 管理员修改权限时,必须同步执行
Redis::del('perm:uid:*')或按用户批量删——否则缓存永远不更新,这是最常被忽略的坑 - 别把整个用户对象(含密码哈希)塞进 session 或 Redis,只存必要字段:用户 ID、角色名数组、权限名数组
中间件里怎么安全提取路由所需权限
硬编码权限字符串(如 if ($permission !== 'manage_users') {...})等于没做权限控制——散落在各处,漏一个就崩。
统一走路由定义绑定权限,Laravel 风格的写法最稳:Route::get('/users', [UserController::class, 'index'])->middleware('permission:view_users');
- 中间件
PermissionMiddleware的handle()方法要能从$request->route()->middleware()或$request->route()->action['middleware']中解析出permission:xxx参数 - 不要依赖控制器方法名自动映射权限(比如
index()→view_*),命名冲突、重构重命名后权限就失效 - 如果用注解(如
@RequirePermission("publish_post")),确保解析器在框架生命周期早期运行,且注解缓存需与权限缓存联动失效
前端按钮要不要做权限隐藏
要,但只是体验优化;后端接口校验才是生死线。只隐藏按钮却不校验接口,等于门上挂纸糊的锁。
登录后把用户权限数组透出到前端 JS 全局变量(如 window.__USER_PERMISSIONS = ["view_dashboard", "edit_profile"];),用 JS 控制按钮显隐。
- 别用 PHP
echo拼 HTML 来控制显示,前后端耦合死,Vue/React 项目根本没法用 - 透出的权限列表必须和后端校验用的是同一份缓存数据,否则出现“按钮能点但接口 403”的体验断层
- 敏感操作(如删除、导出)的按钮,除了隐藏,建议加二次确认弹窗,并在弹窗文案里写明“此操作需要
delete_user权限”,方便运维对齐权限配置



















