[]]], 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); OperationLogService::log( user_id: OperationLogService::getCurrentUserId(), action: 'scope.update', target_type: 'user', target_id: $id, description: "更新用户 #{$id} 数据权限", detail: ['scopes' => $scopes], ip: OperationLogService::getRequestIp(), ); 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'], default => null, }; } // 批量查询名称 $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, default => null, }; return [ 'scope_type' => $scope['scope_type'], 'scope_id' => $scope['scope_id'], 'name' => $name, ]; }, $scopes); } }