add scope

This commit is contained in:
2026-03-09 14:15:29 +08:00
parent d4bb96fbcb
commit c2af36760b
2 changed files with 275 additions and 0 deletions
+176
View File
@@ -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);
}
/**
* 合并多个 bitmapOR 运算)
*
* @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);
}
}
+99
View File
@@ -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);
}
}