4. [API 详细说明](#4-api-详细说明)
- [4.1 认证 API](#41-认证-api)
- [4.1.1 获取访问令牌](#411-获取访问令牌)
- [4.2 客户用户 API](#42-客户用户-api)
- [4.2.1 发送提示词请求](#421-发送提示词请求)
- [4.3 管理员 API](#43-管理员-api)
- [4.3.1 LLM 提供商管理](#431-llm-提供商管理)
- [4.3.1.1 新增 LLM 提供商](#4311-新增-llm-提供商)
- [4.3.1.2 修改 LLM 提供商](#4312-修改-llm-提供商)
- [4.3.1.3 删除 LLM 提供商](#4313-删除-llm-提供商)
- [4.3.1.4 获取 LLM 提供商列表](#4314-获取-llm-提供商列表)
This commit is contained in:
parent
9cebfec5b0
commit
c44c25d86f
9 changed files with 933 additions and 10 deletions
|
|
@ -1 +1,143 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Admin;
|
||||||
|
use App\Services\LogService;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class AuthController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly LogService $logService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function login(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'username' => 'required|string',
|
||||||
|
'password' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!Auth::guard('admin')->attempt($validated)) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'invalid_credentials',
|
||||||
|
'message' => '用户名或密码错误。',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Admin $admin */
|
||||||
|
$admin = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$admin->id,
|
||||||
|
'Admin logged in'
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'id' => $admin->id,
|
||||||
|
'username' => $admin->username,
|
||||||
|
'email' => $admin->email,
|
||||||
|
'role' => $admin->role,
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error during admin login', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logout(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
/** @var Admin $admin */
|
||||||
|
$admin = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
Auth::guard('admin')->logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$admin->id,
|
||||||
|
'Admin logged out'
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => '已成功退出登录。',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function changePassword(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'current_password' => 'required|string',
|
||||||
|
'new_password' => 'required|string|min:8|confirmed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** @var Admin $admin */
|
||||||
|
$admin = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
if (!Hash::check($validated['current_password'], $admin->password)) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'invalid_password',
|
||||||
|
'message' => '当前密码错误。',
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$admin->update([
|
||||||
|
'password' => Hash::make($validated['new_password']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$admin->id,
|
||||||
|
'Changed password'
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => '密码已成功修改。',
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error changing admin password', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,202 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Services\Auth\TokenService;
|
||||||
|
use App\Services\LogService;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class ClientController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly LogService $logService,
|
||||||
|
private readonly TokenService $tokenService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function index(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$admin = $request->admin;
|
||||||
|
$query = $admin->isSuperAdmin() ? Client::query() : $admin->clients();
|
||||||
|
|
||||||
|
$clients = $query->with('llmProvider')->get();
|
||||||
|
return response()->json($clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100',
|
||||||
|
'llm_provider_id' => 'required|exists:llm_providers,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
$client = Client::create($validated);
|
||||||
|
|
||||||
|
// Associate client with admin
|
||||||
|
$request->admin->clients()->attach($client->id);
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Created client: {$client->name}"
|
||||||
|
);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
return response()->json($client, 201);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
Log::error('Error creating client', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$client = $this->getAuthorizedClient($request->admin, $id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => 'required|string|max:100',
|
||||||
|
'llm_provider_id' => 'required|exists:llm_providers,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$client->update($validated);
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Updated client: {$client->name}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($client);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error updating client', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy(Request $request, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$client = $this->getAuthorizedClient($request->admin, $id);
|
||||||
|
|
||||||
|
$clientName = $client->name;
|
||||||
|
$client->delete();
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Deleted client: {$clientName}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => '客户用户已删除。',
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error deleting client', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generateAuthToken(Request $request, int $id): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$client = $this->getAuthorizedClient($request->admin, $id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'expires_in_days' => 'nullable|integer|min:1',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$token = $this->tokenService->generateAuthToken(
|
||||||
|
$client,
|
||||||
|
$validated['expires_in_days'] ?? null
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Generated auth token for client: {$client->name}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'client_id' => $client->id,
|
||||||
|
'auth_token' => $token->token,
|
||||||
|
'created_at' => $token->created_at,
|
||||||
|
'expires_at' => $token->expires_at,
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error generating auth token', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getAuthorizedClient($admin, int $id): Client
|
||||||
|
{
|
||||||
|
$query = $admin->isSuperAdmin() ? Client::query() : $admin->clients();
|
||||||
|
|
||||||
|
$client = $query->findOrFail($id);
|
||||||
|
|
||||||
|
return $client;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,323 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\LlmProvider;
|
||||||
|
use App\Services\LogService;
|
||||||
|
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
|
class LlmProviderController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private readonly LogService $logService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get LLM provider list
|
||||||
|
*
|
||||||
|
* @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 之间
|
||||||
|
|
||||||
|
$providers = LlmProvider::orderBy('created_at', 'desc')
|
||||||
|
->paginate($perPage);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'data' => $providers->items(),
|
||||||
|
'meta' => [
|
||||||
|
'current_page' => $providers->currentPage(),
|
||||||
|
'per_page' => $providers->perPage(),
|
||||||
|
'total' => $providers->total(),
|
||||||
|
'total_pages' => $providers->lastPage(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error fetching LLM providers', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new LLM provider
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:100',
|
||||||
|
'unique:llm_providers',
|
||||||
|
'regex:/^[\w\-\s]+$/u', // 只允许字母、数字、下划线、横线和空格
|
||||||
|
],
|
||||||
|
'service_name' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:100',
|
||||||
|
'regex:/^[\w\-]+$/u', // 只允许字母、数字、下划线和横线
|
||||||
|
],
|
||||||
|
'api_url' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:255',
|
||||||
|
'url',
|
||||||
|
'starts_with:https', // 强制使用 HTTPS
|
||||||
|
],
|
||||||
|
'api_token' => 'required|string|max:255',
|
||||||
|
], [
|
||||||
|
'name.regex' => '提供商名称只能包含字母、数字、下划线、横线和空<E5928C><E7A9BA>。',
|
||||||
|
'service_name.regex' => '服务名称只能包含字母、数字、下划线和横线。',
|
||||||
|
'api_url.starts_with' => 'API URL 必须使用 HTTPS 协议。',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$provider = LlmProvider::create($validated);
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Created LLM provider: {$provider->name}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($provider, 201);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error creating LLM provider', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update LLM provider
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$provider = LlmProvider::findOrFail($id);
|
||||||
|
|
||||||
|
$validated = $request->validate([
|
||||||
|
'name' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:100',
|
||||||
|
"unique:llm_providers,name,{$id}",
|
||||||
|
'regex:/^[\w\-\s]+$/u', // 只允许字母、数字、下划线、横线和空格
|
||||||
|
],
|
||||||
|
'service_name' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:100',
|
||||||
|
'regex:/^[\w\-]+$/u', // 只允许字母、数字、下划线和横线
|
||||||
|
],
|
||||||
|
'api_url' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'max:255',
|
||||||
|
'url',
|
||||||
|
'starts_with:https', // 强制使用 HTTPS
|
||||||
|
],
|
||||||
|
'api_token' => 'required|string|max:255',
|
||||||
|
], [
|
||||||
|
'name.regex' => '提供商名称只能包含字母、数字、下划线、横线和空格。',
|
||||||
|
'service_name.regex' => '服务名称只能包含字母、数字、下划线和横线。',
|
||||||
|
'api_url.starts_with' => 'API URL 必须使用 HTTPS 协议。',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$provider->update($validated);
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Updated LLM provider: {$provider->name}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json($provider);
|
||||||
|
|
||||||
|
} catch (ValidationException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'validation_error',
|
||||||
|
'message' => '请求参数验证失败。',
|
||||||
|
'errors' => $e->errors(),
|
||||||
|
], 422);
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'not_found',
|
||||||
|
'message' => 'LLM 提供商不存在。',
|
||||||
|
], 404);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error updating LLM provider', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete LLM provider
|
||||||
|
*
|
||||||
|
* @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);
|
||||||
|
}
|
||||||
|
|
||||||
|
$providerName = $provider->name;
|
||||||
|
$provider->delete();
|
||||||
|
|
||||||
|
$this->logService->logOperation(
|
||||||
|
'admin',
|
||||||
|
$request->admin->id,
|
||||||
|
"Deleted LLM provider: {$providerName}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => '提供商已删除。',
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (ModelNotFoundException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'not_found',
|
||||||
|
'message' => 'LLM 提供商不存在。',
|
||||||
|
], 404);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error deleting LLM provider', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
'provider_id' => $id,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'server_error',
|
||||||
|
'message' => '服务器内部错误。',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
namespace App\Http\Controllers\Api;
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Rules\ThrottleAuthToken;
|
||||||
use App\Services\Auth\TokenService;
|
use App\Services\Auth\TokenService;
|
||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
@ -17,11 +18,39 @@ 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
|
public function getAccessToken(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'auth_token' => 'required|string',
|
'auth_token' => [
|
||||||
|
'required',
|
||||||
|
'string',
|
||||||
|
'size:64',
|
||||||
|
new ThrottleAuthToken,
|
||||||
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$authToken = $this->tokenService->validateAuthToken($validated['auth_token']);
|
$authToken = $this->tokenService->validateAuthToken($validated['auth_token']);
|
||||||
|
|
@ -33,14 +62,15 @@ public function getAccessToken(Request $request): JsonResponse
|
||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
$accessToken = $this->tokenService->generateAccessToken($authToken);
|
$result = $this->tokenService->generateAccessToken($authToken);
|
||||||
|
|
||||||
return response()->json($accessToken);
|
return response()->json($result);
|
||||||
|
|
||||||
} catch (ValidationException $e) {
|
} catch (ValidationException $e) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'error' => 'invalid_request',
|
'error' => 'invalid_request',
|
||||||
'message' => '请求参数缺失。',
|
'message' => '请求参数缺失或格式错误。',
|
||||||
|
'details' => $e->errors(),
|
||||||
], 400);
|
], 400);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
Log::error('Error generating access token', [
|
Log::error('Error generating access token', [
|
||||||
|
|
|
||||||
|
|
@ -1 +1,149 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Client;
|
||||||
|
use App\Services\LogService;
|
||||||
|
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; // 重置时间(分钟)
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly LogService $logService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send prompt request to LLM provider
|
||||||
|
*
|
||||||
|
* @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',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'response' => $response->json(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
} 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);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
Log::error('Error processing LLM request', [
|
||||||
|
'error' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class AdminAuthenticate
|
||||||
|
{
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
if (!Auth::guard('admin')->check()) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'unauthorized',
|
||||||
|
'message' => '未授权,请先登录。',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$admin = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
// Add admin information to the request
|
||||||
|
$request->merge(['admin' => $admin]);
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Http\Middleware\AdminAuthenticate;
|
||||||
use App\Http\Middleware\ValidateAccessToken;
|
use App\Http\Middleware\ValidateAccessToken;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
@ -22,5 +23,6 @@ public function register(): void
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
Route::aliasMiddleware('auth.access_token', ValidateAccessToken::class);
|
Route::aliasMiddleware('auth.access_token', ValidateAccessToken::class);
|
||||||
|
Route::aliasMiddleware('auth.admin', AdminAuthenticate::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
28
app/Rules/ThrottleAuthToken.php
Normal file
28
app/Rules/ThrottleAuthToken.php
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Rules;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\Validation\Rule;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
class ThrottleAuthToken implements Rule
|
||||||
|
{
|
||||||
|
private const MAX_ATTEMPTS = 5; // 最大尝试次数
|
||||||
|
private const DECAY_MINUTES = 1; // 重置时间(分钟)
|
||||||
|
|
||||||
|
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||||
|
{
|
||||||
|
$key = 'auth_token_' . $value;
|
||||||
|
|
||||||
|
if (RateLimiter::tooManyAttempts($key, self::MAX_ATTEMPTS)) {
|
||||||
|
$seconds = RateLimiter::availableIn($key);
|
||||||
|
$fail("请求过于频繁,请在 {$seconds} 秒后重试。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RateLimiter::hit($key, self::DECAY_MINUTES * 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\Admin\AuthController as AdminAuthController;
|
||||||
|
use App\Http\Controllers\Api\Admin\ClientController;
|
||||||
|
use App\Http\Controllers\Api\Admin\LlmProviderController;
|
||||||
use App\Http\Controllers\Api\AuthController;
|
use App\Http\Controllers\Api\AuthController;
|
||||||
|
use App\Http\Controllers\Api\LlmController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
// Public routes
|
// Public routes
|
||||||
|
|
@ -8,5 +12,22 @@
|
||||||
|
|
||||||
// Protected routes (require access token)
|
// Protected routes (require access token)
|
||||||
Route::middleware('auth.access_token')->group(function () {
|
Route::middleware('auth.access_token')->group(function () {
|
||||||
// Add protected routes here
|
Route::post('/llm/request', [LlmController::class, 'request']);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin authentication routes
|
||||||
|
Route::prefix('admin')->group(function () {
|
||||||
|
Route::post('login', [AdminAuthController::class, 'login']);
|
||||||
|
|
||||||
|
Route::middleware('auth.admin')->group(function () {
|
||||||
|
Route::post('logout', [AdminAuthController::class, 'logout']);
|
||||||
|
Route::post('change-password', [AdminAuthController::class, 'changePassword']);
|
||||||
|
|
||||||
|
// LLM Provider management
|
||||||
|
Route::apiResource('llm-providers', LlmProviderController::class);
|
||||||
|
|
||||||
|
// Client management
|
||||||
|
Route::apiResource('clients', ClientController::class);
|
||||||
|
Route::post('clients/{id}/auth-token', [ClientController::class, 'generateAuthToken']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue