325 lines
9.4 KiB
PHP
325 lines
9.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace App\Http\Controllers\Api\Admin;
|
||
|
||
use App\Constants\ErrorCode;
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Admin;
|
||
use App\Models\Client;
|
||
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\Log;
|
||
use Illuminate\Support\Facades\Redis;
|
||
use Illuminate\Validation\ValidationException;
|
||
|
||
class ClientController extends Controller
|
||
{
|
||
use ApiResponse;
|
||
|
||
private Admin $admin;
|
||
|
||
public function __construct(
|
||
private readonly TokenService $tokenService,
|
||
private readonly LogService $logService,
|
||
Request $request
|
||
) {
|
||
$this->admin = $request->admin;
|
||
}
|
||
|
||
/**
|
||
* 獲取客戶列表
|
||
*/
|
||
public function index(): JsonResponse
|
||
{
|
||
try {
|
||
$clients = Client::select([
|
||
'id',
|
||
'name',
|
||
'llm_provider_id',
|
||
'created_at',
|
||
])->get();
|
||
|
||
return $this->success($clients);
|
||
|
||
} catch (\Exception $e) {
|
||
Log::error('Error fetching clients', [
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString(),
|
||
]);
|
||
|
||
return $this->error(
|
||
ErrorCode::SERVER_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 新增客戶
|
||
*/
|
||
public function store(Request $request): JsonResponse
|
||
{
|
||
try {
|
||
$validated = $request->validate([
|
||
'name' => [
|
||
'required',
|
||
'string',
|
||
'max:100',
|
||
'unique:clients',
|
||
],
|
||
'llm_provider_id' => [
|
||
'required',
|
||
'integer',
|
||
'exists:llm_providers,id',
|
||
],
|
||
]);
|
||
|
||
$validated['status'] = Client::STATUS_ACTIVE;
|
||
$validated['rate_limit'] = config('llm.default_rate_limit', 60); // 默認每分鐘 60 次
|
||
$validated['timeout'] = config('llm.default_timeout', 30); // 默認 30 秒
|
||
|
||
$client = Client::create($validated);
|
||
|
||
// 加載 LLM 提供商信息
|
||
$client->load('llmProvider:id,name,service_name');
|
||
|
||
$this->logService->logOperation(
|
||
'admin',
|
||
$this->admin->id,
|
||
"Created client: {$client->name}"
|
||
);
|
||
|
||
return $this->created([
|
||
'id' => $client->id,
|
||
'name' => $client->name,
|
||
'llm_provider_id' => $client->llm_provider_id,
|
||
'created_at' => $client->created_at->toIso8601String(),
|
||
]);
|
||
|
||
} catch (ValidationException $e) {
|
||
return $this->error(
|
||
ErrorCode::VALIDATION_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR),
|
||
$e->errors()
|
||
);
|
||
} catch (\Exception $e) {
|
||
Log::error('Error creating client', [
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString(),
|
||
'request_data' => $request->all(),
|
||
]);
|
||
|
||
return $this->error(
|
||
ErrorCode::SERVER_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 修改客戶
|
||
*/
|
||
public function update(Request $request, int $id): JsonResponse
|
||
{
|
||
try {
|
||
$client = Client::findOrFail($id);
|
||
|
||
if (!$this->admin->canManageClient($client->id)) {
|
||
return $this->error(
|
||
ErrorCode::FORBIDDEN,
|
||
'您無權管理該客戶。'
|
||
);
|
||
}
|
||
|
||
$validated = $request->validate([
|
||
'name' => [
|
||
'required',
|
||
'string',
|
||
'max:100',
|
||
"unique:clients,name,{$id}",
|
||
],
|
||
'llm_provider_id' => [
|
||
'required',
|
||
'integer',
|
||
'exists:llm_providers,id',
|
||
],
|
||
]);
|
||
|
||
$client->update($validated);
|
||
|
||
// 加載 LLM 提供商信息
|
||
$client->load('llmProvider:id,name,service_name');
|
||
|
||
$this->logService->logOperation(
|
||
'admin',
|
||
$this->admin->id,
|
||
"Updated client: {$client->name}"
|
||
);
|
||
|
||
return $this->success([
|
||
'id' => $client->id,
|
||
'name' => $client->name,
|
||
'llm_provider_id' => $client->llm_provider_id,
|
||
'updated_at' => $client->updated_at->toIso8601String(),
|
||
]);
|
||
|
||
} catch (ValidationException $e) {
|
||
return $this->error(
|
||
ErrorCode::VALIDATION_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::VALIDATION_ERROR),
|
||
$e->errors()
|
||
);
|
||
} catch (ModelNotFoundException $e) {
|
||
return $this->error(
|
||
ErrorCode::CLIENT_NOT_FOUND,
|
||
ErrorCode::getMessage(ErrorCode::CLIENT_NOT_FOUND)
|
||
);
|
||
} catch (\Exception $e) {
|
||
Log::error('Error updating client', [
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString(),
|
||
'client_id' => $id,
|
||
'request_data' => $request->all(),
|
||
]);
|
||
|
||
return $this->error(
|
||
ErrorCode::SERVER_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 刪除客戶
|
||
*/
|
||
public function destroy(int $id): JsonResponse
|
||
{
|
||
try {
|
||
$client = Client::findOrFail($id);
|
||
|
||
if (!$this->admin->canManageClient($client->id)) {
|
||
return $this->error(
|
||
ErrorCode::FORBIDDEN,
|
||
'您無權管理該客戶。'
|
||
);
|
||
}
|
||
|
||
if ($this->hasActiveTokens($client)) {
|
||
return $this->error(
|
||
ErrorCode::CLIENT_HAS_ACTIVE_TOKENS,
|
||
'該客戶有活躍的認證令牌,無法刪除。'
|
||
);
|
||
}
|
||
|
||
$client->delete();
|
||
|
||
$this->logService->logOperation(
|
||
'admin',
|
||
$this->admin->id,
|
||
"Deleted client: {$client->name}"
|
||
);
|
||
|
||
return $this->success(null, '客戶已刪除。');
|
||
|
||
} catch (ModelNotFoundException $e) {
|
||
return $this->error(
|
||
ErrorCode::CLIENT_NOT_FOUND,
|
||
ErrorCode::getMessage(ErrorCode::CLIENT_NOT_FOUND)
|
||
);
|
||
} catch (\Exception $e) {
|
||
Log::error('Error deleting client', [
|
||
'error' => $e->getMessage(),
|
||
'trace' => $e->getTraceAsString(),
|
||
'client_id' => $id,
|
||
]);
|
||
|
||
return $this->error(
|
||
ErrorCode::SERVER_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 為客戶生成認證令牌
|
||
*/
|
||
public function generateAuthToken(int $id): JsonResponse
|
||
{
|
||
try {
|
||
$client = Client::findOrFail($id);
|
||
|
||
if (!$this->admin->canManageClient($client->id)) {
|
||
return $this->error(
|
||
ErrorCode::FORBIDDEN,
|
||
'您無<E682A8><E784A1>管理該客戶。'
|
||
);
|
||
}
|
||
|
||
if (!$client->isActive()) {
|
||
return $this->error(
|
||
ErrorCode::CLIENT_INACTIVE,
|
||
'客戶未啟用,無法生成令牌。'
|
||
);
|
||
}
|
||
|
||
$result = $this->tokenService->generateAuthToken($client);
|
||
|
||
$this->logService->logOperation(
|
||
'admin',
|
||
$this->admin->id,
|
||
"Generated auth token for client: {$client->name}"
|
||
);
|
||
|
||
return $this->success([
|
||
'auth_token' => $result['token'],
|
||
'expires_at' => $result['expires_at'],
|
||
]);
|
||
|
||
} catch (ModelNotFoundException $e) {
|
||
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(),
|
||
'trace' => $e->getTraceAsString(),
|
||
'client_id' => $id,
|
||
]);
|
||
|
||
return $this->error(
|
||
ErrorCode::SERVER_ERROR,
|
||
ErrorCode::getMessage(ErrorCode::SERVER_ERROR)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 檢查客戶是否有活躍的令牌
|
||
*/
|
||
private function hasActiveTokens(Client $client): bool
|
||
{
|
||
$pattern = 'auth_token:*';
|
||
$keys = Redis::keys($pattern);
|
||
|
||
foreach ($keys as $key) {
|
||
$data = Redis::get($key);
|
||
if ($data) {
|
||
$tokenData = json_decode($data, true);
|
||
if ($tokenData &&
|
||
$tokenData['client_id'] === $client->id &&
|
||
now()->isBefore($tokenData['expires_at'])) {
|
||
return true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|