update permission and scope gate
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Controller\Api\V1;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Middleware\AuthMiddleware;
|
||||
use App\Middleware\PermissionMiddleware;
|
||||
use App\Model\Company;
|
||||
use App\Model\Platform;
|
||||
use App\Model\Store;
|
||||
use App\Model\User;
|
||||
use App\Model\UserDataScope;
|
||||
use App\Service\ScopeBitmapService;
|
||||
use App\Service\ScopeTableManager;
|
||||
use Hyperf\DbConnection\Db;
|
||||
use Hyperf\HttpServer\Annotation\Controller;
|
||||
use Hyperf\HttpServer\Annotation\Middleware;
|
||||
use Hyperf\HttpServer\Annotation\RequestMapping;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
#[OA\Tag(name: 'DataScope', description: '用户数据范围管理')]
|
||||
#[Controller(prefix: "/api/v1/users")]
|
||||
class DataScopeController extends AbstractController
|
||||
{
|
||||
public function __construct(
|
||||
protected readonly ScopeTableManager $scopeTableManager,
|
||||
protected readonly ScopeBitmapService $bitmapService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看用户数据权限
|
||||
*
|
||||
* 返回用户的 scope 列表(含实体名称)和解析后的 store_ids
|
||||
*
|
||||
* @param int $id 用户 ID
|
||||
*/
|
||||
#[OA\Get(
|
||||
path: '/users/{id}/data-scope',
|
||||
summary: '查看用户数据权限',
|
||||
description: '返回用户的 scope 列表(含实体名称)和解析后的 store_ids',
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['DataScope'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'id', in: 'path', required: true, description: '用户 ID', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: '获取成功',
|
||||
content: new OA\JsonContent(properties: [
|
||||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||||
new OA\Property(property: 'message', type: 'string', example: '获取成功'),
|
||||
new OA\Property(property: 'data', properties: [
|
||||
new OA\Property(property: 'user_id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'role', type: 'string', example: 'admin'),
|
||||
new OA\Property(
|
||||
property: 'scopes',
|
||||
type: 'array',
|
||||
items: new OA\Items(properties: [
|
||||
new OA\Property(property: 'scope_type', type: 'string', example: 'company'),
|
||||
new OA\Property(property: 'scope_id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'name', type: 'string', example: '示例公司'),
|
||||
], type: 'object')
|
||||
),
|
||||
new OA\Property(property: 'resolved_store_ids', type: 'array', items: new OA\Items(type: 'integer'), example: [1, 2, 3]),
|
||||
], type: 'object'),
|
||||
])
|
||||
),
|
||||
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||||
new OA\Response(response: 404, description: '用户不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||||
]
|
||||
)]
|
||||
#[RequestMapping(path: "{id}/data-scope", methods: "GET")]
|
||||
#[Middleware(AuthMiddleware::class)]
|
||||
#[Middleware(PermissionMiddleware::class)]
|
||||
public function show(int $id): ResponseInterface|array
|
||||
{
|
||||
$user = User::query()->with('role')->find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->response->json([
|
||||
'code' => 404,
|
||||
'message' => '用户不存在',
|
||||
])->withStatus(404);
|
||||
}
|
||||
|
||||
$scopes = UserDataScope::query()->where('user_id', $id)->get();
|
||||
|
||||
// 批量查询实体名称,避免 N+1
|
||||
$scope_data = $this->enrichScopes($scopes->toArray());
|
||||
|
||||
// 解析最终 store_ids
|
||||
$resolved_store_ids = [];
|
||||
if ($user->role && $user->role->name !== 'administrator') {
|
||||
$bitmap = $this->bitmapService->buildForUser($id);
|
||||
$resolved_store_ids = $this->bitmapService->decode($bitmap);
|
||||
}
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'message' => '获取成功',
|
||||
'data' => [
|
||||
'user_id' => $id,
|
||||
'role' => $user->role?->name,
|
||||
'scopes' => $scope_data,
|
||||
'resolved_store_ids' => $resolved_store_ids,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户数据权限
|
||||
*
|
||||
* 全量替换用户的 scope 绑定,并重建 bitmap
|
||||
*
|
||||
* @param int $id 用户 ID
|
||||
*/
|
||||
#[OA\Put(
|
||||
path: '/users/{id}/data-scope',
|
||||
summary: '设置用户数据权限',
|
||||
description: '全量替换用户的 scope 绑定,并重建 bitmap',
|
||||
security: [['bearerAuth' => []]],
|
||||
tags: ['DataScope'],
|
||||
parameters: [
|
||||
new OA\Parameter(name: 'id', in: 'path', required: true, description: '用户 ID', schema: new OA\Schema(type: 'integer')),
|
||||
],
|
||||
requestBody: new OA\RequestBody(
|
||||
required: true,
|
||||
content: new OA\JsonContent(
|
||||
required: ['scopes'],
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'scopes',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
required: ['scope_type', 'scope_id'],
|
||||
properties: [
|
||||
new OA\Property(property: 'scope_type', type: 'string', enum: ['company', 'platform', 'store'], example: 'company'),
|
||||
new OA\Property(property: 'scope_id', type: 'integer', example: 1),
|
||||
],
|
||||
type: 'object'
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
responses: [
|
||||
new OA\Response(
|
||||
response: 200,
|
||||
description: '数据权限更新成功',
|
||||
content: new OA\JsonContent(properties: [
|
||||
new OA\Property(property: 'code', type: 'integer', example: 0),
|
||||
new OA\Property(property: 'message', type: 'string', example: '数据权限更新成功'),
|
||||
])
|
||||
),
|
||||
new OA\Response(response: 400, description: '参数校验失败(无效 scope_type 等)', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||||
new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||||
new OA\Response(response: 404, description: '用户不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')),
|
||||
]
|
||||
)]
|
||||
#[RequestMapping(path: "{id}/data-scope", methods: "PUT")]
|
||||
#[Middleware(AuthMiddleware::class)]
|
||||
#[Middleware(PermissionMiddleware::class)]
|
||||
public function update(int $id): ResponseInterface|array
|
||||
{
|
||||
$user = User::query()->with('role')->find($id);
|
||||
|
||||
if (!$user) {
|
||||
return $this->response->json([
|
||||
'code' => 404,
|
||||
'message' => '用户不存在',
|
||||
])->withStatus(404);
|
||||
}
|
||||
|
||||
$body = $this->request->getParsedBody();
|
||||
|
||||
if (!array_key_exists('scopes', $body)) {
|
||||
return $this->response->json([
|
||||
'code' => 400,
|
||||
'message' => '缺少 scopes 参数',
|
||||
])->withStatus(400);
|
||||
}
|
||||
|
||||
$scopes = $body['scopes'];
|
||||
|
||||
if (!is_array($scopes)) {
|
||||
return $this->response->json([
|
||||
'code' => 400,
|
||||
'message' => 'scopes 必须为数组',
|
||||
])->withStatus(400);
|
||||
}
|
||||
|
||||
// 校验每条 scope
|
||||
$valid_scope_types = ['company', 'platform', 'store'];
|
||||
$records = [];
|
||||
foreach ($scopes as $item) {
|
||||
if (!is_array($item) || !isset($item['scope_type']) || !isset($item['scope_id'])) {
|
||||
return $this->response->json([
|
||||
'code' => 400,
|
||||
'message' => '每条 scope 必须包含 scope_type 和 scope_id',
|
||||
])->withStatus(400);
|
||||
}
|
||||
|
||||
if (!in_array($item['scope_type'], $valid_scope_types, true)) {
|
||||
return $this->response->json([
|
||||
'code' => 400,
|
||||
'message' => "scope_type 必须为 company、platform 或 store",
|
||||
])->withStatus(400);
|
||||
}
|
||||
|
||||
$records[] = [
|
||||
'user_id' => $id,
|
||||
'scope_type' => $item['scope_type'],
|
||||
'scope_id' => (int) $item['scope_id'],
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
];
|
||||
}
|
||||
|
||||
// 事务内全量替换
|
||||
Db::beginTransaction();
|
||||
try {
|
||||
UserDataScope::query()->where('user_id', $id)->delete();
|
||||
|
||||
if (!empty($records)) {
|
||||
UserDataScope::query()->insert($records);
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
} catch (\Throwable $e) {
|
||||
Db::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
// 重建 bitmap 并更新 Swoole\Table
|
||||
$this->scopeTableManager->rebuildUserScope($id);
|
||||
|
||||
return [
|
||||
'code' => 0,
|
||||
'message' => '数据权限更新成功',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量查询 scope 关联的实体名称
|
||||
*
|
||||
* @param array $scopes scope 记录数组
|
||||
* @return array 带名称的 scope 列表
|
||||
*/
|
||||
protected function enrichScopes(array $scopes): array
|
||||
{
|
||||
if (empty($scopes)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 按 scope_type 分组收集 IDs
|
||||
$company_ids = [];
|
||||
$platform_ids = [];
|
||||
$store_ids = [];
|
||||
|
||||
foreach ($scopes as $scope) {
|
||||
match ($scope['scope_type']) {
|
||||
'company' => $company_ids[] = $scope['scope_id'],
|
||||
'platform' => $platform_ids[] = $scope['scope_id'],
|
||||
'store' => $store_ids[] = $scope['scope_id'],
|
||||
};
|
||||
}
|
||||
|
||||
// 批量查询名称
|
||||
$company_names = !empty($company_ids)
|
||||
? Company::query()->whereIn('id', $company_ids)->pluck('label', 'id')->toArray()
|
||||
: [];
|
||||
$platform_names = !empty($platform_ids)
|
||||
? Platform::query()->whereIn('id', $platform_ids)->pluck('id', 'id')->toArray()
|
||||
: [];
|
||||
$store_names = !empty($store_ids)
|
||||
? Store::query()->whereIn('id', $store_ids)->pluck('label', 'id')->toArray()
|
||||
: [];
|
||||
|
||||
// 组装结果
|
||||
return array_map(function (array $scope) use ($company_names, $platform_names, $store_names): array {
|
||||
$name = match ($scope['scope_type']) {
|
||||
'company' => $company_names[$scope['scope_id']] ?? null,
|
||||
'platform' => isset($platform_names[$scope['scope_id']]) ? "Platform #{$scope['scope_id']}" : null,
|
||||
'store' => $store_names[$scope['scope_id']] ?? null,
|
||||
};
|
||||
|
||||
return [
|
||||
'scope_type' => $scope['scope_type'],
|
||||
'scope_id' => $scope['scope_id'],
|
||||
'name' => $name,
|
||||
];
|
||||
}, $scopes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user