|
|
@@ -961,6 +961,32 @@ class ExamTypeStrategy
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // 【新增】如果用户指定了章节,解析章节节点类型并获取所有section/subsection节点
|
|
|
+ if (!empty($chapterIdList)) {
|
|
|
+ Log::info('ExamTypeStrategy: 用户指定了章节,开始解析节点类型', [
|
|
|
+ 'input_chapter_ids' => $chapterIdList
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 解析用户传入的chapter_id_list,获取所有section/subsection节点
|
|
|
+ $resolvedSectionIds = $this->resolveSectionNodesFromChapters($chapterIdList);
|
|
|
+
|
|
|
+ if (!empty($resolvedSectionIds)) {
|
|
|
+ // 使用解析后的section/subsection节点ID替换原有的chapterIdList
|
|
|
+ $originalChapterCount = count($chapterIdList);
|
|
|
+ $chapterIdList = $resolvedSectionIds;
|
|
|
+
|
|
|
+ Log::info('ExamTypeStrategy: 章节节点解析完成,使用解析后的节点', [
|
|
|
+ 'original_count' => $originalChapterCount,
|
|
|
+ 'resolved_count' => count($chapterIdList),
|
|
|
+ 'resolved_ids' => $chapterIdList
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ Log::warning('ExamTypeStrategy: 章节节点解析失败,使用原始节点列表', [
|
|
|
+ 'chapter_id_list' => $chapterIdList
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
if (empty($chapterIdList)) {
|
|
|
Log::warning('ExamTypeStrategy: 教材组卷需要 chapter_id_list 参数或有效的textbook_id');
|
|
|
return $this->buildGeneralParams($params);
|
|
|
@@ -972,7 +998,7 @@ class ExamTypeStrategy
|
|
|
'total_questions' => $totalQuestions
|
|
|
]);
|
|
|
|
|
|
- // 【优化】第一步:根据章节ID查询知识点关联,并统计每个章节的知识点数量
|
|
|
+ // 【修复】第一步:根据章节ID查询知识点关联,并统计每个章节的知识点数量
|
|
|
$kpCodes = $this->getKnowledgePointsFromChapters($chapterIdList);
|
|
|
|
|
|
// 【新增】获取每个章节对应的知识点数量统计
|
|
|
@@ -983,16 +1009,17 @@ class ExamTypeStrategy
|
|
|
'total_chapters' => count($chapterIdList)
|
|
|
]);
|
|
|
|
|
|
- // 【重要】即使没有找到知识点关联,也保留textbook_catalog_node_ids参数用于题目筛选
|
|
|
+ // 【重要】教材组卷严格限制知识点数量,不进行任何扩展
|
|
|
if (empty($kpCodes)) {
|
|
|
Log::warning('ExamTypeStrategy: 未找到章节知识点关联,但保留章节筛选参数', [
|
|
|
'chapter_id_list' => $chapterIdList,
|
|
|
'note' => '将在LearningAnalyticsService中按textbook_catalog_nodes_id字段筛选题目'
|
|
|
]);
|
|
|
} else {
|
|
|
- Log::info('ExamTypeStrategy: 获取章节知识点', [
|
|
|
+ Log::info('ExamTypeStrategy: 获取章节知识点(教材组卷严格限制)', [
|
|
|
'kp_count' => count($kpCodes),
|
|
|
- 'kp_codes' => array_slice($kpCodes, 0, 5)
|
|
|
+ 'kp_codes' => $kpCodes,
|
|
|
+ 'note' => '教材组卷只返回章节关联的知识点,不进行扩展'
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
@@ -1395,4 +1422,130 @@ class ExamTypeStrategy
|
|
|
return [];
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【新增】递归获取section节点ID列表
|
|
|
+ * 根据用户传入的chapter_id_list,自动识别节点类型:
|
|
|
+ * - 如果是chapter节点,查找其下的所有section子节点(不包含subsection)
|
|
|
+ * - 如果是section节点,直接使用
|
|
|
+ *
|
|
|
+ * @param array $chapterIdList 用户传入的章节ID列表
|
|
|
+ * @return array 最终用于查询知识点的section节点ID列表
|
|
|
+ */
|
|
|
+ private function resolveSectionNodesFromChapters(array $chapterIdList): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ if (empty($chapterIdList)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('ExamTypeStrategy: 开始解析章节节点', [
|
|
|
+ 'input_chapter_ids' => $chapterIdList
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $finalSectionIds = [];
|
|
|
+
|
|
|
+ foreach ($chapterIdList as $nodeId) {
|
|
|
+ // 查询节点信息
|
|
|
+ $node = DB::table('textbook_catalog_nodes')
|
|
|
+ ->where('id', $nodeId)
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if (!$node) {
|
|
|
+ Log::warning('ExamTypeStrategy: 未找到节点', ['node_id' => $nodeId]);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::debug('ExamTypeStrategy: 处理节点', [
|
|
|
+ 'node_id' => $nodeId,
|
|
|
+ 'node_type' => $node->node_type,
|
|
|
+ 'title' => $node->title
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 根据节点类型处理
|
|
|
+ switch ($node->node_type) {
|
|
|
+ case 'chapter':
|
|
|
+ // 如果是chapter节点,查找其下的所有section子节点
|
|
|
+ $childNodes = DB::table('textbook_catalog_nodes')
|
|
|
+ ->where('parent_id', $nodeId)
|
|
|
+ ->where('node_type', 'section')
|
|
|
+ ->orderBy('sort_order')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ Log::info('ExamTypeStrategy: 解析chapter节点', [
|
|
|
+ 'chapter_id' => $nodeId,
|
|
|
+ 'chapter_title' => $node->title,
|
|
|
+ 'section_count' => count($childNodes)
|
|
|
+ ]);
|
|
|
+
|
|
|
+ foreach ($childNodes as $child) {
|
|
|
+ if (!in_array($child->id, $finalSectionIds)) {
|
|
|
+ $finalSectionIds[] = $child->id;
|
|
|
+ Log::debug('ExamTypeStrategy: 添加section子节点', [
|
|
|
+ 'parent_chapter' => $nodeId,
|
|
|
+ 'child_node_id' => $child->id,
|
|
|
+ 'child_title' => $child->title,
|
|
|
+ 'child_type' => $child->node_type
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'section':
|
|
|
+ // 如果是section节点,直接使用
|
|
|
+ if (!in_array($nodeId, $finalSectionIds)) {
|
|
|
+ $finalSectionIds[] = $nodeId;
|
|
|
+ Log::debug('ExamTypeStrategy: 直接使用section节点', [
|
|
|
+ 'node_id' => $nodeId,
|
|
|
+ 'title' => $node->title,
|
|
|
+ 'node_type' => $node->node_type
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 'subsection':
|
|
|
+ // 如果是subsection节点,需要找到其父级section节点
|
|
|
+ $parentSection = DB::table('textbook_catalog_nodes')
|
|
|
+ ->where('id', $node->parent_id)
|
|
|
+ ->where('node_type', 'section')
|
|
|
+ ->first();
|
|
|
+
|
|
|
+ if ($parentSection && !in_array($parentSection->id, $finalSectionIds)) {
|
|
|
+ $finalSectionIds[] = $parentSection->id;
|
|
|
+ Log::debug('ExamTypeStrategy: 通过subsection找到父级section', [
|
|
|
+ 'subsection_id' => $nodeId,
|
|
|
+ 'subsection_title' => $node->title,
|
|
|
+ 'parent_section_id' => $parentSection->id,
|
|
|
+ 'parent_section_title' => $parentSection->title
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ Log::warning('ExamTypeStrategy: 未知节点类型', [
|
|
|
+ 'node_id' => $nodeId,
|
|
|
+ 'node_type' => $node->node_type
|
|
|
+ ]);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('ExamTypeStrategy: 章节节点解析完成', [
|
|
|
+ 'input_count' => count($chapterIdList),
|
|
|
+ 'output_section_count' => count($finalSectionIds),
|
|
|
+ 'section_ids' => $finalSectionIds
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $finalSectionIds;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('ExamTypeStrategy: 解析章节节点失败', [
|
|
|
+ 'chapter_id_list' => $chapterIdList,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 出错时返回空数组
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|