thinkphp权限系统重构核心是分层解耦:将auth、rule、role、user逻辑剥离控制器,移入service+repository+dto层;权限校验统一经permissionservice::can(),拒绝抛permissiondeniedexception;角色权限须动态关联、缓存按tag粒度管理;菜单与权限分离,前端按钮权限调getallowedactions();须先全局扫描隐式权限调用再重构。

ThinkPHP 的权限系统重构,核心不是换框架,而是把 Auth、Rule、Role、User 四类逻辑从控制器里剥出来,按职责拆进独立 Service + Repository + DTO 层,否则越改越难加新规则。
权限判断逻辑不能写在控制器里
常见错误是把 Auth::check('user/edit') 或 $this->auth->hasAccess('order:delete') 直接塞进控制器方法开头。这会导致:规则散落、测试难写、RBAC 变 ABAC 时无从下手。
实操建议:
- 所有权限校验统一走
PermissionService::can($user, $action, $resource),不暴露Auth类给控制器 -
$action统一用小写冒号分隔格式,如'article:publish',避免'ArticlePublish'这类魔术字符串 - 资源(
$resource)支持传模型实例或 ID,Service 内部自动 resolve,避免控制器做额外查询 - 拒绝时抛
PermissionDeniedException,由全局异常处理器返回 403,不写if (!Auth::check()) die()
角色-权限关系必须支持运行时动态加载
硬编码角色权限(比如在配置文件里写 'admin' => ['user:*', 'log:view'])会卡死后续多租户、临时角色、权限继承等需求。
实操建议:
- 角色表
role和权限表permission之间通过中间表role_permission关联,禁用“角色字段存 JSON 权限数组”方案 - 权限缓存用
Cache::tag('permission')->get($key),不直接缓存整个二维数组;更新某角色权限时只删对应 tag,不影响其他角色 - 前端按钮级权限需调用
PermissionService::getAllowedActions($user)返回扁平数组,而非每次请求都查库 - 避免在
Role::getPermissionsAttribute()这类访问器里触发 N+1 查询,一律用预加载或单独 Repository 方法
菜单与权限分离,菜单结构走独立配置 + 数据库混合管理
把菜单当权限看(例如认为有 'menu:user' 就该显示用户菜单)是典型耦合陷阱——菜单展示受语言、设备、组织架构影响,和“能否操作”根本不是一回事。
实操建议:
- 菜单数据存在
menu表,含route、icon、sort、visible_role_ids(可为空),不存 permission 字段 - 前端请求菜单时,后端用
MenuService::buildForUser($user):先查出所有 visible_role_ids 包含该用户的菜单,再对每项调用PermissionService::can()判断是否启用href或显示操作按钮 - 后台菜单管理页提供“绑定权限”开关,但只是 UI 提示,实际生效靠
role_permission表,不修改菜单表字段 - 路由定义仍用 ThinkPHP 原生
Route::rule('user/list', 'User/list'),权限校验层在中间件里调PermissionService,不依赖路由名自动映射权限
真正麻烦的从来不是怎么拆模块,而是老项目里那些藏在 __construct()、initialize()、模板 {:auth_check()} 里的隐式权限调用——它们不会报错,但会让任何一次权限策略调整变成全站回归测试。动手前先 grep 出所有 auth、check、hasRole、canEdit,逐个标记来源和上下文,比直接写新 Service 重要得多。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











