add scope
This commit is contained in:
@@ -0,0 +1,176 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Model\Platform;
|
||||||
|
use App\Model\Store;
|
||||||
|
use App\Model\UserDataScope;
|
||||||
|
|
||||||
|
class ScopeBitmapService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 将 store_id 数组编码为 bitmap 二进制串
|
||||||
|
* store_id=N → bit N-1 置 1
|
||||||
|
*
|
||||||
|
* @param int[] $store_ids
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function encode(array $store_ids): string
|
||||||
|
{
|
||||||
|
if (empty($store_ids)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$max_id = max($store_ids);
|
||||||
|
$byte_length = intdiv($max_id - 1, 8) + 1;
|
||||||
|
$bitmap = str_repeat("\0", $byte_length);
|
||||||
|
|
||||||
|
foreach ($store_ids as $store_id) {
|
||||||
|
$bit_pos = $store_id - 1;
|
||||||
|
$byte_index = intdiv($bit_pos, 8);
|
||||||
|
$bit_offset = $bit_pos % 8;
|
||||||
|
$bitmap[$byte_index] = chr(ord($bitmap[$byte_index]) | (1 << $bit_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 bitmap 解码为 store_id 数组
|
||||||
|
*
|
||||||
|
* @param string $bitmap
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
public function decode(string $bitmap): array
|
||||||
|
{
|
||||||
|
$store_ids = [];
|
||||||
|
$length = strlen($bitmap);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $length; $i++) {
|
||||||
|
$byte = ord($bitmap[$i]);
|
||||||
|
if ($byte === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($bit = 0; $bit < 8; $bit++) {
|
||||||
|
if (($byte >> $bit) & 1) {
|
||||||
|
$store_ids[] = $i * 8 + $bit + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $store_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查 bitmap 中是否包含指定 store_id
|
||||||
|
*
|
||||||
|
* @param string $bitmap
|
||||||
|
* @param int $store_id
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function has(string $bitmap, int $store_id): bool
|
||||||
|
{
|
||||||
|
$bit_pos = $store_id - 1;
|
||||||
|
$byte_index = intdiv($bit_pos, 8);
|
||||||
|
|
||||||
|
if ($byte_index >= strlen($bitmap)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bit_offset = $bit_pos % 8;
|
||||||
|
return (bool) ((ord($bitmap[$byte_index]) >> $bit_offset) & 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并多个 bitmap(OR 运算)
|
||||||
|
*
|
||||||
|
* @param string ...$bitmaps
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function merge(string ...$bitmaps): string
|
||||||
|
{
|
||||||
|
$bitmaps = array_filter($bitmaps, fn(string $b): bool => $b !== '');
|
||||||
|
|
||||||
|
if (empty($bitmaps)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$max_len = max(array_map('strlen', $bitmaps));
|
||||||
|
$result = str_repeat("\0", $max_len);
|
||||||
|
|
||||||
|
foreach ($bitmaps as $bitmap) {
|
||||||
|
for ($i = 0; $i < strlen($bitmap); $i++) {
|
||||||
|
$result[$i] = chr(ord($result[$i]) | ord($bitmap[$i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户的 user_data_scopes 记录构建 scope bitmap
|
||||||
|
* 处理 company/platform/store 三种 scope_type 的解析和合并
|
||||||
|
*
|
||||||
|
* @param int $user_id
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function buildForUser(int $user_id): string
|
||||||
|
{
|
||||||
|
$scopes = UserDataScope::query()->where('user_id', $user_id)->get();
|
||||||
|
|
||||||
|
if ($scopes->isEmpty()) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$store_ids = [];
|
||||||
|
$grouped = $scopes->groupBy('scope_type');
|
||||||
|
|
||||||
|
// company 类型:查该 company 下所有 store
|
||||||
|
if ($company_scopes = $grouped->get('company')) {
|
||||||
|
$company_ids = $company_scopes->pluck('scope_id')->toArray();
|
||||||
|
$store_ids = array_merge(
|
||||||
|
$store_ids,
|
||||||
|
Store::query()->whereIn('company_id', $company_ids)->pluck('id')->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// platform 类型:查该 platform 下所有 store
|
||||||
|
if ($platform_scopes = $grouped->get('platform')) {
|
||||||
|
$platform_ids = $platform_scopes->pluck('scope_id')->toArray();
|
||||||
|
$store_ids = array_merge(
|
||||||
|
$store_ids,
|
||||||
|
Store::query()->whereIn('platform_id', $platform_ids)->pluck('id')->toArray()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store 类型:直接使用
|
||||||
|
if ($store_scopes = $grouped->get('store')) {
|
||||||
|
$store_ids = array_merge($store_ids, $store_scopes->pluck('scope_id')->toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
$store_ids = array_unique(array_map('intval', $store_ids));
|
||||||
|
return $this->encode($store_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 developer 维护的 platform 构建 scope bitmap
|
||||||
|
*
|
||||||
|
* @param int $user_id
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function buildForDeveloper(int $user_id): string
|
||||||
|
{
|
||||||
|
$platform_ids = Platform::query()->where('developer_id', $user_id)->pluck('id')->toArray();
|
||||||
|
|
||||||
|
if (empty($platform_ids)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$store_ids = Store::query()->whereIn('platform_id', $platform_ids)->pluck('id')->toArray();
|
||||||
|
$store_ids = array_map('intval', $store_ids);
|
||||||
|
return $this->encode($store_ids);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use App\Model\User;
|
||||||
|
use Swoole\Table;
|
||||||
|
|
||||||
|
class ScopeTableManager
|
||||||
|
{
|
||||||
|
protected Table $table;
|
||||||
|
|
||||||
|
public function __construct(protected ScopeBitmapService $bitmapService)
|
||||||
|
{
|
||||||
|
$this->initTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 Swoole\Table
|
||||||
|
*/
|
||||||
|
protected function initTable(): void
|
||||||
|
{
|
||||||
|
$this->table = new Table(1024);
|
||||||
|
$this->table->column('role', Table::TYPE_STRING, 32);
|
||||||
|
$this->table->column('scope', Table::TYPE_STRING, 4096);
|
||||||
|
$this->table->column('version', Table::TYPE_INT, 8);
|
||||||
|
$this->table->create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTable(): Table
|
||||||
|
{
|
||||||
|
return $this->table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户 scope 数据,未命中时懒加载
|
||||||
|
*
|
||||||
|
* @param int $user_id
|
||||||
|
* @return array{role: string, scope: string, version: int}|null
|
||||||
|
*/
|
||||||
|
public function getUserScope(int $user_id): ?array
|
||||||
|
{
|
||||||
|
$key = (string) $user_id;
|
||||||
|
$row = $this->table->get($key);
|
||||||
|
|
||||||
|
if ($row !== false) {
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table 未命中,从数据库加载
|
||||||
|
return $this->rebuildUserScope($user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重建用户 scope 并写入 Table
|
||||||
|
*
|
||||||
|
* @param int $user_id
|
||||||
|
* @return array{role: string, scope: string, version: int}|null
|
||||||
|
*/
|
||||||
|
public function rebuildUserScope(int $user_id): ?array
|
||||||
|
{
|
||||||
|
$user = User::query()->with('role')->find($user_id);
|
||||||
|
if (!$user || !$user->role) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$role_name = $user->role->name;
|
||||||
|
|
||||||
|
// administrator 不需要 bitmap
|
||||||
|
if ($role_name === 'administrator') {
|
||||||
|
$bitmap = '';
|
||||||
|
} elseif ($role_name === 'developer') {
|
||||||
|
$bitmap = $this->bitmapService->buildForDeveloper($user_id);
|
||||||
|
} else {
|
||||||
|
$bitmap = $this->bitmapService->buildForUser($user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'role' => $role_name,
|
||||||
|
'scope' => $bitmap,
|
||||||
|
'version' => time(),
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->table->set((string) $user_id, $data);
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除用户 scope(权限变更时先清再重建)
|
||||||
|
*
|
||||||
|
* @param int $user_id
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function invalidateUser(int $user_id): void
|
||||||
|
{
|
||||||
|
$this->table->del((string) $user_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user