From 9df41788004855ea8f912fc5ef896ede12e29407 Mon Sep 17 00:00:00 2001 From: Jethro Lin Date: Wed, 4 Dec 2024 12:27:33 +0800 Subject: [PATCH] bug fix --- .../Controllers/Api/Admin/AuthController.php | 146 ++++++---- .../Api/Admin/ClientController.php | 271 +++++------------- .../Api/Admin/LlmProviderController.php | 236 +++++++-------- app/Http/Controllers/Api/AuthController.php | 72 ++--- app/Http/Controllers/Api/LlmController.php | 146 +++------- app/Http/Kernel.php | 49 +++- 6 files changed, 366 insertions(+), 554 deletions(-) diff --git a/app/Http/Controllers/Api/Admin/AuthController.php b/app/Http/Controllers/Api/Admin/AuthController.php index e649197..35180e8 100644 --- a/app/Http/Controllers/Api/Admin/AuthController.php +++ b/app/Http/Controllers/Api/Admin/AuthController.php @@ -4,9 +4,11 @@ namespace App\Http\Controllers\Api\Admin; +use App\Constants\ErrorCode; use App\Http\Controllers\Controller; use App\Models\Admin; use App\Services\LogService; +use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -16,27 +18,36 @@ class AuthController extends Controller { + use ApiResponse; + public function __construct( - private readonly LogService $logService + private readonly LogService $logService, ) {} + /** + * 管理员登录 + * + * @param Request $request + * @return JsonResponse + */ public function login(Request $request): JsonResponse { try { $validated = $request->validate([ - 'username' => 'required|string', + 'email' => 'required|email', 'password' => 'required|string', ]); - if (!Auth::guard('admin')->attempt($validated)) { - return response()->json([ - 'error' => 'invalid_credentials', - 'message' => '用户名或密码错误。', - ], 401); + $admin = Admin::where('email', $validated['email'])->first(); + + if (!$admin || !Hash::check($validated['password'], $admin->password)) { + return $this->error( + ErrorCode::INVALID_CREDENTIALS, + ErrorCode::getMessage(ErrorCode::INVALID_CREDENTIALS) + ); } - /** @var Admin $admin */ - $admin = Auth::guard('admin')->user(); + $token = $admin->createToken('admin-token')->plainTextToken; $this->logService->logOperation( 'admin', @@ -44,52 +55,74 @@ public function login(Request $request): JsonResponse 'Admin logged in' ); - return response()->json([ - 'id' => $admin->id, - 'username' => $admin->username, - 'email' => $admin->email, - 'role' => $admin->role, + return $this->success([ + 'token' => $token, + 'admin' => [ + 'id' => $admin->id, + 'name' => $admin->name, + 'email' => $admin->email, + ], ]); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (\Exception $e) { Log::error('Error during admin login', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } + /** + * 管理员登出 + * + * @param Request $request + * @return JsonResponse + */ public function logout(Request $request): JsonResponse { - /** @var Admin $admin */ - $admin = Auth::guard('admin')->user(); + try { + $admin = $request->user(); + $admin->currentAccessToken()->delete(); - Auth::guard('admin')->logout(); - $request->session()->invalidate(); - $request->session()->regenerateToken(); + $this->logService->logOperation( + 'admin', + $admin->id, + 'Admin logged out' + ); - $this->logService->logOperation( - 'admin', - $admin->id, - 'Admin logged out' - ); + return $this->success(null, '已成功登出。'); - return response()->json([ - 'message' => '已成功退出登录。', - ]); + } catch (\Exception $e) { + Log::error('Error during admin logout', [ + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + 'admin_id' => $request->user()?->id, + ]); + + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); + } } + /** + * 修改管理员密码 + * + * @param Request $request + * @return JsonResponse + */ public function changePassword(Request $request): JsonResponse { try { @@ -98,46 +131,43 @@ public function changePassword(Request $request): JsonResponse 'new_password' => 'required|string|min:8|confirmed', ]); - /** @var Admin $admin */ - $admin = Auth::guard('admin')->user(); + $admin = $request->user(); if (!Hash::check($validated['current_password'], $admin->password)) { - return response()->json([ - 'error' => 'invalid_password', - 'message' => '当前密码错误。', - ], 422); + return $this->error( + ErrorCode::INVALID_CREDENTIALS, + '当前密码错误。' + ); } - $admin->update([ - 'password' => Hash::make($validated['new_password']), - ]); + $admin->password = Hash::make($validated['new_password']); + $admin->save(); $this->logService->logOperation( 'admin', $admin->id, - 'Changed password' + 'Admin changed password' ); - return response()->json([ - 'message' => '密码已成功修改。', - ]); + return $this->success(null, '密码修改成功。'); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (\Exception $e) { Log::error('Error changing admin password', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), + 'admin_id' => $request->user()?->id, ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } } diff --git a/app/Http/Controllers/Api/Admin/ClientController.php b/app/Http/Controllers/Api/Admin/ClientController.php index dbb0227..b9e0894 100644 --- a/app/Http/Controllers/Api/Admin/ClientController.php +++ b/app/Http/Controllers/Api/Admin/ClientController.php @@ -4,10 +4,12 @@ namespace App\Http\Controllers\Api\Admin; +use App\Constants\ErrorCode; use App\Http\Controllers\Controller; use App\Models\Client; use App\Services\LogService; use App\Services\Auth\TokenService; +use App\Traits\ApiResponse; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -16,62 +18,30 @@ class ClientController extends Controller { + use ApiResponse; + public function __construct( private readonly LogService $logService, private readonly TokenService $tokenService, ) {} /** - * Get client list + * 获取客户列表 * * @param Request $request * @return JsonResponse - * - * Query parameters: - * - page: int (optional) - 页码,默认 1 - * - per_page: int (optional) - 每页数量,默认 15 - * - search: string (optional) - 搜索关键词,支持名称和邮箱搜索 - * - status: string (optional) - 状态筛选:active 或 inactive - * - * Success response: - * { - * "data": [ - * { - * "id": 1001, - * "name": "Client Name", - * "email": "client@example.com", - * "status": "active", - * "llm_provider_id": 1, - * "rate_limit": 100, - * "timeout": 30, - * "created_at": "2023-10-01T14:00:00Z", - * "llm_provider": { - * "id": 1, - * "name": "OpenAI", - * "service_name": "openai" - * } - * } - * ], - * "meta": { - * "current_page": 1, - * "per_page": 15, - * "total": 50, - * "total_pages": 4 - * } - * } */ public function index(Request $request): JsonResponse { try { $perPage = $request->input('per_page', 15); - $perPage = min(max($perPage, 1), 100); // 限制每页数量在 1-100 之间 + $perPage = min(max($perPage, 1), 100); $search = $request->input('search'); $status = $request->input('status'); $query = Client::with('llmProvider:id,name,service_name') ->orderBy('created_at', 'desc'); - // 搜索条件 if ($search) { $query->where(function ($q) use ($search) { $q->where('name', 'like', "%{$search}%") @@ -79,22 +49,21 @@ public function index(Request $request): JsonResponse }); } - // 状态筛选 if ($status && in_array($status, ['active', 'inactive'])) { $query->where('status', $status); } $clients = $query->paginate($perPage); - return response()->json([ - 'data' => $clients->items(), - 'meta' => [ + return $this->paginate( + $clients->items(), + [ 'current_page' => $clients->currentPage(), 'per_page' => $clients->perPage(), 'total' => $clients->total(), 'total_pages' => $clients->lastPage(), - ], - ]); + ] + ); } catch (\Exception $e) { Log::error('Error fetching clients', [ @@ -104,39 +73,18 @@ public function index(Request $request): JsonResponse 'status' => $status, ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Create new client + * 创建客户 * * @param Request $request * @return JsonResponse - * - * Request body: - * { - * "name": "string", - * "email": "string", - * "llm_provider_id": "integer", - * "rate_limit": "integer", - * "timeout": "integer" - * } - * - * Success response (201): - * { - * "id": 1001, - * "name": "Client Name", - * "email": "client@example.com", - * "status": "active", - * "llm_provider_id": 1, - * "rate_limit": 100, - * "timeout": 30, - * "created_at": "2023-10-01T14:00:00Z" - * } */ public function store(Request $request): JsonResponse { @@ -194,14 +142,14 @@ public function store(Request $request): JsonResponse "Created client: {$client->name}" ); - return response()->json($client, 201); + return $this->created($client); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (\Exception $e) { Log::error('Error creating client', [ 'error' => $e->getMessage(), @@ -209,46 +157,19 @@ public function store(Request $request): JsonResponse 'request_data' => $request->except(['api_token']), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Update client + * 更新客户 * * @param Request $request * @param int $id * @return JsonResponse - * - * Request body: - * { - * "name": "string", - * "email": "string", - * "llm_provider_id": "integer", - * "rate_limit": "integer", - * "timeout": "integer", - * "status": "string" - * } - * - * Success response: - * { - * "id": 1001, - * "name": "Client Name", - * "email": "client@example.com", - * "status": "active", - * "llm_provider_id": 1, - * "rate_limit": 100, - * "timeout": 30, - * "updated_at": "2023-10-02T14:00:00Z", - * "llm_provider": { - * "id": 1, - * "name": "OpenAI", - * "service_name": "openai" - * } - * } */ public function update(Request $request, int $id): JsonResponse { @@ -305,10 +226,10 @@ public function update(Request $request, int $id): JsonResponse // 如果状态改为 inactive,检查是否有活跃的认证令牌 if ($validated['status'] === 'inactive' && $client->status === 'active') { if ($client->authTokens()->where('status', 'active')->exists()) { - return response()->json([ - 'error' => 'active_tokens_exist', - 'message' => '该客户有活跃的认证令牌,请先停用所有令牌。', - ], 422); + return $this->error( + ErrorCode::CLIENT_HAS_ACTIVE_TOKENS, + '该客户有活跃的认证令牌,请先停用所有令牌。' + ); } } @@ -323,19 +244,19 @@ public function update(Request $request, int $id): JsonResponse "Updated client: {$client->name}" ); - return response()->json($client); + return $this->success($client); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (ModelNotFoundException $e) { - return response()->json([ - 'error' => 'not_found', - 'message' => '客户不存在。', - ], 404); + return $this->error( + ErrorCode::CLIENT_NOT_FOUND, + ErrorCode::getMessage(ErrorCode::CLIENT_NOT_FOUND) + ); } catch (\Exception $e) { Log::error('Error updating client', [ 'error' => $e->getMessage(), @@ -344,29 +265,19 @@ public function update(Request $request, int $id): JsonResponse 'request_data' => $request->except(['api_token']), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Delete client + * 删除客户 * * @param Request $request * @param int $id * @return JsonResponse - * - * Success response: - * { - * "message": "客户已删除。" - * } - * - * Error responses: - * - 404: 客户不存在 - * - 422: 客户有活跃的认证令牌 - * - 500: 服务器内部错误 */ public function destroy(Request $request, int $id): JsonResponse { @@ -375,15 +286,13 @@ public function destroy(Request $request, int $id): JsonResponse // 检查是否有活跃的认证令牌 if ($client->authTokens()->where('status', 'active')->exists()) { - return response()->json([ - 'error' => 'client_in_use', - 'message' => '该客户有活跃的认证令牌,请先停用或删除令牌。', - ], 422); + return $this->error( + ErrorCode::CLIENT_HAS_ACTIVE_TOKENS, + '该客户有活跃的认证令牌,请先停用或删除令牌。' + ); } $clientName = $client->name; - - // 软删除客户记录 $client->delete(); $this->logService->logOperation( @@ -392,15 +301,13 @@ public function destroy(Request $request, int $id): JsonResponse "Deleted client: {$clientName}" ); - return response()->json([ - 'message' => '客户已删除。', - ]); + return $this->success(null, '客户已删除。'); } catch (ModelNotFoundException $e) { - return response()->json([ - 'error' => 'not_found', - 'message' => '客户不存在。', - ], 404); + return $this->error( + ErrorCode::CLIENT_NOT_FOUND, + ErrorCode::getMessage(ErrorCode::CLIENT_NOT_FOUND) + ); } catch (\Exception $e) { Log::error('Error deleting client', [ 'error' => $e->getMessage(), @@ -408,41 +315,19 @@ public function destroy(Request $request, int $id): JsonResponse 'client_id' => $id, ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Generate auth token for client + * 生成认证令牌 * * @param Request $request * @param int $id * @return JsonResponse - * - * Request body: - * { - * "expires_in_days": int (optional) // 令牌有效期(天),默认 30 天 - * } - * - * Success response: - * { - * "auth_token": "string", // 认证令牌 - * "expires_at": "2024-10-01T14:00:00Z", // 过期时间 - * "client": { // 客户信息 - * "id": 1001, - * "name": "Client Name", - * "email": "client@example.com", - * "status": "active" - * } - * } - * - * Error responses: - * - 404: 客户不存在 - * - 422: 客户状态非活跃 - * - 500: 服务器内部错误 */ public function generateAuthToken(Request $request, int $id): JsonResponse { @@ -450,10 +335,10 @@ public function generateAuthToken(Request $request, int $id): JsonResponse $client = Client::findOrFail($id); if ($client->status !== 'active') { - return response()->json([ - 'error' => 'client_inactive', - 'message' => '客户状态为非活跃,无法生成认证令牌。', - ], 422); + return $this->error( + ErrorCode::CLIENT_INACTIVE, + ErrorCode::getMessage(ErrorCode::CLIENT_INACTIVE) + ); } // 验证过期时间 @@ -475,7 +360,7 @@ public function generateAuthToken(Request $request, int $id): JsonResponse "Generated auth token for client: {$client->name}" ); - return response()->json([ + return $this->success([ 'auth_token' => $token->token, 'expires_at' => $token->expires_at, 'client' => [ @@ -487,16 +372,16 @@ public function generateAuthToken(Request $request, int $id): JsonResponse ]); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (ModelNotFoundException $e) { - return response()->json([ - 'error' => 'not_found', - 'message' => '客户不存在。', - ], 404); + return $this->error( + ErrorCode::CLIENT_NOT_FOUND, + ErrorCode::getMessage(ErrorCode::CLIENT_NOT_FOUND) + ); } catch (\Exception $e) { Log::error('Error generating auth token', [ 'error' => $e->getMessage(), @@ -505,10 +390,10 @@ public function generateAuthToken(Request $request, int $id): JsonResponse 'request_data' => $request->except(['api_token']), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } } diff --git a/app/Http/Controllers/Api/Admin/LlmProviderController.php b/app/Http/Controllers/Api/Admin/LlmProviderController.php index ac8b8ca..3b38bd2 100644 --- a/app/Http/Controllers/Api/Admin/LlmProviderController.php +++ b/app/Http/Controllers/Api/Admin/LlmProviderController.php @@ -4,9 +4,11 @@ namespace App\Http\Controllers\Api\Admin; +use App\Constants\ErrorCode; use App\Http\Controllers\Controller; use App\Models\LlmProvider; use App\Services\LogService; +use App\Traits\ApiResponse; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -15,57 +17,50 @@ class LlmProviderController extends Controller { + use ApiResponse; + public function __construct( - private readonly LogService $logService + private readonly LogService $logService, ) {} /** - * Get LLM provider list + * 获取 LLM 提供商列表 * * @param Request $request * @return JsonResponse - * - * Query parameters: - * - page: int (optional) - Page number - * - per_page: int (optional) - Items per page - * - * Success response: - * { - * "data": [ - * { - * "id": 1001, - * "name": "OpenAI", - * "service_name": "openai", - * "api_url": "https://api.openai.com/v1/chat/completions", - * "created_at": "2023-10-01T14:00:00Z" - * } - * ], - * "meta": { - * "current_page": 1, - * "per_page": 15, - * "total": 50, - * "total_pages": 4 - * } - * } */ public function index(Request $request): JsonResponse { try { $perPage = $request->input('per_page', 15); - $perPage = min(max($perPage, 1), 100); // 限制每页数量在 1-100 之间 + $perPage = min(max($perPage, 1), 100); + $search = $request->input('search'); + $status = $request->input('status'); - $providers = LlmProvider::orderBy('created_at', 'desc') - ->paginate($perPage); + $query = LlmProvider::query()->orderBy('created_at', 'desc'); - return response()->json([ - 'data' => $providers->items(), - 'meta' => [ + if ($search) { + $query->where(function ($q) use ($search) { + $q->where('name', 'like', "%{$search}%") + ->orWhere('service_name', 'like', "%{$search}%"); + }); + } + + if ($status && in_array($status, ['active', 'inactive'])) { + $query->where('status', $status); + } + + $providers = $query->paginate($perPage); + + return $this->paginate( + $providers->items(), + [ 'current_page' => $providers->currentPage(), 'per_page' => $providers->perPage(), 'total' => $providers->total(), 'total_pages' => $providers->lastPage(), - ], - ]); + ] + ); } catch (\Exception $e) { Log::error('Error fetching LLM providers', [ @@ -73,45 +68,18 @@ public function index(Request $request): JsonResponse 'trace' => $e->getTraceAsString(), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Create new LLM provider + * 创建 LLM 提供商 * * @param Request $request * @return JsonResponse - * - * Request body: - * { - * "name": "string", - * "service_name": "string", - * "api_url": "string", - * "api_token": "string" - * } - * - * Success response (201): - * { - * "id": 1001, - * "name": "OpenAI", - * "service_name": "openai", - * "api_url": "https://api.openai.com/v1/chat/completions", - * "created_at": "2023-10-01T14:00:00Z" - * } - * - * Error response (422): - * { - * "error": "validation_error", - * "message": "请求参数验证失败。", - * "errors": { - * "name": ["提供商名称已存在。"], - * "api_url": ["API URL 格式不正确。"] - * } - * } */ public function store(Request $request): JsonResponse { @@ -122,28 +90,29 @@ public function store(Request $request): JsonResponse 'string', 'max:100', 'unique:llm_providers', - 'regex:/^[\w\-\s]+$/u', // 只允许字母、数字、下划线、横线和空格 + 'regex:/^[\w\-\s]+$/u', ], 'service_name' => [ 'required', 'string', 'max:100', - 'regex:/^[\w\-]+$/u', // 只允许字母、数字、下划线和横线 + 'regex:/^[\w\-]+$/u', ], 'api_url' => [ 'required', 'string', 'max:255', 'url', - 'starts_with:https', // 强制使用 HTTPS + 'starts_with:https', ], 'api_token' => 'required|string|max:255', ], [ - 'name.regex' => '提供商名称只能包含字母、数字、下划线、横线和空��。', + 'name.regex' => '提供商名称只能包含字母、数字、下划线、横线和空格。', 'service_name.regex' => '服务名称只能包含字母、数字、下划线和横线。', 'api_url.starts_with' => 'API URL 必须使用 HTTPS 协议。', ]); + $validated['status'] = 'active'; $provider = LlmProvider::create($validated); $this->logService->logOperation( @@ -152,50 +121,34 @@ public function store(Request $request): JsonResponse "Created LLM provider: {$provider->name}" ); - return response()->json($provider, 201); + return $this->created($provider); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (\Exception $e) { Log::error('Error creating LLM provider', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), + 'request_data' => $request->except(['api_token']), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Update LLM provider + * 更新 LLM 提供商 * * @param Request $request * @param int $id * @return JsonResponse - * - * Request body: - * { - * "name": "string", - * "service_name": "string", - * "api_url": "string", - * "api_token": "string" - * } - * - * Success response: - * { - * "id": 1001, - * "name": "OpenAI Updated", - * "service_name": "openai", - * "api_url": "https://api.openai.com/v1/chat/completions", - * "updated_at": "2023-10-02T14:00:00Z" - * } */ public function update(Request $request, int $id): JsonResponse { @@ -208,28 +161,44 @@ public function update(Request $request, int $id): JsonResponse 'string', 'max:100', "unique:llm_providers,name,{$id}", - 'regex:/^[\w\-\s]+$/u', // 只允许字母、数字、下划线、横线和空格 + 'regex:/^[\w\-\s]+$/u', ], 'service_name' => [ 'required', 'string', 'max:100', - 'regex:/^[\w\-]+$/u', // 只允许字母、数字、下划线和横线 + 'regex:/^[\w\-]+$/u', ], 'api_url' => [ 'required', 'string', 'max:255', 'url', - 'starts_with:https', // 强制使用 HTTPS + 'starts_with:https', ], 'api_token' => 'required|string|max:255', + 'status' => [ + 'required', + 'string', + 'in:active,inactive', + ], ], [ 'name.regex' => '提供商名称只能包含字母、数字、下划线、横线和空格。', 'service_name.regex' => '服务名称只能包含字母、数字、下划线和横线。', 'api_url.starts_with' => 'API URL 必须使用 HTTPS 协议。', + 'status.in' => '状态只能是 active 或 inactive。', ]); + // 如果状态改为 inactive,检查是否有客户正在使用 + if ($validated['status'] === 'inactive' && $provider->status === 'active') { + if ($provider->clients()->exists()) { + return $this->error( + ErrorCode::RESOURCE_IN_USE, + '该提供商正在被客户使用,无法停用。' + ); + } + } + $provider->update($validated); $this->logService->logOperation( @@ -238,55 +207,52 @@ public function update(Request $request, int $id): JsonResponse "Updated LLM provider: {$provider->name}" ); - return response()->json($provider); + return $this->success($provider); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'errors' => $e->errors(), - ], 422); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); } catch (ModelNotFoundException $e) { - return response()->json([ - 'error' => 'not_found', - 'message' => 'LLM 提供商不存在。', - ], 404); + return $this->error( + ErrorCode::RESOURCE_NOT_FOUND, + ErrorCode::getMessage(ErrorCode::RESOURCE_NOT_FOUND) + ); } catch (\Exception $e) { Log::error('Error updating LLM provider', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), + 'provider_id' => $id, + 'request_data' => $request->except(['api_token']), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } /** - * Delete LLM provider + * 删除 LLM 提供商 * * @param Request $request * @param int $id * @return JsonResponse - * - * Success response: - * { - * "message": "提供商已删除。" - * } */ public function destroy(Request $request, int $id): JsonResponse { try { $provider = LlmProvider::findOrFail($id); - // 检查是否有客户正在使用该提供商 + // 检查是否有客户正在使用 if ($provider->clients()->exists()) { - return response()->json([ - 'error' => 'provider_in_use', - 'message' => '该提供商正在被客户使用,无法删除。', - ], 422); + return $this->error( + ErrorCode::RESOURCE_IN_USE, + '该提供商正在被客户使用,无法删除。' + ); } $providerName = $provider->name; @@ -298,15 +264,13 @@ public function destroy(Request $request, int $id): JsonResponse "Deleted LLM provider: {$providerName}" ); - return response()->json([ - 'message' => '提供商已删除。', - ]); + return $this->success(null, '提供商已删除。'); } catch (ModelNotFoundException $e) { - return response()->json([ - 'error' => 'not_found', - 'message' => 'LLM 提供商不存在。', - ], 404); + return $this->error( + ErrorCode::RESOURCE_NOT_FOUND, + ErrorCode::getMessage(ErrorCode::RESOURCE_NOT_FOUND) + ); } catch (\Exception $e) { Log::error('Error deleting LLM provider', [ 'error' => $e->getMessage(), @@ -314,10 +278,10 @@ public function destroy(Request $request, int $id): JsonResponse 'provider_id' => $id, ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } } diff --git a/app/Http/Controllers/Api/AuthController.php b/app/Http/Controllers/Api/AuthController.php index 70f3701..9510f39 100644 --- a/app/Http/Controllers/Api/AuthController.php +++ b/app/Http/Controllers/Api/AuthController.php @@ -4,9 +4,10 @@ namespace App\Http\Controllers\Api; +use App\Constants\ErrorCode; use App\Http\Controllers\Controller; -use App\Rules\ThrottleAuthToken; use App\Services\Auth\TokenService; +use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; @@ -14,74 +15,53 @@ class AuthController extends Controller { + use ApiResponse; + public function __construct( - private readonly TokenService $tokenService + private readonly TokenService $tokenService, ) {} /** - * Get access token using auth token + * 获取访问令牌 * * @param Request $request * @return JsonResponse - * - * Request body: - * { - * "auth_token": "string" - * } - * - * Success response: - * { - * "access_token": "string", - * "expires_in": 3600 - * } - * - * Error response: - * { - * "error": "error_code", - * "message": "错误描述" - * } */ public function getAccessToken(Request $request): JsonResponse { try { $validated = $request->validate([ - 'auth_token' => [ - 'required', - 'string', - 'size:64', - new ThrottleAuthToken, - ], + 'auth_token' => 'required|string|size:64', ]); - $authToken = $this->tokenService->validateAuthToken($validated['auth_token']); + $result = $this->tokenService->generateAccessToken($validated['auth_token']); - if (!$authToken) { - return response()->json([ - 'error' => 'invalid_auth_token', - 'message' => '认证令牌无效。', - ], 401); - } - - $result = $this->tokenService->generateAccessToken($authToken); - - return response()->json($result); + return $this->success([ + 'access_token' => $result['token'], + 'expires_at' => $result['expires_at'], + ]); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'invalid_request', - 'message' => '请求参数缺失或格式错误。', - 'details' => $e->errors(), - ], 400); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); + } catch (\InvalidArgumentException $e) { + return $this->error( + ErrorCode::TOKEN_INVALID, + $e->getMessage() + ); } catch (\Exception $e) { Log::error('Error generating access token', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } } diff --git a/app/Http/Controllers/Api/LlmController.php b/app/Http/Controllers/Api/LlmController.php index e437888..9feec6d 100644 --- a/app/Http/Controllers/Api/LlmController.php +++ b/app/Http/Controllers/Api/LlmController.php @@ -4,146 +4,80 @@ namespace App\Http\Controllers\Api; +use App\Constants\ErrorCode; use App\Http\Controllers\Controller; -use App\Models\Client; -use App\Services\LogService; +use App\Services\LlmService; +use App\Traits\ApiResponse; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\RateLimiter; use Illuminate\Validation\ValidationException; class LlmController extends Controller { - private const REQUEST_TIMEOUT = 30; // 请求超时时间(秒) - private const MAX_ATTEMPTS = 60; // 每分钟最大请求次数 - private const DECAY_MINUTES = 1; // 重置时间(分钟) + use ApiResponse; public function __construct( - private readonly LogService $logService + private readonly LlmService $llmService, ) {} /** - * Send prompt request to LLM provider + * 发送 LLM 请求 * * @param Request $request * @return JsonResponse - * - * Request headers: - * - Content-Type: application/json - * - Authorization: Bearer {access_token} - * - * Request body: - * { - * "prompt": "string" - * } - * - * Success response: - * { - * "response": "LLM 提供商返回的响应内容" - * } - * - * Error response: - * { - * "error": "error_code", - * "message": "错误描述" - * } */ public function request(Request $request): JsonResponse { try { - $clientId = $request->get('client_id'); - - // 检查请求频率限制 - $key = 'llm_request_' . $clientId; - if (RateLimiter::tooManyAttempts($key, self::MAX_ATTEMPTS)) { - $seconds = RateLimiter::availableIn($key); - return response()->json([ - 'error' => 'too_many_requests', - 'message' => "请求过于频繁,请在 {$seconds} 秒后重试。", - ], 429); - } - $validated = $request->validate([ - 'prompt' => 'required|string|max:4096', + 'prompt' => 'required|string|max:4000', + 'max_tokens' => 'nullable|integer|min:1|max:4000', + 'temperature' => 'nullable|numeric|min:0|max:2', + 'top_p' => 'nullable|numeric|min:0|max:1', + 'frequency_penalty' => 'nullable|numeric|min:-2|max:2', + 'presence_penalty' => 'nullable|numeric|min:-2|max:2', ]); - $client = Client::with('llmProvider')->findOrFail($clientId); - $provider = $client->llmProvider; - - // Send request to LLM provider with timeout - $response = Http::timeout(self::REQUEST_TIMEOUT) - ->withToken($provider->api_token) - ->withHeaders([ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ]) - ->post($provider->api_url, [ - 'prompt' => $validated['prompt'], - ]); - - if (!$response->successful()) { - Log::error('LLM provider request failed', [ - 'status' => $response->status(), - 'body' => $response->body(), - 'provider' => $provider->name, - ]); - - throw new \Exception('LLM provider request failed: ' . $response->body()); - } - - // 记录成功的请求 - RateLimiter::hit($key, self::DECAY_MINUTES * 60); - - $this->logService->logOperation( - 'client', - $client->id, - 'Sent prompt to LLM provider: ' . $provider->name + $result = $this->llmService->sendRequest( + $request->client, + $validated['prompt'], + array_filter($validated, fn($key) => $key !== 'prompt', ARRAY_FILTER_USE_KEY) ); - return response()->json([ - 'response' => $response->json(), + return $this->success([ + 'response' => $result['response'], + 'usage' => $result['usage'], ]); } catch (ValidationException $e) { - return response()->json([ - 'error' => 'validation_error', - 'message' => '请求参数验证失败。', - 'details' => $e->errors(), - ], 422); - } catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) { - return response()->json([ - 'error' => 'not_found', - 'message' => '客户用户不存在。', - ], 404); - } catch (\Illuminate\Http\Client\ConnectionException $e) { - Log::error('LLM provider connection timeout', [ - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString(), - ]); - - return response()->json([ - 'error' => 'provider_timeout', - 'message' => 'LLM 提供商响应超时,请稍后重试。', - ], 504); + return $this->error( + ErrorCode::VALIDATION_ERROR, + ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR), + $e->errors() + ); + } catch (\InvalidArgumentException $e) { + return $this->error( + ErrorCode::INVALID_REQUEST_FORMAT, + $e->getMessage() + ); + } catch (\RuntimeException $e) { + return $this->error( + ErrorCode::PROVIDER_ERROR, + $e->getMessage() + ); } catch (\Exception $e) { Log::error('Error processing LLM request', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), + 'client_id' => $request->client->id, + 'request_data' => $request->except(['prompt']), ]); - if (str_contains($e->getMessage(), 'LLM provider request failed')) { - return response()->json([ - 'error' => 'provider_error', - 'message' => 'LLM 提供商服务异常,请稍后重试。', - ], 502); - } - - return response()->json([ - 'error' => 'server_error', - 'message' => '服务器内部错误。', - ], 500); + return $this->error( + ErrorCode::SERVER_ERROR, + ErrorCode::getMessage(ErrorCode::SERVER_ERROR) + ); } } } diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 1987fe6..5529ef7 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -7,27 +7,24 @@ class Kernel extends HttpKernel { /** - * The application's route middleware. + * The application's global HTTP middleware stack. * - * These middleware may be assigned to groups or used individually. + * These middleware are run during every request to your application. * - * @var array + * @var array */ - protected $routeMiddleware = [ - 'auth' => \App\Http\Middleware\Authenticate::class, - 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, - 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, - 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, - 'can' => \Illuminate\Auth\Middleware\Authorize::class, - 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, - 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, - 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, - 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, - 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + protected $middleware = [ + // \App\Http\Middleware\TrustHosts::class, + \App\Http\Middleware\TrustProxies::class, + \Illuminate\Http\Middleware\HandleCors::class, + \App\Http\Middleware\PreventRequestsDuringMaintenance::class, + \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, + \App\Http\Middleware\TrimStrings::class, + \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ]; /** - * The application's middleware groups. + * The application's route middleware groups. * * @var array> */ @@ -47,4 +44,26 @@ class Kernel extends HttpKernel \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; + + /** + * The application's route middleware. + * + * These middleware may be assigned to groups or used individually. + * + * @var array + */ + protected $routeMiddleware = [ + 'auth' => \App\Http\Middleware\Authenticate::class, + 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, + 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, + 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, + 'can' => \Illuminate\Auth\Middleware\Authorize::class, + 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, + 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, + 'signed' => \App\Http\Middleware\ValidateSignature::class, + 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, + 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, + 'auth.access_token' => \App\Http\Middleware\ValidateAccessToken::class, + 'admin' => \App\Http\Middleware\AdminAuthenticate::class, + ]; }