From 05b45e49186639e74af33f959541713fa5e400fc Mon Sep 17 00:00:00 2001 From: Nick Zeng Date: Mon, 9 Mar 2026 10:13:45 +0800 Subject: [PATCH] add api key controller --- .../Controller/api/v1/ApiKeyController.php | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 backend/app/Controller/api/v1/ApiKeyController.php diff --git a/backend/app/Controller/api/v1/ApiKeyController.php b/backend/app/Controller/api/v1/ApiKeyController.php new file mode 100644 index 0000000..5d7d37e --- /dev/null +++ b/backend/app/Controller/api/v1/ApiKeyController.php @@ -0,0 +1,244 @@ + []]], + tags: ['ApiKeys'], + requestBody: new OA\RequestBody( + required: true, + content: new OA\JsonContent( + required: ['name'], + properties: [ + new OA\Property(property: 'name', type: 'string', maxLength: 100, example: 'Production Key'), + new OA\Property(property: 'expires_at', type: 'string', format: 'date-time', nullable: true, description: '过期时间,不传则永不过期'), + ] + ) + ), + 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: 'plain_key', type: 'string', description: '明文 Key,仅此次可见'), + new OA\Property(property: 'api_key', properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'name', type: 'string'), + new OA\Property(property: 'key_prefix', type: 'string'), + new OA\Property(property: 'expires_at', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'enabled', type: 'boolean'), + new OA\Property(property: 'created_at', type: 'string', format: 'date-time'), + ], type: 'object'), + ], type: 'object'), + ]) + ), + new OA\Response(response: 400, description: '参数校验失败', 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: 403, description: '未启用 API Key 功能', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "", methods: "POST")] + #[Middleware(AuthMiddleware::class)] + public function store(AuthManager $auth): \Psr\Http\Message\ResponseInterface|array + { + $user = $auth->guard('jwt')->user(); + + if (!$user instanceof User) { + return $this->response->json([ + 'code' => 401, + 'message' => '未授权', + ])->withStatus(401); + } + + if (!$user->api_key_enabled) { + return $this->response->json([ + 'code' => 403, + 'message' => '未启用 API Key 功能,请联系管理员开启', + ])->withStatus(403); + } + + $name = $this->request->input('name'); + $expires_at = $this->request->input('expires_at'); + + if (!is_string($name) || trim($name) === '') { + return $this->response->json([ + 'code' => 400, + 'message' => 'API Key 名称不能为空', + ])->withStatus(400); + } + + $name = trim($name); + if (strlen($name) > 100) { + return $this->response->json([ + 'code' => 400, + 'message' => 'API Key 名称不能超过 100 个字符', + ])->withStatus(400); + } + + // 校验过期时间格式 + if ($expires_at !== null && $expires_at !== '') { + try { + $expires_at = \Carbon\Carbon::parse($expires_at)->toDateTimeString(); + } catch (\Throwable $e) { + return $this->response->json([ + 'code' => 400, + 'message' => '过期时间格式不正确', + ])->withStatus(400); + } + } else { + $expires_at = null; + } + + $result = ApiKey::generate($user->id, $name, $expires_at); + + return [ + 'code' => 0, + 'message' => '生成成功', + 'data' => [ + 'plain_key' => $result['plain_key'], + 'api_key' => $result['api_key'], + ], + ]; + } + + /** + * API Key 列表 + * + * 列出当前用户的所有 API Keys(不含哈希) + */ + #[OA\Get( + path: '/me/api-keys', + summary: 'API Key 列表', + description: '列出当前用户的所有 API Keys', + security: [['bearerAuth' => []]], + tags: ['ApiKeys'], + 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', type: 'array', items: new OA\Items(properties: [ + new OA\Property(property: 'id', type: 'integer'), + new OA\Property(property: 'name', type: 'string'), + new OA\Property(property: 'key_prefix', type: 'string'), + new OA\Property(property: 'last_used_at', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'expires_at', type: 'string', format: 'date-time', nullable: true), + new OA\Property(property: 'enabled', type: 'boolean'), + new OA\Property(property: 'created_at', type: 'string', format: 'date-time'), + ])), + ]) + ), + new OA\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "", methods: "GET")] + #[Middleware(AuthMiddleware::class)] + public function index(AuthManager $auth): \Psr\Http\Message\ResponseInterface|array + { + $user = $auth->guard('jwt')->user(); + + if (!$user instanceof User) { + return $this->response->json([ + 'code' => 401, + 'message' => '未授权', + ])->withStatus(401); + } + + $keys = ApiKey::query() + ->where('user_id', $user->id) + ->orderBy('created_at', 'desc') + ->get(); + + return [ + 'code' => 0, + 'message' => '获取成功', + 'data' => $keys, + ]; + } + + /** + * 删除 API Key + */ + #[OA\Delete( + path: '/me/api-keys/{id}', + summary: '删除 API Key', + security: [['bearerAuth' => []]], + tags: ['ApiKeys'], + parameters: [ + new OA\Parameter(name: 'id', in: 'path', required: true, description: 'API Key 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\Response(response: 401, description: '未认证', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + new OA\Response(response: 404, description: 'API Key 不存在', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse')), + ] + )] + #[RequestMapping(path: "{id}", methods: "DELETE")] + #[Middleware(AuthMiddleware::class)] + public function destroy(int $id, AuthManager $auth): \Psr\Http\Message\ResponseInterface|array + { + $user = $auth->guard('jwt')->user(); + + if (!$user instanceof User) { + return $this->response->json([ + 'code' => 401, + 'message' => '未授权', + ])->withStatus(401); + } + + $api_key = ApiKey::query() + ->where('id', $id) + ->where('user_id', $user->id) + ->first(); + + if (!$api_key) { + return $this->response->json([ + 'code' => 404, + 'message' => 'API Key 不存在', + ])->withStatus(404); + } + + $api_key->delete(); + + return [ + 'code' => 0, + 'message' => '删除成功', + ]; + } +}