baseUrl = config('services.question_bank.base_url', env('QUESTION_BANK_API_BASE', 'http://localhost:5015')); $this->baseUrl = rtrim($this->baseUrl, '/'); } /** * 获取题目列表 */ public function listQuestions(int $page = 1, int $perPage = 50, array $filters = []): array { try { $response = Http::timeout(10) ->get($this->baseUrl . '/questions', [ 'page' => $page, 'per_page' => $perPage, ...$filters ]); if ($response->successful()) { return $response->json(); } Log::warning('题库API调用失败', [ 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('获取题目列表失败', [ 'error' => $e->getMessage() ]); } return ['data' => [], 'meta' => ['total' => 0]]; } /** * 筛选题目 (支持 kp_codes, skills 等高级筛选) */ public function filterQuestions(array $params): array { try { $response = Http::timeout(30) ->get($this->baseUrl . '/questions', $params); if ($response->successful()) { return $response->json(); } Log::warning('筛选题目API调用失败', [ 'status' => $response->status(), 'params' => $params ]); } catch (\Exception $e) { Log::error('筛选题目异常', [ 'error' => $e->getMessage(), 'params' => $params ]); } return ['data' => []]; } /** * 批量获取题目详情(根据题目 ID 列表) */ public function getQuestionsByIds(array $ids): array { if (empty($ids)) { return ['data' => []]; } try { $response = Http::timeout(15) ->get($this->baseUrl . '/questions', [ 'ids' => implode(',', $ids), ]); if ($response->successful()) { return $response->json(); } Log::warning('批量获取题目失败', [ 'ids' => $ids, 'status' => $response->status(), ]); } catch (\Exception $e) { Log::error('批量获取题目异常', [ 'ids' => $ids, 'error' => $e->getMessage(), ]); } return ['data' => []]; } /** * 智能生成题目(异步模式) */ public function generateIntelligentQuestions(array $params, ?string $callbackUrl = null): array { try { // 添加回调 URL if ($callbackUrl) { $params['callback_url'] = $callbackUrl; } $response = Http::timeout(10) ->post($this->baseUrl . '/generate-intelligent-questions', $params); if ($response->successful()) { return $response->json(); } Log::warning('题目生成API调用失败', [ 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('题目生成异常', [ 'error' => $e->getMessage() ]); } return ['success' => false, 'message' => '生成失败']; } /** * 获取任务状态 */ public function getTaskStatus(string $taskId): ?array { try { $response = Http::timeout(10) ->get($this->baseUrl . '/tasks/' . $taskId); if ($response->successful()) { return $response->json(); } Log::warning('获取任务状态失败', [ 'task_id' => $taskId, 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('获取任务状态异常', [ 'task_id' => $taskId, 'error' => $e->getMessage() ]); } return null; } /** * 获取任务列表 */ public function listTasks(?string $status = null, int $page = 1, int $perPage = 10): array { try { $params = [ 'page' => $page, 'per_page' => $perPage ]; if ($status) { $params['status'] = $status; } $response = Http::timeout(10) ->get($this->baseUrl . '/tasks', $params); if ($response->successful()) { return $response->json(); } Log::warning('获取任务列表失败', [ 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('获取任务列表异常', [ 'error' => $e->getMessage() ]); } return ['data' => [], 'meta' => ['total' => 0]]; } /** * 获取题目统计信息 */ public function getStatistics(): array { try { $response = Http::timeout(10) ->get($this->baseUrl . '/questions/statistics'); if ($response->successful()) { return $response->json(); } Log::warning('获取题目统计失败', [ 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('获取题目统计异常', [ 'error' => $e->getMessage() ]); } return [ 'total' => 0, 'by_difficulty' => [], 'by_kp' => [], 'by_source' => [] ]; } /** * 根据知识点获取题目 */ public function getQuestionsByKpCode(string $kpCode, int $limit = 100): array { try { $response = Http::timeout(10) ->get($this->baseUrl . '/questions', [ 'kp_code' => $kpCode, 'limit' => $limit ]); if ($response->successful()) { return $response->json(); } } catch (\Exception $e) { Log::error('根据知识点获取题目失败', [ 'kp_code' => $kpCode, 'error' => $e->getMessage() ]); } return []; } /** * 删除题目 */ public function deleteQuestion(string $questionCode): bool { try { $response = Http::timeout(10) ->delete($this->baseUrl . "/questions/{$questionCode}"); // 只有返回204(删除成功)才返回true,404(不存在)返回false if ($response->status() === 204) { return true; } if ($response->status() === 404) { Log::warning('尝试删除不存在的题目', ['question_code' => $questionCode]); return false; } return false; } catch (\Exception $e) { Log::error('删除题目失败', [ 'question_code' => $questionCode, 'error' => $e->getMessage() ]); return false; } } /** * 智能选择试卷题目 */ public function selectQuestionsForExam(int $totalQuestions, array $filters): array { try { $response = Http::timeout(30) ->post($this->baseUrl . '/exam/select-questions', [ 'total_questions' => $totalQuestions, 'filters' => $filters ]); if ($response->successful()) { return $response->json('data', []); } Log::warning('智能选题API调用失败', [ 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('智能选题异常', [ 'error' => $e->getMessage() ]); } return []; } /** * 保存试卷到数据库(本地 papers 表) */ public function saveExamToDatabase(array $examData): ?string { try { // 生成试卷ID $paperId = 'paper_' . time() . '_' . bin2hex(random_bytes(4)); // 保存到 papers 表 \Illuminate\Support\Facades\DB::table('papers')->insert([ 'paper_id' => $paperId, 'student_id' => $examData['student_id'] ?? '', 'teacher_id' => $examData['teacher_id'] ?? '', 'paper_name' => $examData['paper_name'] ?? '未命名试卷', 'paper_type' => 'auto_generated', 'question_count' => $examData['total_questions'] ?? 0, 'total_score' => $examData['total_score'] ?? 0, 'status' => 'draft', 'difficulty_category' => $examData['difficulty_category'] ?? '基础', 'created_at' => now(), 'updated_at' => now(), ]); // 如果有题目列表,保存到 paper_questions 表 if (!empty($examData['questions'])) { foreach ($examData['questions'] as $index => $question) { // 处理难度字段:如果是字符串则转换为数字 $difficultyValue = $question['difficulty'] ?? 0.5; if (is_string($difficultyValue)) { // 将中文难度转换为数字 if (strpos($difficultyValue, '基础') !== false || strpos($difficultyValue, '简单') !== false) { $difficultyValue = 0.3; } elseif (strpos($difficultyValue, '中等') !== false || strpos($difficultyValue, '一般') !== false) { $difficultyValue = 0.6; } elseif (strpos($difficultyValue, '拔高') !== false || strpos($difficultyValue, '困难') !== false) { $difficultyValue = 0.9; } else { $difficultyValue = 0.5; } } // 确保 knowledge_point 有值 $knowledgePoint = $question['kp'] ?? $question['kp_code'] ?? $question['knowledge_point'] ?? $question['knowledge_point_code'] ?? ''; if (empty($knowledgePoint) && isset($question['kp_code'])) { $knowledgePoint = $question['kp_code']; } // 获取题目类型 $questionType = $question['question_type'] ?? 'answer'; if (!$questionType) { // 如果没有类型,根据内容推断 $content = $question['stem'] ?? $question['content'] ?? ''; if (is_string($content)) { // 1. 优先检查填空题(下划线) if (strpos($content, '____') !== false || strpos($content, '______') !== false) { $questionType = 'fill'; } // 2. 检查选择题(必须有选项 A. B. C. D.) elseif (preg_match('/[A-D]\s*\./', $content) || preg_match('/\([A-D]\)/', $content)) { if (preg_match('/A\./', $content) && preg_match('/B\./', $content)) { $questionType = 'choice'; } else { // 只有括号没有选项,可能是填空 if (strpos($content, '()') !== false || strpos($content, '()') !== false) { $questionType = 'fill'; } else { $questionType = 'answer'; } } } // 3. 检查纯括号填空 elseif (strpos($content, '()') !== false || strpos($content, '()') !== false) { $questionType = 'fill'; } else { $questionType = 'answer'; } } else { $questionType = 'answer'; } } \Illuminate\Support\Facades\DB::table('paper_questions')->insert([ 'id' => $paperId . '_q' . ($index + 1), 'paper_id' => $paperId, 'question_bank_id' => $question['id'] ?? $question['question_id'] ?? 0, 'knowledge_point' => $knowledgePoint, 'question_type' => $questionType, 'difficulty' => $difficultyValue, 'score' => $question['score'] ?? 5, // 默认5分 'estimated_time' => $question['estimated_time'] ?? 300, 'question_number' => $index + 1, ]); } } Log::info('试卷保存成功', ['paper_id' => $paperId, 'question_count' => count($examData['questions'] ?? [])]); return $paperId; } catch (\Exception $e) { Log::error('保存试卷到数据库失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return null; } } /** * 获取试卷列表 */ public function listExams(int $page = 1, int $perPage = 20): array { try { $response = Http::timeout(10) ->get($this->baseUrl . '/exam/list', [ 'page' => $page, 'per_page' => $perPage ]); if ($response->successful()) { return $response->json(); } Log::warning('获取试卷列表失败', [ 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('获取试卷列表异常', [ 'error' => $e->getMessage() ]); } return ['data' => [], 'meta' => ['total' => 0]]; } /** * 获取试卷详情 */ public function getExamById(string $paperId): ?array { try { $response = Http::timeout(10) ->get($this->baseUrl . '/exam/' . $paperId); if ($response->successful()) { return $response->json(); } Log::warning('获取试卷详情失败', [ 'paper_id' => $paperId, 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('获取试卷详情异常', [ 'paper_id' => $paperId, 'error' => $e->getMessage() ]); } return null; } /** * 导出试卷为PDF */ public function exportExamToPdf(string $paperId): ?string { try { $response = Http::timeout(60) ->get($this->baseUrl . '/exam/' . $paperId . '/export/pdf'); if ($response->successful()) { // 返回PDF文件路径或URL return $response->json('pdf_url', null); } Log::warning('导出PDF失败', [ 'paper_id' => $paperId, 'status' => $response->status() ]); } catch (\Exception $e) { Log::error('导出PDF异常', [ 'paper_id' => $paperId, 'error' => $e->getMessage() ]); } return null; } /** * 检查服务健康状态 */ public function checkHealth(): bool { try { $response = Http::timeout(5) ->get($this->baseUrl . '/health'); return $response->successful(); } catch (\Exception $e) { Log::error('题库服务健康检查失败', [ 'error' => $e->getMessage() ]); return false; } } }