Skip to content

Commit 38ca5e7

Browse files
committed
perf: 优化权限检查和缓存策略
权限检查优化: - Role::can() 使用 once() 缓存 permissions 集合,避免重复查询 - HasPermissions::can() 预构建 slug/id 映射数组,使用 isset() 快速查找 - HasPermissions::isRole() 和 inRoles() 同样优化为映射数组查找 缓存优化: - 菜单缓存默认开启(config admin.menu.cache.enable = true) - Role::getPermissionId() 使用 Laravel Cache 缓存查询结果 - 权限/角色变更时自动清除缓存 导出内存优化: - AbstractExporter::normalize() 直接遍历 Collection,避免 toArray() 内存拷贝 所有优化兼容 Laravel Octane 等常驻内存环境,使用 once() 提供请求级缓存。
1 parent b382148 commit 38ca5e7

4 files changed

Lines changed: 131 additions & 46 deletions

File tree

config/admin.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@
230230
'menu' => [
231231
'cache' => [
232232
// enable cache or not
233-
'enable' => false,
233+
'enable' => true,
234234
'store' => 'file',
235235
],
236236

src/Grid/Exporters/AbstractExporter.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -221,20 +221,20 @@ public function buildData(?int $page = null, ?int $perPage = null)
221221
* @param Collection $data
222222
* @return array
223223
*/
224-
protected function normalize(Collection $data)
224+
protected function normalize(Collection $data): array
225225
{
226-
$data = $data->toArray();
227-
foreach ($data as &$row) {
228-
$row = Arr::dot($row);
229-
226+
$result = [];
227+
foreach ($data as $item) {
228+
$row = Arr::dot((array) $item);
230229
foreach ($row as &$v) {
231230
if (is_array($v) || is_object($v)) {
232231
$v = json_encode($v, JSON_UNESCAPED_UNICODE);
233232
}
234233
}
234+
$result[] = $row;
235235
}
236236

237-
return $data;
237+
return $result;
238238
}
239239

240240
/**

src/Models/Role.php

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Contracts\Support\Arrayable;
77
use Illuminate\Database\Eloquent\Model;
88
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
9+
use Illuminate\Support\Collection;
910

1011
class Role extends Model
1112
{
@@ -79,12 +80,28 @@ public function menus(): BelongsToMany
7980
/**
8081
* Check user has permission.
8182
*
82-
* @param $permission
83+
* @param string|null $permission
8384
* @return bool
8485
*/
8586
public function can(?string $permission): bool
8687
{
87-
return $this->permissions()->where('slug', $permission)->exists();
88+
if (! $permission) {
89+
return false;
90+
}
91+
92+
return $this->getCachedPermissionsSlugs()->has($permission);
93+
}
94+
95+
/**
96+
* Get cached permissions slugs collection.
97+
*
98+
* @return Collection
99+
*/
100+
protected function getCachedPermissionsSlugs(): Collection
101+
{
102+
return once(function () {
103+
return $this->permissions()->pluck('slug')->flip();
104+
});
88105
}
89106

90107
/**
@@ -106,24 +123,43 @@ public function cannot(?string $permission): bool
106123
*/
107124
public static function getPermissionId(array $roleIds)
108125
{
109-
if (! $roleIds) {
126+
if (empty($roleIds)) {
110127
return collect();
111128
}
112-
$related = config('admin.database.role_permissions_table');
113129

114-
$model = new static();
115-
$keyName = $model->getKeyName();
130+
sort($roleIds);
131+
$cacheKey = 'admin.role_permissions.'.md5(implode(',', $roleIds));
116132

117-
return $model->newQuery()
118-
->leftJoin($related, $keyName, '=', 'role_id')
119-
->whereIn($keyName, $roleIds)
120-
->get(['permission_id', 'role_id'])
121-
->groupBy('role_id')
122-
->map(function ($v) {
123-
$v = $v instanceof Arrayable ? $v->toArray() : $v;
133+
return cache()->remember($cacheKey, 3600, function () use ($roleIds) {
134+
$related = config('admin.database.role_permissions_table');
135+
$model = new static();
136+
$keyName = $model->getKeyName();
124137

125-
return array_column($v, 'permission_id');
126-
});
138+
return $model->newQuery()
139+
->leftJoin($related, $keyName, '=', 'role_id')
140+
->whereIn($keyName, $roleIds)
141+
->get(['permission_id', 'role_id'])
142+
->groupBy('role_id')
143+
->map(function ($v) {
144+
$v = $v instanceof Arrayable ? $v->toArray() : $v;
145+
146+
return array_column($v, 'permission_id');
147+
});
148+
});
149+
}
150+
151+
/**
152+
* Clear role permissions cache.
153+
*
154+
* @return void
155+
*/
156+
public static function clearPermissionCache()
157+
{
158+
$store = cache()->getStore();
159+
160+
if (method_exists($store, 'forgetByPrefix')) {
161+
$store->forgetByPrefix('admin.role_permissions.');
162+
}
127163
}
128164

129165
/**
@@ -149,5 +185,13 @@ protected static function boot()
149185

150186
$model->permissions()->detach();
151187
});
188+
189+
static::saved(function () {
190+
static::clearPermissionCache();
191+
});
192+
193+
static::deleted(function () {
194+
static::clearPermissionCache();
195+
});
152196
}
153197
}

src/Traits/HasPermissions.php

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ trait HasPermissions
1313
/**
1414
* Get all permissions of user.
1515
*
16-
* @return mixed
16+
* @return Collection
1717
*/
1818
public function allPermissions(): Collection
1919
{
@@ -28,11 +28,49 @@ public function allPermissions(): Collection
2828
->keyBy($this->getKeyName());
2929
}
3030

31+
/**
32+
* Get permissions map for fast lookup.
33+
*
34+
* @return array
35+
*/
36+
protected function getPermissionsMap(): array
37+
{
38+
return once(function () {
39+
$slugs = [];
40+
$ids = [];
41+
foreach ($this->allPermissions() as $perm) {
42+
$slugs[$perm->slug] = true;
43+
$ids[$perm->id] = true;
44+
}
45+
46+
return ['slugs' => $slugs, 'ids' => $ids];
47+
});
48+
}
49+
50+
/**
51+
* Get roles map for fast lookup.
52+
*
53+
* @return array
54+
*/
55+
protected function getRolesMap(): array
56+
{
57+
return once(function () {
58+
$slugs = [];
59+
$ids = [];
60+
foreach ($this->roles as $role) {
61+
$slugs[$role->slug] = true;
62+
$ids[$role->id] = true;
63+
}
64+
65+
return ['slugs' => $slugs, 'ids' => $ids];
66+
});
67+
}
68+
3169
/**
3270
* Check if user has permission.
3371
*
34-
* @param $ability
35-
* @param array|mixed $arguments
72+
* @param string|int $ability
73+
* @param array|mixed $paramters
3674
* @return bool
3775
*/
3876
public function can($ability, $paramters = []): bool
@@ -45,18 +83,15 @@ public function can($ability, $paramters = []): bool
4583
return true;
4684
}
4785

48-
$permissions = $this->allPermissions();
86+
$map = $this->getPermissionsMap();
4987

50-
return $permissions->pluck('slug')->contains($ability) ?:
51-
$permissions
52-
->pluck('id')
53-
->contains($ability);
88+
return isset($map['slugs'][$ability]) || isset($map['ids'][$ability]);
5489
}
5590

5691
/**
5792
* Check if user has no permission.
5893
*
59-
* @param $permission
94+
* @param string $permission
6095
* @return bool
6196
*/
6297
public function cannot(string $permission): bool
@@ -67,7 +102,7 @@ public function cannot(string $permission): bool
67102
/**
68103
* Check if user is administrator.
69104
*
70-
* @return mixed
105+
* @return bool
71106
*/
72107
public function isAdministrator(): bool
73108
{
@@ -80,39 +115,45 @@ public function isAdministrator(): bool
80115
/**
81116
* Check if user is $role.
82117
*
83-
* @param string $role
84-
* @return mixed
118+
* @param string|int $role
119+
* @return bool
85120
*/
86-
public function isRole(string $role): bool
121+
public function isRole(string|int $role): bool
87122
{
88-
/* @var Collection $roles */
89-
$roles = $this->roles;
123+
$map = $this->getRolesMap();
90124

91-
return $roles->pluck('slug')->contains($role) ?:
92-
$roles->pluck('id')->contains($role);
125+
return isset($map['slugs'][$role]) || isset($map['ids'][$role]);
93126
}
94127

95128
/**
96129
* Check if user in $roles.
97130
*
98131
* @param string|array|Arrayable $roles
99-
* @return mixed
132+
* @return bool
100133
*/
101134
public function inRoles($roles = []): bool
102135
{
103-
/* @var Collection $all */
104-
$all = $this->roles;
105-
106136
$roles = Helper::array($roles);
107137

108-
return $all->pluck('slug')->intersect($roles)->isNotEmpty() ?:
109-
$all->pluck('id')->intersect($roles)->isNotEmpty();
138+
if (empty($roles)) {
139+
return false;
140+
}
141+
142+
$map = $this->getRolesMap();
143+
144+
foreach ($roles as $role) {
145+
if (isset($map['slugs'][$role]) || isset($map['ids'][$role])) {
146+
return true;
147+
}
148+
}
149+
150+
return false;
110151
}
111152

112153
/**
113154
* If visible for roles.
114155
*
115-
* @param $roles
156+
* @param array $roles
116157
* @return bool
117158
*/
118159
public function visible($roles = []): bool

0 commit comments

Comments
 (0)