- [4.3.2 客户用户管理](#432-客户用户管理)

- [4.3.2.1 新增客户用户](#4321-新增客户用户)
       - [4.3.2.2 修改客户用户](#4322-修改客户用户)
       - [4.3.2.3 删除客户用户](#4323-删除客户用户)
       - [4.3.2.4 获取客户用户列表](#4324-获取客户用户列表)
     - [4.3.3 生成认证令牌](#433-生成认证令牌)
This commit is contained in:
Jethro Lin 2024-12-04 12:10:15 +08:00
parent c44c25d86f
commit 31b69e318a
2 changed files with 372 additions and 60 deletions

View file

@ -6,11 +6,11 @@
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Client; use App\Models\Client;
use App\Services\Auth\TokenService;
use App\Services\LogService; use App\Services\LogService;
use App\Services\Auth\TokenService;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException; use Illuminate\Validation\ValidationException;
@ -18,54 +18,90 @@ class ClientController extends Controller
{ {
public function __construct( public function __construct(
private readonly LogService $logService, private readonly LogService $logService,
private readonly TokenService $tokenService 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 public function index(Request $request): JsonResponse
{ {
$admin = $request->admin; try {
$query = $admin->isSuperAdmin() ? Client::query() : $admin->clients(); $perPage = $request->input('per_page', 15);
$perPage = min(max($perPage, 1), 100); // 限制每页数量在 1-100 之间
$search = $request->input('search');
$status = $request->input('status');
$clients = $query->with('llmProvider')->get(); $query = Client::with('llmProvider:id,name,service_name')
return response()->json($clients); ->orderBy('created_at', 'desc');
// 搜索条件
if ($search) {
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
} }
public function store(Request $request): JsonResponse // 状态筛选
{ if ($status && in_array($status, ['active', 'inactive'])) {
try { $query->where('status', $status);
$validated = $request->validate([ }
'name' => 'required|string|max:100',
'llm_provider_id' => 'required|exists:llm_providers,id', $clients = $query->paginate($perPage);
return response()->json([
'data' => $clients->items(),
'meta' => [
'current_page' => $clients->currentPage(),
'per_page' => $clients->perPage(),
'total' => $clients->total(),
'total_pages' => $clients->lastPage(),
],
]); ]);
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) { } catch (\Exception $e) {
DB::rollBack(); Log::error('Error fetching clients', [
Log::error('Error creating client', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(), 'trace' => $e->getTraceAsString(),
'search' => $search,
'status' => $status,
]); ]);
return response()->json([ return response()->json([
@ -75,18 +111,212 @@ public function store(Request $request): JsonResponse
} }
} }
/**
* 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
{
try {
$validated = $request->validate([
'name' => [
'required',
'string',
'max:100',
'regex:/^[\w\-\s]+$/u',
],
'email' => [
'required',
'string',
'email',
'max:255',
'unique:clients',
],
'llm_provider_id' => [
'required',
'integer',
'exists:llm_providers,id',
],
'rate_limit' => [
'required',
'integer',
'min:1',
'max:1000',
],
'timeout' => [
'required',
'integer',
'min:1',
'max:300',
],
], [
'name.regex' => '客户名称只能包含字母、数字、下划线、横线和空格。',
'email.unique' => '该邮箱地址已被使用。',
'llm_provider_id.exists' => '选择的 LLM 提供商不存在。',
'rate_limit.min' => '速率限制不能小于 1。',
'rate_limit.max' => '速率限制不能大于 1000。',
'timeout.min' => '超时时间不能小于 1 秒。',
'timeout.max' => '超时时间不能大于 300 秒。',
]);
$validated['status'] = 'active';
$client = Client::create($validated);
// 加载 LLM 提供商信息
$client->load('llmProvider:id,name,service_name');
$this->logService->logOperation(
'admin',
$request->admin->id,
"Created client: {$client->name}"
);
return response()->json($client, 201);
} catch (ValidationException $e) {
return response()->json([
'error' => 'validation_error',
'message' => '请求参数验证失败。',
'errors' => $e->errors(),
], 422);
} catch (\Exception $e) {
Log::error('Error creating client', [
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(),
'request_data' => $request->except(['api_token']),
]);
return response()->json([
'error' => 'server_error',
'message' => '服务器内部错误。',
], 500);
}
}
/**
* 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 public function update(Request $request, int $id): JsonResponse
{ {
try { try {
$client = $this->getAuthorizedClient($request->admin, $id); $client = Client::findOrFail($id);
$validated = $request->validate([ $validated = $request->validate([
'name' => 'required|string|max:100', 'name' => [
'llm_provider_id' => 'required|exists:llm_providers,id', 'required',
'string',
'max:100',
'regex:/^[\w\-\s]+$/u',
],
'email' => [
'required',
'string',
'email',
'max:255',
"unique:clients,email,{$id}",
],
'llm_provider_id' => [
'required',
'integer',
'exists:llm_providers,id',
],
'rate_limit' => [
'required',
'integer',
'min:1',
'max:1000',
],
'timeout' => [
'required',
'integer',
'min:1',
'max:300',
],
'status' => [
'required',
'string',
'in:active,inactive',
],
], [
'name.regex' => '客户名称只能包含字母、数字、下划线、横线和空格。',
'email.unique' => '该邮箱地址已被使用。',
'llm_provider_id.exists' => '选择的 LLM 提供商不存在。',
'rate_limit.min' => '速率限制不能小于 1。',
'rate_limit.max' => '速率限制不能大于 1000。',
'timeout.min' => '超时时间不能小于 1 秒。',
'timeout.max' => '超时时间不能大于 300 秒。',
'status.in' => '状态只能是 active 或 inactive。',
]); ]);
// 如果状态改为 inactive检查是否有活跃的认证令牌
if ($validated['status'] === 'inactive' && $client->status === 'active') {
if ($client->authTokens()->where('status', 'active')->exists()) {
return response()->json([
'error' => 'active_tokens_exist',
'message' => '该客户有活跃的认证令牌,请先停用所有令牌。',
], 422);
}
}
$client->update($validated); $client->update($validated);
// 加载 LLM 提供商信息
$client->load('llmProvider:id,name,service_name');
$this->logService->logOperation( $this->logService->logOperation(
'admin', 'admin',
$request->admin->id, $request->admin->id,
@ -101,10 +331,17 @@ public function update(Request $request, int $id): JsonResponse
'message' => '请求参数验证失败。', 'message' => '请求参数验证失败。',
'errors' => $e->errors(), 'errors' => $e->errors(),
], 422); ], 422);
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => 'not_found',
'message' => '客户不存在。',
], 404);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Error updating client', [ Log::error('Error updating client', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(), 'trace' => $e->getTraceAsString(),
'client_id' => $id,
'request_data' => $request->except(['api_token']),
]); ]);
return response()->json([ return response()->json([
@ -114,12 +351,39 @@ public function update(Request $request, int $id): JsonResponse
} }
} }
/**
* 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 public function destroy(Request $request, int $id): JsonResponse
{ {
try { try {
$client = $this->getAuthorizedClient($request->admin, $id); $client = Client::findOrFail($id);
// 检查是否有活跃的认证令牌
if ($client->authTokens()->where('status', 'active')->exists()) {
return response()->json([
'error' => 'client_in_use',
'message' => '该客户有活跃的认证令牌,请先停用或删除令牌。',
], 422);
}
$clientName = $client->name; $clientName = $client->name;
// 软删除客户记录
$client->delete(); $client->delete();
$this->logService->logOperation( $this->logService->logOperation(
@ -129,13 +393,19 @@ public function destroy(Request $request, int $id): JsonResponse
); );
return response()->json([ return response()->json([
'message' => '客户用户已删除。', 'message' => '客户已删除。',
]); ]);
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => 'not_found',
'message' => '客户不存在。',
], 404);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Error deleting client', [ Log::error('Error deleting client', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(), 'trace' => $e->getTraceAsString(),
'client_id' => $id,
]); ]);
return response()->json([ return response()->json([
@ -145,18 +415,58 @@ public function destroy(Request $request, int $id): JsonResponse
} }
} }
/**
* 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 public function generateAuthToken(Request $request, int $id): JsonResponse
{ {
try { try {
$client = $this->getAuthorizedClient($request->admin, $id); $client = Client::findOrFail($id);
if ($client->status !== 'active') {
return response()->json([
'error' => 'client_inactive',
'message' => '客户状态为非活跃,无法生成认证令牌。',
], 422);
}
// 验证过期时间
$validated = $request->validate([ $validated = $request->validate([
'expires_in_days' => 'nullable|integer|min:1', 'expires_in_days' => 'nullable|integer|min:1|max:365',
], [
'expires_in_days.min' => '令牌有效期不能小于 1 天。',
'expires_in_days.max' => '令牌有效期不能超过 365 天。',
]); ]);
$token = $this->tokenService->generateAuthToken( $token = $this->tokenService->generateAuthToken(
$client, $client,
$validated['expires_in_days'] ?? null $validated['expires_in_days'] ?? 30
); );
$this->logService->logOperation( $this->logService->logOperation(
@ -166,10 +476,14 @@ public function generateAuthToken(Request $request, int $id): JsonResponse
); );
return response()->json([ return response()->json([
'client_id' => $client->id,
'auth_token' => $token->token, 'auth_token' => $token->token,
'created_at' => $token->created_at,
'expires_at' => $token->expires_at, 'expires_at' => $token->expires_at,
'client' => [
'id' => $client->id,
'name' => $client->name,
'email' => $client->email,
'status' => $client->status,
],
]); ]);
} catch (ValidationException $e) { } catch (ValidationException $e) {
@ -178,10 +492,17 @@ public function generateAuthToken(Request $request, int $id): JsonResponse
'message' => '请求参数验证失败。', 'message' => '请求参数验证失败。',
'errors' => $e->errors(), 'errors' => $e->errors(),
], 422); ], 422);
} catch (ModelNotFoundException $e) {
return response()->json([
'error' => 'not_found',
'message' => '客户不存在。',
], 404);
} catch (\Exception $e) { } catch (\Exception $e) {
Log::error('Error generating auth token', [ Log::error('Error generating auth token', [
'error' => $e->getMessage(), 'error' => $e->getMessage(),
'trace' => $e->getTraceAsString(), 'trace' => $e->getTraceAsString(),
'client_id' => $id,
'request_data' => $request->except(['api_token']),
]); ]);
return response()->json([ return response()->json([
@ -190,13 +511,4 @@ public function generateAuthToken(Request $request, int $id): JsonResponse
], 500); ], 500);
} }
} }
private function getAuthorizedClient($admin, int $id): Client
{
$query = $admin->isSuperAdmin() ? Client::query() : $admin->clients();
$client = $query->findOrFail($id);
return $client;
}
} }

View file

@ -16,7 +16,7 @@
}); });
// Admin authentication routes // Admin authentication routes
Route::prefix('admin')->group(function () { Route::middleware(['auth:sanctum', 'admin'])->prefix('admin')->group(function () {
Route::post('login', [AdminAuthController::class, 'login']); Route::post('login', [AdminAuthController::class, 'login']);
Route::middleware('auth.admin')->group(function () { Route::middleware('auth.admin')->group(function () {