ownedBanksQuery($request)->withCount('questions')->latest(); if ($keyword = $request->query('keyword')) { $query->where('name', 'like', '%'.$keyword.'%'); } return ApiResponse::page($query->paginate((int) $request->query('per_page', 20))); } #[Apidoc\Title('创建题库')] #[Apidoc\Url('/api/admin/banks')] #[Apidoc\Method('POST')] #[Apidoc\RouteMiddleware(['permission:banks.create'])] public function store(Request $request): JsonResponse { $data = $request->validate([ 'name' => ['required', 'string', 'max:120'], 'description' => ['nullable', 'string'], 'visibility' => ['required', 'in:public,private,assigned'], ]); $bank = QuestionBank::create($data + ['owner_id' => $request->user()->id, 'is_active' => true]); OperationLog::create([ 'user_id' => $request->user()->id, 'action' => 'bank.created', 'target_type' => QuestionBank::class, 'target_id' => $bank->id, 'ip' => $request->ip(), 'payload' => ['name' => $bank->name], ]); return ApiResponse::success($bank, '题库已创建'); } #[Apidoc\Title('更新题库')] #[Apidoc\Url('/api/admin/banks/{bank}')] #[Apidoc\Method('PUT')] #[Apidoc\RouteMiddleware(['permission:banks.update'])] public function update(Request $request, mixed $bank): JsonResponse { $bank = $this->resolveBank($bank); $this->authorizeBankOwner($request, $bank); $data = $request->validate([ 'name' => ['sometimes', 'string', 'max:120'], 'description' => ['nullable', 'string'], 'visibility' => ['sometimes', 'in:public,private,assigned'], 'is_active' => ['sometimes', 'boolean'], ]); $bank->update($data); return ApiResponse::success($bank->fresh(), '题库已更新'); } #[Apidoc\Title('删除题库')] #[Apidoc\Url('/api/admin/banks/{bank}')] #[Apidoc\Method('DELETE')] #[Apidoc\RouteMiddleware(['permission:banks.delete'])] public function destroy(Request $request, mixed $bank): JsonResponse { $bank = $this->resolveBank($bank); $this->authorizeBankOwner($request, $bank); $bank->delete(); OperationLog::create([ 'user_id' => $request->user()->id, 'action' => 'bank.deleted', 'target_type' => QuestionBank::class, 'target_id' => $bank->id, 'ip' => $request->ip(), ]); return ApiResponse::success(null, '题库已删除'); } #[Apidoc\Title('题库授权')] #[Apidoc\Url('/api/admin/banks/{bank}/shares')] #[Apidoc\Method('POST')] #[Apidoc\RouteMiddleware(['permission:banks.share'])] public function share(Request $request, mixed $bank): JsonResponse { $bank = $this->resolveBank($bank); $this->authorizeBankOwner($request, $bank); $data = $request->validate([ 'targets' => ['array'], 'targets.*.type' => ['required', 'in:user,class'], 'targets.*.id' => ['required', 'integer'], ]); DB::table('bank_shares')->where('question_bank_id', $bank->id)->delete(); foreach ($data['targets'] ?? [] as $target) { DB::table('bank_shares')->insert([ 'question_bank_id' => $bank->id, 'target_type' => $target['type'], 'target_id' => $target['id'], 'created_at' => now(), 'updated_at' => now(), ]); } $bank->update(['visibility' => ($data['targets'] ?? []) === [] ? $bank->visibility : 'assigned']); return ApiResponse::success(null, '授权已保存'); } #[Apidoc\Title('题库导出')] #[Apidoc\Url('/api/admin/banks/{bank}/export')] #[Apidoc\Method('POST')] #[Apidoc\RouteMiddleware(['permission:questions.export'])] public function export(Request $request, mixed $bank): JsonResponse { $bank = $this->resolveBank($bank); $this->authorizeBankOwner($request, $bank); $payload = $bank->load('questions.options', 'categories', 'tags')->toArray(); $path = 'exports/bank-'.$bank->id.'-'.now()->format('YmdHis').'.json'; Storage::put($path, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); $job = ExportJob::create([ 'user_id' => $request->user()->id, 'type' => 'question_bank', 'file_path' => $path, 'payload' => ['question_bank_id' => $bank->id], ]); return ApiResponse::success($job, '题库已导出'); } private function resolveBank(mixed $bank): QuestionBank { if ($bank instanceof QuestionBank && $bank->exists) { return $bank; } return QuestionBank::query()->findOrFail((int) $bank); } }