Laravel 5.2+Zizaco Entrust重复权限查询优化方案咨询
嘿,这个场景我太熟悉了!当初用Entrust做权限管理时,菜单里一堆@permission指令,结果页面加载时数据库疯狂执行重复的角色/权限查询,性能掉得厉害。咱们可以通过预加载+缓存的方式彻底解决这个问题,下面是具体的实现步骤:
1. 预加载用户的角色与权限关联数据
Entrust默认每次调用hasPermissionTo或@permission时,都会重新查询用户的角色和权限关联。我们可以在用户登录完成后,一次性预加载这些关联数据,让后续检查直接用内存里的对象:
比如在你的登录逻辑(比如AuthController的login方法)最后添加:
if (Auth::check()) { // 预加载用户的角色,以及每个角色对应的权限 Auth::user()->load(['roles', 'roles.permissions']); }
或者在AppServiceProvider的boot方法里,通过监听登录事件来实现:
use Illuminate\Auth\Events\Login; public function boot() { Event::listen(Login::class, function ($event) { $event->user->load(['roles', 'roles.permissions']); }); }
这样用户对象初始化后就已经包含了所有需要的权限数据,后续权限检查不会再触发新的数据库查询。
2. 替换默认@permission指令(用已预加载的数据)
默认的@permission指令内部逻辑可能还是会强制重新查询,我们可以自定义一个Blade指令,直接调用用户已加载的关联数据来做权限检查:
在AppServiceProvider的boot方法里注册自定义指令:
use Illuminate\Support\Facades\Blade; public function boot() { // 自定义缓存版权限检查指令 Blade::directive('cachedPermission', function ($expression) { return "<?php if(auth()->check() && auth()->user()->hasPermissionTo{$expression}): ?>"; }); Blade::directive('endCachedPermission', function () { return "<?php endif; ?>"; }); }
然后把菜单里的@permission('xxx')替换成@cachedPermission('xxx'),对应的@endpermission换成@endCachedPermission。
这里的核心是hasPermissionTo方法——当用户的角色和权限关联已经预加载后,Entrust会直接使用内存中的数据,不会再发起数据库请求。
3. 可选:缓存权限列表(进一步提升性能)
如果你的系统权限不会频繁变动,可以把用户的权限名称列表缓存起来,连模型关联的内存查询都省掉:
在User模型里添加一个获取缓存权限的方法:
use Illuminate\Support\Facades\Cache; public function getCachedPermissions() { // 缓存1小时,可根据实际情况调整有效期 return Cache::remember('user_permissions_' . $this->id, 3600, function () { // 取出所有角色下的权限,去重后返回权限名称数组 return $this->roles->flatMap(function ($role) { return $role->permissions; })->pluck('name')->unique(); }); }
然后修改自定义指令,直接用缓存的权限列表检查:
Blade::directive('cachedPermission', function ($expression) { return "<?php if(auth()->check() && auth()->user()->getCachedPermissions()->contains{$expression}): ?>"; });
注意:当用户的角色或权限发生变动时,需要手动清除对应的缓存,比如在修改权限的逻辑里添加:
Cache::forget('user_permissions_' . $user->id);
4. 验证优化效果
开启Laravel的查询日志(在路由或控制器里添加DB::enableQueryLog()),刷新页面后查看dd(DB::getQueryLog()),你会发现原来重复出现的那两条角色/权限查询只会执行一次(或者缓存有效期内完全不执行),菜单的权限检查都不会再触发新的数据库请求了。
内容的提问来源于stack exchange,提问作者Richard

