baseUrl = rtrim($this->baseUrl ?: config('question_bank.api_base', 'http://localhost:5015'), '/'); $this->timeout = (int) config('question_bank.timeout', 10); $this->cacheTtl = (int) config('question_bank.cache_ttl', 300); } /** * 获取所有题目(分页) */ public function listQuestions(int $page = 1, int $perPage = 50, array $filters = []): array { $cacheKey = sprintf( 'questions-list-%d-%d-%s', $page, $perPage, md5(json_encode($filters)) ); return Cache::remember( $cacheKey, now()->addSeconds($this->cacheTtl), function () use ($page, $perPage, $filters): array { $query = array_filter([ 'page' => $page, 'per_page' => $perPage, 'kp_code' => $filters['kp_code'] ?? null, 'difficulty' => $filters['difficulty'] ?? null, 'type' => $filters['type'] ?? null, 'skill' => $filters['skill'] ?? null, 'search' => $filters['search'] ?? null, ], fn ($value) => filled($value)); $response = $this->request('GET', '/questions', $query); // 处理数学公式 $data = $response['data'] ?? []; foreach ($data as &$question) { // 使用数学公式处理器处理题目数据 $question = MathFormulaProcessor::processQuestionData($question); } return [ 'data' => $data, 'meta' => $response['meta'] ?? [ 'page' => $page, 'per_page' => $perPage, 'total' => is_array($response) ? count($response) : 0, 'total_pages' => 1, ], ]; } ); } /** * 获取题目统计信息 */ public function getStatistics(): array { $cacheKey = 'question-statistics'; return Cache::remember( $cacheKey, now()->addSeconds($this->cacheTtl), function (): array { $response = $this->request('GET', '/questions/statistics'); return $response ?? [ 'total' => 0, 'by_difficulty' => [], 'by_kp' => [], 'by_source' => [], ]; } ); } /** * 根据 kp_code 获取题目 */ public function getQuestionsByKpCode(string $kpCode, int $limit = 100): array { $response = $this->request('GET', '/questions', [ 'kp_code' => $kpCode, 'limit' => $limit, ]); // 处理数学公式 if ($response && isset($response['data']) && is_array($response['data'])) { foreach ($response['data'] as &$question) { $question = MathFormulaProcessor::processQuestionData($question); } } return $response; } /** * 语义搜索题目 */ public function searchQuestions(string $query, int $limit = 20): array { $cacheKey = sprintf('question-search-%s-%d', md5($query), $limit); return Cache::remember( $cacheKey, now()->addSeconds($this->cacheTtl), function () use ($query, $limit): array { try { $response = $this->request('POST', '/questions/search', [ 'query' => $query, 'limit' => $limit, ]); // 处理数学公式 if ($response && is_array($response)) { $response = MathFormulaProcessor::processArray($response, [ 'stem', 'content', 'question_text', 'answer', 'explanation' ]); } return $response ?? []; } catch (\Exception $e) { \Log::error('Question search failed: ' . $e->getMessage()); return []; } } ); } /** * 获取单个题目详情 */ public function getQuestionById(int $id): ?array { $cacheKey = "question-{$id}"; return Cache::remember( $cacheKey, now()->addSeconds($this->cacheTtl), function () use ($id): ?array { try { $response = $this->request('GET', "/questions/{$id}"); // 处理数学公式 if ($response && is_array($response)) { $response = MathFormulaProcessor::processQuestionData($response); } return $response ?: null; } catch (\Exception $e) { \Log::error("Failed to get question {$id}: " . $e->getMessage()); return null; } } ); } /** * 创建题目(通过 AI 生成) */ public function generateQuestions(array $params): array { try { $response = $this->request('POST', '/questions/generate', $params); return $response ?? [ 'success' => false, 'message' => '生成失败', ]; } catch (\Exception $e) { \Log::error('Question generation failed: ' . $e->getMessage()); return [ 'success' => false, 'message' => '生成失败:' . $e->getMessage(), ]; } } /** * 导入题目(JSON 批量导入) */ public function importQuestions(array $questions): array { try { $response = $this->request('POST', '/questions/import', [ 'questions' => $questions, ]); return $response ?? [ 'success' => false, 'message' => '导入失败', ]; } catch (\Exception $e) { \Log::error('Question import failed: ' . $e->getMessage()); return [ 'success' => false, 'message' => '导入失败:' . $e->getMessage(), ]; } } /** * 删除题目 */ public function deleteQuestion(int $id): bool { try { $response = $this->request('DELETE', "/questions/{$id}"); return $response['success'] ?? false; } catch (\Exception $e) { \Log::error("Failed to delete question {$id}: " . $e->getMessage()); return false; } } /** * 获取知识点选项(从知识图谱服务) */ public function getKnowledgePointOptions(): array { try { // 使用新的知识图谱API $knowledgeApiBase = config('services.knowledge_api.base_url', 'http://localhost:5011'); $response = Http::timeout(10) ->get($knowledgeApiBase . '/graph/export'); if ($response->successful()) { $data = $response->json(); $nodes = $data['nodes'] ?? []; // 转换为键值对格式 $options = []; foreach ($nodes as $node) { $code = $node['kp_code'] ?? null; $name = $node['cn_name'] ?? null; if ($code && $name) { $options[$code] = $name; } } // 按代码排序 ksort($options); \Log::info('成功获取知识点选项', ['count' => count($options)]); return $options; } \Log::warning('知识图谱API调用失败', [ 'status' => $response->status(), 'url' => $knowledgeApiBase . '/graph/export' ]); } catch (\Exception $e) { \Log::error('Failed to get knowledge points: ' . $e->getMessage()); } // 返回空数组作为fallback return []; } /** * 获取提示词模板列表 */ public function listPrompts(?string $type = null, ?string $active = null): array { $query = array_filter([ 'type' => $type, 'active' => $active, ], fn ($value) => filled($value)); try { $response = $this->request('GET', '/prompts', $query); return $response ?? []; } catch (\Exception $e) { \Log::error('Failed to get prompts: ' . $e->getMessage()); return []; } } /** * 保存提示词模板 */ public function savePrompt(array $data): array { try { // 先尝试更新 $response = $this->request('PUT', '/prompts/default', $data); // 如果更新失败(可能不存在),则尝试创建 if (!isset($response['success']) || !$response['success']) { $response = $this->request('POST', '/prompts', $data); } return $response ?? [ 'success' => true, 'message' => '提示词保存成功', ]; } catch (\Exception $e) { \Log::error('Failed to save prompt: ' . $e->getMessage()); return [ 'success' => false, 'message' => '保存失败:' . $e->getMessage(), ]; } } /** * @throws RequestException * @return mixed */ protected function request(string $method, string $path, array $params = []): mixed { $response = $this->http()->send($method, ltrim($path, '/'), [ 'query' => $method === 'GET' ? $params : null, 'json' => $method !== 'GET' ? $params : null, ]); return $response->throw()->json(); } protected function http(): PendingRequest { return Http::baseUrl($this->baseUrl) ->acceptJson() ->timeout($this->timeout) ->retry(2, 200) ->withHeaders([ 'User-Agent' => 'Laravel/' . (app()->version()), 'Accept' => 'application/json', ]); } }