remove Traits
This commit is contained in:
parent
27cad57159
commit
1ab5da5576
14 changed files with 362 additions and 234 deletions
|
|
@ -8,7 +8,6 @@
|
|||
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;
|
||||
|
|
@ -18,26 +17,33 @@
|
|||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
/**
|
||||
* @var LogService
|
||||
*/
|
||||
private readonly LogService $logService;
|
||||
|
||||
public function __construct(
|
||||
private readonly LogService $logService,
|
||||
) {}
|
||||
public function __construct(LogService $logService)
|
||||
{
|
||||
$this->logService = $logService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员登录
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
/** @var array{email: string, password: string} $validated */
|
||||
$validated = $request->validate([
|
||||
'email' => 'required|email',
|
||||
'password' => 'required|string',
|
||||
]);
|
||||
|
||||
/** @var Admin|null $admin */
|
||||
$admin = Admin::where('email', $validated['email'])->first();
|
||||
|
||||
if (!$admin || !Hash::check($validated['password'], $admin->password)) {
|
||||
|
|
@ -47,6 +53,7 @@ public function login(Request $request): JsonResponse
|
|||
);
|
||||
}
|
||||
|
||||
/** @var string $token */
|
||||
$token = $admin->createToken('admin-token')->plainTextToken;
|
||||
|
||||
$this->logService->logOperation(
|
||||
|
|
@ -59,7 +66,6 @@ public function login(Request $request): JsonResponse
|
|||
'token' => $token,
|
||||
'admin' => [
|
||||
'id' => $admin->id,
|
||||
'name' => $admin->name,
|
||||
'email' => $admin->email,
|
||||
],
|
||||
]);
|
||||
|
|
@ -92,7 +98,16 @@ public function login(Request $request): JsonResponse
|
|||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
/** @var Admin|null $admin */
|
||||
$admin = $request->user();
|
||||
|
||||
if (!$admin) {
|
||||
return $this->error(
|
||||
ErrorCode::UNAUTHORIZED,
|
||||
'未登錄或會話已過期。'
|
||||
);
|
||||
}
|
||||
|
||||
$admin->currentAccessToken()->delete();
|
||||
|
||||
$this->logService->logOperation(
|
||||
|
|
@ -122,17 +137,27 @@ public function logout(Request $request): JsonResponse
|
|||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function changePassword(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
/** @var Admin|null $admin */
|
||||
$admin = $request->user();
|
||||
|
||||
if (!$admin) {
|
||||
return $this->error(
|
||||
ErrorCode::UNAUTHORIZED,
|
||||
'未登錄或會話已過期。'
|
||||
);
|
||||
}
|
||||
|
||||
/** @var array{current_password: string, new_password: string} $validated */
|
||||
$validated = $request->validate([
|
||||
'current_password' => 'required|string',
|
||||
'new_password' => 'required|string|min:8|confirmed',
|
||||
]);
|
||||
|
||||
$admin = $request->user();
|
||||
|
||||
if (!Hash::check($validated['current_password'], $admin->password)) {
|
||||
return $this->error(
|
||||
ErrorCode::INVALID_CREDENTIALS,
|
||||
|
|
|
|||
|
|
@ -8,28 +8,31 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Admin;
|
||||
use App\Models\Client;
|
||||
use App\Models\LlmProvider;
|
||||
use App\Services\Auth\TokenService;
|
||||
use App\Services\LogService;
|
||||
use App\Traits\ApiResponse;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
|
||||
private Admin $admin;
|
||||
/**
|
||||
* @var TokenService
|
||||
*/
|
||||
private readonly TokenService $tokenService;
|
||||
|
||||
public function __construct(
|
||||
private readonly TokenService $tokenService,
|
||||
private readonly LogService $logService,
|
||||
Request $request
|
||||
) {
|
||||
$this->admin = $request->admin;
|
||||
$admin = $request->admin;
|
||||
if (!$admin) {
|
||||
throw new \RuntimeException('管理員信息未找到。');
|
||||
}
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -38,12 +41,21 @@ public function __construct(
|
|||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$clients = Client::select([
|
||||
$query = Client::select([
|
||||
'id',
|
||||
'name',
|
||||
'llm_provider_id',
|
||||
'created_at',
|
||||
])->get();
|
||||
]);
|
||||
|
||||
// 如果不是超級管理員,只能看到自己管理的客戶
|
||||
if (!$this->admin->isSuperAdmin()) {
|
||||
$query->whereHas('admins', function ($query) {
|
||||
$query->where('admin_id', $this->admin->id);
|
||||
});
|
||||
}
|
||||
|
||||
$clients = $query->get();
|
||||
|
||||
return $this->success([
|
||||
'items' => $clients->map(fn($client) => [
|
||||
|
|
@ -87,12 +99,25 @@ public function store(Request $request): JsonResponse
|
|||
],
|
||||
]);
|
||||
|
||||
// 檢查是否有權限管理該提供商
|
||||
if (!$this->admin->canManageLlmProvider($validated['llm_provider_id'])) {
|
||||
return $this->error(
|
||||
ErrorCode::FORBIDDEN,
|
||||
'您無權使用該 LLM 提供商。'
|
||||
);
|
||||
}
|
||||
|
||||
$validated['status'] = Client::STATUS_ACTIVE;
|
||||
$validated['rate_limit'] = config('llm.default_rate_limit', 60);
|
||||
$validated['timeout'] = config('llm.default_timeout', 30);
|
||||
|
||||
$client = Client::create($validated);
|
||||
|
||||
// 如果不是超級管理員,需要建立關聯
|
||||
if (!$this->admin->isSuperAdmin()) {
|
||||
$client->admins()->attach($this->admin->id);
|
||||
}
|
||||
|
||||
$this->logService->logOperation(
|
||||
'admin',
|
||||
$this->admin->id,
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Admin;
|
||||
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;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -18,23 +15,29 @@
|
|||
|
||||
class LlmProviderController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
/**
|
||||
* @var Admin
|
||||
*/
|
||||
private readonly Admin $admin;
|
||||
|
||||
private Admin $admin;
|
||||
|
||||
public function __construct(
|
||||
private readonly LogService $logService,
|
||||
Request $request
|
||||
) {
|
||||
$this->admin = $request->admin;
|
||||
public function __construct(Request $request)
|
||||
{
|
||||
$admin = $request->admin;
|
||||
if (!$admin instanceof Admin) {
|
||||
throw new \RuntimeException('管理員信息未找到。');
|
||||
}
|
||||
$this->admin = $admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取 LLM 提供商列表
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
/** @var \Illuminate\Database\Eloquent\Builder $providers */
|
||||
$providers = LlmProvider::select([
|
||||
'id',
|
||||
'name',
|
||||
|
|
@ -42,9 +45,30 @@ public function index(): JsonResponse
|
|||
'api_url',
|
||||
'status',
|
||||
'created_at',
|
||||
])->get();
|
||||
]);
|
||||
|
||||
return $this->success($providers);
|
||||
// 如果不是超級管理員,只能看到自己管理的提供商
|
||||
if (!$this->admin->isSuperAdmin()) {
|
||||
$providers->whereHas('clients', function ($query) {
|
||||
$query->whereHas('admins', function ($query) {
|
||||
$query->where('admin_id', $this->admin->id);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** @var \Illuminate\Database\Eloquent\Collection $providerList */
|
||||
$providerList = $providers->get();
|
||||
|
||||
return response()->json([
|
||||
'items' => $providerList->map(fn($provider) => [
|
||||
'id' => $provider->id,
|
||||
'name' => $provider->name,
|
||||
'service_name' => $provider->service_name,
|
||||
'api_url' => $provider->api_url,
|
||||
'status' => $provider->status,
|
||||
'created_at' => $provider->created_at->toIso8601String(),
|
||||
])
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching LLM providers', [
|
||||
|
|
@ -52,19 +76,37 @@ public function index(): JsonResponse
|
|||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return $this->error(
|
||||
ErrorCode::SERVER_ERROR,
|
||||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::SERVER_ERROR,
|
||||
'message' => ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增 LLM 提供商
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function store(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
// 只有超級管理員可以新增提供商
|
||||
if (!$this->admin->isSuperAdmin()) {
|
||||
return response()->json([
|
||||
'error' => ErrorCode::FORBIDDEN,
|
||||
'message' => '只有超級管理員可以新增 LLM 提供商。'
|
||||
]);
|
||||
}
|
||||
|
||||
/** @var array{
|
||||
* name: string,
|
||||
* service_name: string,
|
||||
* api_url: string,
|
||||
* api_token: string
|
||||
* } $validated */
|
||||
$validated = $request->validate([
|
||||
'name' => [
|
||||
'required',
|
||||
|
|
@ -91,15 +133,11 @@ public function store(Request $request): JsonResponse
|
|||
]);
|
||||
|
||||
$validated['status'] = LlmProvider::STATUS_ACTIVE;
|
||||
|
||||
/** @var LlmProvider $provider */
|
||||
$provider = LlmProvider::create($validated);
|
||||
|
||||
$this->logService->logOperation(
|
||||
'admin',
|
||||
$this->admin->id,
|
||||
"Created LLM provider: {$provider->name}"
|
||||
);
|
||||
|
||||
return $this->created([
|
||||
return response()->json([
|
||||
'id' => $provider->id,
|
||||
'name' => $provider->name,
|
||||
'service_name' => $provider->service_name,
|
||||
|
|
@ -109,11 +147,11 @@ public function store(Request $request): JsonResponse
|
|||
]);
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error(
|
||||
ErrorCode::VALIDATION_ERROR,
|
||||
ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR),
|
||||
$e->errors()
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::VALIDATION_ERROR,
|
||||
'message' => ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR),
|
||||
'errors' => $e->errors()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating LLM provider', [
|
||||
'error' => $e->getMessage(),
|
||||
|
|
@ -121,10 +159,10 @@ public function store(Request $request): JsonResponse
|
|||
'request_data' => $request->except(['api_token']),
|
||||
]);
|
||||
|
||||
return $this->error(
|
||||
ErrorCode::SERVER_ERROR,
|
||||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::SERVER_ERROR,
|
||||
'message' => ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,10 +175,10 @@ public function update(Request $request, int $id): JsonResponse
|
|||
$provider = LlmProvider::findOrFail($id);
|
||||
|
||||
if (!$this->admin->canManageLlmProvider($provider->id)) {
|
||||
return $this->error(
|
||||
ErrorCode::FORBIDDEN,
|
||||
'您無權管理該 LLM 提供商。'
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::FORBIDDEN,
|
||||
'message' => '您無權管理該 LLM 提供商。'
|
||||
]);
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
|
|
@ -177,22 +215,16 @@ public function update(Request $request, int $id): JsonResponse
|
|||
if ($validated['status'] === LlmProvider::STATUS_INACTIVE &&
|
||||
$provider->status === LlmProvider::STATUS_ACTIVE) {
|
||||
if ($provider->clients()->exists()) {
|
||||
return $this->error(
|
||||
ErrorCode::RESOURCE_IN_USE,
|
||||
'該提供商正在被客戶使用,無法停用。'
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::RESOURCE_IN_USE,
|
||||
'message' => '該提供商正在被客戶使用,無法停用。'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$provider->update($validated);
|
||||
|
||||
$this->logService->logOperation(
|
||||
'admin',
|
||||
$this->admin->id,
|
||||
"Updated LLM provider: {$provider->name}"
|
||||
);
|
||||
|
||||
return $this->success([
|
||||
return response()->json([
|
||||
'id' => $provider->id,
|
||||
'name' => $provider->name,
|
||||
'service_name' => $provider->service_name,
|
||||
|
|
@ -202,16 +234,11 @@ public function update(Request $request, int $id): JsonResponse
|
|||
]);
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error(
|
||||
ErrorCode::VALIDATION_ERROR,
|
||||
ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR),
|
||||
$e->errors()
|
||||
);
|
||||
} catch (ModelNotFoundException $e) {
|
||||
return $this->error(
|
||||
ErrorCode::RESOURCE_NOT_FOUND,
|
||||
ErrorCode::getMessage(ErrorCode::RESOURCE_NOT_FOUND)
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::VALIDATION_ERROR,
|
||||
'message' => ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR),
|
||||
'errors' => $e->errors()
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating LLM provider', [
|
||||
'error' => $e->getMessage(),
|
||||
|
|
@ -220,10 +247,10 @@ public function update(Request $request, int $id): JsonResponse
|
|||
'request_data' => $request->except(['api_token']),
|
||||
]);
|
||||
|
||||
return $this->error(
|
||||
ErrorCode::SERVER_ERROR,
|
||||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::SERVER_ERROR,
|
||||
'message' => ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -236,36 +263,27 @@ public function destroy(int $id): JsonResponse
|
|||
$provider = LlmProvider::findOrFail($id);
|
||||
|
||||
if (!$this->admin->canManageLlmProvider($provider->id)) {
|
||||
return $this->error(
|
||||
ErrorCode::FORBIDDEN,
|
||||
'您無權管理該 LLM 提供商。'
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::FORBIDDEN,
|
||||
'message' => '您無權管理該 LLM 提供商。'
|
||||
]);
|
||||
}
|
||||
|
||||
// 檢查是否有客戶在使用
|
||||
if ($provider->clients()->exists()) {
|
||||
return $this->error(
|
||||
ErrorCode::RESOURCE_IN_USE,
|
||||
'該提供商正在被客戶使用,無法刪除。'
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::RESOURCE_IN_USE,
|
||||
'message' => '該提供商正在被客戶使用,無法刪除。'
|
||||
]);
|
||||
}
|
||||
|
||||
$providerName = $provider->name;
|
||||
$provider->delete();
|
||||
|
||||
$this->logService->logOperation(
|
||||
'admin',
|
||||
$this->admin->id,
|
||||
"Deleted LLM provider: {$providerName}"
|
||||
);
|
||||
return response()->json([
|
||||
'message' => 'LLM 提供商已刪除。'
|
||||
]);
|
||||
|
||||
return $this->success(null, 'LLM 提供商已刪除。');
|
||||
|
||||
} catch (ModelNotFoundException $e) {
|
||||
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(),
|
||||
|
|
@ -273,10 +291,10 @@ public function destroy(int $id): JsonResponse
|
|||
'provider_id' => $id,
|
||||
]);
|
||||
|
||||
return $this->error(
|
||||
ErrorCode::SERVER_ERROR,
|
||||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
);
|
||||
return response()->json([
|
||||
'error' => ErrorCode::SERVER_ERROR,
|
||||
'message' => ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
use App\Constants\ErrorCode;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\Auth\TokenService;
|
||||
use App\Traits\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -15,25 +14,35 @@
|
|||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
/**
|
||||
* @var TokenService
|
||||
*/
|
||||
private readonly TokenService $tokenService;
|
||||
|
||||
public function __construct(
|
||||
private readonly TokenService $tokenService,
|
||||
) {}
|
||||
public function __construct(TokenService $tokenService)
|
||||
{
|
||||
$this->tokenService = $tokenService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取訪問令牌
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function getAccessToken(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
/** @var array{auth_token: string} $validated */
|
||||
$validated = $request->validate([
|
||||
'auth_token' => 'required|string|size:64',
|
||||
]);
|
||||
|
||||
/** @var array{client_id: int, expires_at: string}|null $authTokenData */
|
||||
$authTokenData = $this->tokenService->validateAuthToken($validated['auth_token']);
|
||||
|
||||
if (!$authTokenData) {
|
||||
if (!$authTokenData || !isset($authTokenData['expires_at'])) {
|
||||
return $this->error(
|
||||
ErrorCode::TOKEN_INVALID,
|
||||
'認證令牌無效。'
|
||||
|
|
@ -47,9 +56,17 @@ public function getAccessToken(Request $request): JsonResponse
|
|||
);
|
||||
}
|
||||
|
||||
/** @var array{access_token: string, expires_in: int} $result */
|
||||
$result = $this->tokenService->generateAccessToken($authTokenData);
|
||||
|
||||
return $this->success($result);
|
||||
if (!isset($result['access_token']) || !isset($result['expires_in'])) {
|
||||
throw new \RuntimeException('生成訪問令牌失敗。');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'access_token' => $result['access_token'],
|
||||
'expires_in' => $result['expires_in'],
|
||||
]);
|
||||
|
||||
} catch (ValidationException $e) {
|
||||
return $this->error(
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
use App\Constants\ErrorCode;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\LlmService;
|
||||
use App\Traits\ApiResponse;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
|
@ -15,18 +14,38 @@
|
|||
|
||||
class LlmController extends Controller
|
||||
{
|
||||
use ApiResponse;
|
||||
/**
|
||||
* @var LlmService
|
||||
*/
|
||||
private readonly LlmService $llmService;
|
||||
|
||||
public function __construct(
|
||||
private readonly LlmService $llmService,
|
||||
) {}
|
||||
public function __construct(LlmService $llmService)
|
||||
{
|
||||
$this->llmService = $llmService;
|
||||
}
|
||||
|
||||
/**
|
||||
* 發送 LLM 請求
|
||||
*
|
||||
* @param Request $request
|
||||
* @return JsonResponse
|
||||
* @throws ValidationException|\RuntimeException
|
||||
*/
|
||||
public function request(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
if (!$request->client) {
|
||||
throw new \RuntimeException('客戶信息未找到。');
|
||||
}
|
||||
|
||||
/** @var array{
|
||||
* prompt: string,
|
||||
* max_tokens?: int,
|
||||
* temperature?: float,
|
||||
* top_p?: float,
|
||||
* frequency_penalty?: float,
|
||||
* presence_penalty?: float
|
||||
* } $validated */
|
||||
$validated = $request->validate([
|
||||
'prompt' => 'required|string|max:4000',
|
||||
'max_tokens' => 'nullable|integer|min:1|max:4000',
|
||||
|
|
@ -36,12 +55,17 @@ public function request(Request $request): JsonResponse
|
|||
'presence_penalty' => 'nullable|numeric|min:-2|max:2',
|
||||
]);
|
||||
|
||||
/** @var array{response: string} $result */
|
||||
$result = $this->llmService->sendRequest(
|
||||
$request->client,
|
||||
$validated['prompt'],
|
||||
array_filter($validated, fn($key) => $key !== 'prompt', ARRAY_FILTER_USE_KEY)
|
||||
);
|
||||
|
||||
if (!isset($result['response'])) {
|
||||
throw new \RuntimeException('LLM 提供商返回的響應格式無效。');
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'response' => $result['response'],
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,103 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param mixed $data 响应数据
|
||||
* @param string|null $message 成功消息
|
||||
* @param int $code HTTP状态码
|
||||
*/
|
||||
protected function success(mixed $data = null, ?string $message = null, int $code = Response::HTTP_OK): JsonResponse
|
||||
{
|
||||
$response = [
|
||||
'success' => true,
|
||||
];
|
||||
|
||||
if ($data !== null) {
|
||||
$response['data'] = $data;
|
||||
}
|
||||
|
||||
if ($message !== null) {
|
||||
$response['message'] = $message;
|
||||
}
|
||||
|
||||
return response()->json($response, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
*
|
||||
* @param string $error 错误代码
|
||||
* @param string $message 错误消息
|
||||
* @param mixed $errors 详细错误信息
|
||||
* @param int $code HTTP状态码
|
||||
*/
|
||||
protected function error(
|
||||
string $error,
|
||||
string $message,
|
||||
mixed $errors = null,
|
||||
int $code = Response::HTTP_BAD_REQUEST
|
||||
): JsonResponse {
|
||||
$response = [
|
||||
'success' => false,
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
];
|
||||
|
||||
if ($errors !== null) {
|
||||
$response['errors'] = $errors;
|
||||
}
|
||||
|
||||
return response()->json($response, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页响应
|
||||
*
|
||||
* @param array $items 分页数据
|
||||
* @param array $meta 分页元数据
|
||||
* @param string|null $message 成功消息
|
||||
*/
|
||||
protected function paginate(array $items, array $meta, ?string $message = null): JsonResponse
|
||||
{
|
||||
$response = [
|
||||
'success' => true,
|
||||
'data' => $items,
|
||||
'meta' => $meta,
|
||||
];
|
||||
|
||||
if ($message !== null) {
|
||||
$response['message'] = $message;
|
||||
}
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*
|
||||
* @param mixed $data 创建的资源数据
|
||||
* @param string|null $message 成功消息
|
||||
*/
|
||||
protected function created(mixed $data, ?string $message = null): JsonResponse
|
||||
{
|
||||
return $this->success($data, $message, Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无内容响应
|
||||
*/
|
||||
protected function noContent(): JsonResponse
|
||||
{
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,10 +40,13 @@ class Admin extends Authenticatable
|
|||
'role' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return BelongsToMany<Client>
|
||||
*/
|
||||
public function clients(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Client::class, 'admin_client')
|
||||
->withTimestamp('assigned_at');
|
||||
->withTimestamps();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -72,6 +75,8 @@ public function isValidAdmin(): bool
|
|||
|
||||
/**
|
||||
* 检查是否可以管理指定的客户
|
||||
*
|
||||
* @param int $clientId
|
||||
*/
|
||||
public function canManageClient(int $clientId): bool
|
||||
{
|
||||
|
|
@ -84,6 +89,8 @@ public function canManageClient(int $clientId): bool
|
|||
|
||||
/**
|
||||
* 检查是否可以管理指定的LLM提供商
|
||||
*
|
||||
* @param int $providerId
|
||||
*/
|
||||
public function canManageLlmProvider(int $providerId): bool
|
||||
{
|
||||
|
|
|
|||
|
|
@ -40,11 +40,17 @@ class Client extends Model
|
|||
'status' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return BelongsTo<LlmProvider>
|
||||
*/
|
||||
public function llmProvider(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(LlmProvider::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsToMany<Admin>
|
||||
*/
|
||||
public function admins(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Admin::class, 'admin_client')
|
||||
|
|
@ -64,6 +70,8 @@ public function isActive(): bool
|
|||
*/
|
||||
public function canSendLlmRequest(): bool
|
||||
{
|
||||
return $this->isActive() && $this->llmProvider->isActive();
|
||||
/** @var LlmProvider|null $provider */
|
||||
$provider = $this->llmProvider;
|
||||
return $this->isActive() && $provider && $provider->isActive();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,9 @@ class LlmProvider extends Model
|
|||
'status' => 'string',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return HasMany<Client>
|
||||
*/
|
||||
public function clients(): HasMany
|
||||
{
|
||||
return $this->hasMany(Client::class);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
|
|
|||
|
|
@ -13,16 +13,20 @@ class ThrottleAuthToken implements Rule
|
|||
private const MAX_ATTEMPTS = 5; // 最大尝试次数
|
||||
private const DECAY_MINUTES = 1; // 重置时间(分钟)
|
||||
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
public function passes($attribute, $value): bool
|
||||
{
|
||||
$key = 'auth_token_' . $value;
|
||||
|
||||
if (RateLimiter::tooManyAttempts($key, self::MAX_ATTEMPTS)) {
|
||||
$seconds = RateLimiter::availableIn($key);
|
||||
$fail("请求过于频繁,请在 {$seconds} 秒后重试。");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
RateLimiter::hit($key, self::DECAY_MINUTES * 60);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function message(): string
|
||||
{
|
||||
return '请求过于频繁,请稍后重试。';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,21 +65,26 @@ public function generateAuthToken(Client $client, ?int $expiresInHours = null):
|
|||
/**
|
||||
* 驗證認證令牌
|
||||
*
|
||||
* @param string $token
|
||||
* @return array{client_id: int, token: string, created_at: string, expires_at: string}|null
|
||||
*/
|
||||
public function validateAuthToken(string $token): ?array
|
||||
{
|
||||
// 檢查黑名單
|
||||
if (Redis::exists(self::BLACKLIST_PREFIX . $token)) {
|
||||
/** @var bool $isBlacklisted */
|
||||
$isBlacklisted = Redis::exists(self::BLACKLIST_PREFIX . $token);
|
||||
if ($isBlacklisted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 檢查令牌是否存在且有效
|
||||
/** @var string|null $tokenData */
|
||||
$tokenData = Redis::get(self::AUTH_TOKEN_PREFIX . $token);
|
||||
if (!$tokenData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var array{client_id: int, token: string, created_at: string, expires_at: string}|null $data */
|
||||
$data = json_decode($tokenData, true);
|
||||
if (!$data || now()->isAfter($data['expires_at'])) {
|
||||
Redis::del(self::AUTH_TOKEN_PREFIX . $token);
|
||||
|
|
@ -125,16 +130,20 @@ public function generateAccessToken(array $authTokenData): array
|
|||
/**
|
||||
* 驗證訪問令牌
|
||||
*
|
||||
* @param string $token
|
||||
* @return array{client_id: int, auth_token: string, created_at: string}|null
|
||||
*/
|
||||
public function validateAccessToken(string $token): ?array
|
||||
{
|
||||
/** @var string|null $tokenData */
|
||||
$tokenData = Redis::get(self::ACCESS_TOKEN_PREFIX . $token);
|
||||
if (!$tokenData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_decode($tokenData, true);
|
||||
/** @var array{client_id: int, auth_token: string, created_at: string}|null $data */
|
||||
$data = json_decode($tokenData, true);
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ public function sendRequest(Client $client, string $prompt, array $options = [])
|
|||
}
|
||||
|
||||
try {
|
||||
/** @var \App\Models\LlmProvider $provider */
|
||||
$provider = $client->llmProvider;
|
||||
|
||||
if ($provider->status !== 'active') {
|
||||
|
|
|
|||
|
|
@ -1,109 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
trait ApiResponse
|
||||
{
|
||||
/**
|
||||
* 成功响应
|
||||
*
|
||||
* @param mixed $data 响应数据
|
||||
* @param string|null $message 成功消息
|
||||
* @param int $code HTTP状态码
|
||||
* @return JsonResponse
|
||||
*/
|
||||
protected function success(mixed $data = null, ?string $message = null, int $code = Response::HTTP_OK): JsonResponse
|
||||
{
|
||||
$response = [
|
||||
'success' => true,
|
||||
];
|
||||
|
||||
if ($data !== null) {
|
||||
$response['data'] = $data;
|
||||
}
|
||||
|
||||
if ($message !== null) {
|
||||
$response['message'] = $message;
|
||||
}
|
||||
|
||||
return response()->json($response, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误响应
|
||||
*
|
||||
* @param string $error 错误代码
|
||||
* @param string $message 错误消息
|
||||
* @param mixed $errors 详细错误信息
|
||||
* @param int $code HTTP状态码
|
||||
* @return JsonResponse
|
||||
*/
|
||||
protected function error(
|
||||
string $error,
|
||||
string $message,
|
||||
mixed $errors = null,
|
||||
int $code = Response::HTTP_BAD_REQUEST
|
||||
): JsonResponse {
|
||||
$response = [
|
||||
'success' => false,
|
||||
'error' => $error,
|
||||
'message' => $message,
|
||||
];
|
||||
|
||||
if ($errors !== null) {
|
||||
$response['errors'] = $errors;
|
||||
}
|
||||
|
||||
return response()->json($response, $code);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页响应
|
||||
*
|
||||
* @param array $items 分页数据
|
||||
* @param array $meta 分页元数据
|
||||
* @param string|null $message 成功消息
|
||||
* @return JsonResponse
|
||||
*/
|
||||
protected function paginate(array $items, array $meta, ?string $message = null): JsonResponse
|
||||
{
|
||||
$response = [
|
||||
'success' => true,
|
||||
'data' => $items,
|
||||
'meta' => $meta,
|
||||
];
|
||||
|
||||
if ($message !== null) {
|
||||
$response['message'] = $message;
|
||||
}
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建成功响应
|
||||
*
|
||||
* @param mixed $data 创建的资源数据
|
||||
* @param string|null $message 成功消息
|
||||
* @return JsonResponse
|
||||
*/
|
||||
protected function created(mixed $data, ?string $message = null): JsonResponse
|
||||
{
|
||||
return $this->success($data, $message, Response::HTTP_CREATED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 无内容响应
|
||||
*
|
||||
* @return JsonResponse
|
||||
*/
|
||||
protected function noContent(): JsonResponse
|
||||
{
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue