Parcourir la source

chore: log 清理,减少冗余与敏感信息

- LearningAnalyticsService: 移除/降级步骤明细、筛选过程等
- ExamTypeStrategy: 构建参数、难度分布等降为 DEBUG
- ExamPdfController: paper_questions 不再记录题目内容
- PdfMerger: pdfunite 检测降为 DEBUG
- IntelligentExamController: 题型排序等降为 DEBUG
- 保留:错误、警告、组卷完成、试卷保存、PDF 成功等关键日志

Made-with: Cursor
yemeishu il y a 1 semaine
Parent
commit
60998e89b4

+ 2 - 2
app/Http/Controllers/Api/IntelligentExamController.php

@@ -941,7 +941,7 @@ class IntelligentExamController extends Controller
         $sortedQuestions = array_merge($choiceQuestions, $fillQuestions, $answerQuestions);
 
         // 调试日志
-        \Illuminate\Support\Facades\Log::info('adjustQuestionScores 开始', [
+        Log::debug('adjustQuestionScores 开始', [
             'choice_count' => count($choiceQuestions),
             'fill_count' => count($fillQuestions),
             'answer_count' => count($answerQuestions),
@@ -1256,7 +1256,7 @@ class IntelligentExamController extends Controller
         }
         unset($question);
 
-        Log::info('题目已按题型内难度排序', [
+        Log::debug('题目已按题型内难度排序', [
             'choice_difficulty' => array_map(fn ($q) => $q['difficulty'] ?? null, $grouped['choice']),
             'fill_difficulty' => array_map(fn ($q) => $q['difficulty'] ?? null, $grouped['fill']),
             'answer_difficulty' => array_map(fn ($q) => $q['difficulty'] ?? null, $grouped['answer']),

+ 3 - 4
app/Http/Controllers/ExamPdfController.php

@@ -633,10 +633,9 @@ class ExamPdfController extends Controller
                 ];
             }
 
-            Log::info('paper_questions表原始数据', [
+            Log::debug('paper_questions 获取题目', [
                 'paper_id' => $paper_id,
-                'sample_questions' => array_slice($questionsData, 0, 3),
-                'all_types' => array_column($questionsData, 'question_type'),
+                'question_count' => count($questionsData),
             ]);
 
             // 如果需要完整题目详情(stem等),可以从题库获取
@@ -729,7 +728,7 @@ class ExamPdfController extends Controller
                 : $this->determineQuestionType($q);
 
             // 详细调试:记录题目类型判断结果
-            Log::info('题目类型判断', [
+            Log::debug('题目类型判断', [
                 'question_id' => $q['id'] ?? '',
                 'has_question_type' => isset($q['question_type']),
                 'question_type_value' => $q['question_type'] ?? null,

+ 21 - 36
app/Services/ExamTypeStrategy.php

@@ -39,11 +39,6 @@ class ExamTypeStrategy
      */
     public function buildParams(array $baseParams, int $assembleType): array
     {
-        Log::info('ExamTypeStrategy: 构建组卷参数', [
-            'assemble_type' => $assembleType,
-            'base_params_keys' => array_keys($baseParams)
-        ]);
-
         // 映射 assembleType 到实际处理逻辑
         $actualType = match($assembleType) {
             0, 9 => 'chapter_diagnostic',   // 摸底 → 章节摸底(新逻辑)
@@ -51,9 +46,9 @@ class ExamTypeStrategy
             default => $assembleType        // 其他保持不变
         };
 
-        Log::info('ExamTypeStrategy: assembleType 映射', [
-            'original' => $assembleType,
-            'actual' => $actualType,
+        Log::debug('ExamTypeStrategy: 构建组卷参数', [
+            'assemble_type' => $assembleType,
+            'actual_type' => $actualType,
         ]);
 
         return match($actualType) {
@@ -93,7 +88,7 @@ class ExamTypeStrategy
      */
     private function buildGeneralParams(array $params): array
     {
-        Log::info('ExamTypeStrategy: 通用智能出卷参数', $params);
+        Log::debug('ExamTypeStrategy: 通用智能出卷参数', array_keys($params));
 
         // 返回原始参数,难度分布逻辑在 buildParams 中统一处理
         return $params;
@@ -115,10 +110,9 @@ class ExamTypeStrategy
         $difficultyCategory = (int) ($params['difficulty_category'] ?? 1);
         $totalQuestions = (int) ($params['total_questions'] ?? 20);
 
-        Log::info('ExamTypeStrategy: 应用难度系数分布', [
+        Log::debug('ExamTypeStrategy: 应用难度系数分布', [
             'difficulty_category' => $difficultyCategory,
-            'total_questions' => $totalQuestions,
-            'assemble_type' => $assembleType
+            'total_questions' => $totalQuestions
         ]);
 
         // 根据难度类别计算题目分布
@@ -148,7 +142,7 @@ class ExamTypeStrategy
             'difficulty_category' => $difficultyCategory,
         ]);
 
-        Log::info('ExamTypeStrategy: 难度分布应用完成', [
+        Log::debug('ExamTypeStrategy: 难度分布应用完成', [
             'category' => $difficultyCategory,
             'distribution' => $distribution
         ]);
@@ -974,9 +968,8 @@ class ExamTypeStrategy
             return $this->buildGeneralParams($params);
         }
 
-        Log::info('ExamTypeStrategy: 知识点组卷', [
-            'kp_code_list' => $kpCodeList,
-            'student_id' => $studentId,
+        Log::debug('ExamTypeStrategy: 知识点组卷', [
+            'kp_count' => count($kpCodeList),
             'total_questions' => $totalQuestions
         ]);
 
@@ -1006,25 +999,18 @@ class ExamTypeStrategy
             $childAddedCount = max(0, $finalPoolCount - $basePoolCount);
         }
 
-        // 三项汇总日志放在外围,便于统一观察策略触发情况。
-        Log::info('ExamTypeStrategy: 知识点组卷题池评估', [
-            'original_kp_count' => count($kpCodeList),
-            'child_kp_added_count' => $childAddedCount,
-            'pool_count_after_expand' => $finalPoolCount,
-            'total_questions' => (int) $totalQuestions,
-            'assemble_type' => $assembleType,
-        ]);
-
         // 如果有学生ID,获取已做过的题目ID列表(用于排除)
         $answeredQuestionIds = [];
         if ($studentId) {
             $answeredQuestionIds = $this->getStudentAnsweredQuestionIds($studentId, $kpCodeList);
-            Log::info('ExamTypeStrategy: 获取学生已答题目', [
-                'student_id' => $studentId,
-                'answered_count' => count($answeredQuestionIds)
-            ]);
         }
 
+        Log::debug('ExamTypeStrategy: 知识点组卷题池评估', [
+            'kp_count' => count($kpCodeList),
+            'pool_count' => $finalPoolCount,
+            'exclude_count' => count($answeredQuestionIds),
+        ]);
+
         // 组装增强参数
         $enhanced = array_merge($params, [
             'kp_codes' => $kpCodeList,
@@ -1046,10 +1032,9 @@ class ExamTypeStrategy
             'is_knowledge_point_assemble' => true,
         ]);
 
-        Log::info('ExamTypeStrategy: 知识点组卷参数构建完成', [
+        Log::debug('ExamTypeStrategy: 知识点组卷参数构建完成', [
             'kp_count' => count($kpCodeList),
             'exclude_count' => count($answeredQuestionIds),
-            'total_questions' => $totalQuestions
         ]);
 
         return $enhanced;
@@ -1062,7 +1047,10 @@ class ExamTypeStrategy
      */
     private function buildTextbookAssembleParams(array $params): array
     {
-        Log::info('ExamTypeStrategy: 构建教材组卷参数', $params);
+        Log::debug('ExamTypeStrategy: 构建教材组卷参数', [
+            'chapter_count' => count($params['chapter_id_list'] ?? []),
+            'textbook_id' => $params['textbook_id'] ?? null
+        ]);
 
         $chapterIdList = $params['chapter_id_list'] ?? [];
         $studentId = $params['student_id'] ?? null;
@@ -1462,11 +1450,8 @@ class ExamTypeStrategy
                 ->values()
                 ->toArray();
 
-            $paperCount = Paper::where('student_id', $studentId)->count();
-
-            Log::info('ExamTypeStrategy: 从 paper_questions 获取学生已做题目', [
+            Log::debug('ExamTypeStrategy: 从 paper_questions 获取学生已做题目', [
                 'student_id' => $studentId,
-                'paper_count' => $paperCount,
                 'exclude_count' => count($questionIds),
             ]);
 

+ 50 - 183
app/Services/LearningAnalyticsService.php

@@ -1216,10 +1216,8 @@ class LearningAnalyticsService
             $assembleType = (int) ($params['assemble_type'] ?? 4); // 默认为通用类型(4)
             $examTypeLegacy = $params['exam_type'] ?? 'general'; // 兼容旧版参数
 
-            Log::info('LearningAnalyticsService: 检查组卷策略', [
-                'assemble_type' => $assembleType,
-                'exam_type_legacy' => $examTypeLegacy,
-                'has_question_expansion_service' => !empty($this->questionExpansionService)
+            Log::debug('LearningAnalyticsService: 检查组卷策略', [
+                'assemble_type' => $assembleType
             ]);
 
             // 如果有 assemble_type 参数,优先使用新的参数系统
@@ -1228,14 +1226,13 @@ class LearningAnalyticsService
                     // 确保QuestionExpansionService和QuestionLocalService可用
                     $questionExpansionService = $this->questionExpansionService ?? app(QuestionExpansionService::class);
                     $questionLocalService = app(QuestionLocalService::class);
-                    Log::info('LearningAnalyticsService: 从容器获取服务实例');
+                    Log::debug('LearningAnalyticsService: 从容器获取服务实例');
 
                     $strategy = new ExamTypeStrategy($questionExpansionService, $questionLocalService);
                     $params = $strategy->buildParams($params, $assembleType);
 
-                    Log::info('LearningAnalyticsService: 已应用组卷策略', [
-                        'assemble_type' => $assembleType,
-                        'enhanced_params_keys' => array_keys($params)
+                    Log::debug('LearningAnalyticsService: 已应用组卷策略', [
+                        'assemble_type' => $assembleType
                     ]);
                 } catch (Exception $e) {
                     Log::warning('LearningAnalyticsService: 组卷策略应用失败,使用默认策略', [
@@ -1254,9 +1251,8 @@ class LearningAnalyticsService
                     $strategy = new ExamTypeStrategy($questionExpansionService, $questionLocalService);
                     $params = $strategy->buildParamsLegacy($params, $examTypeLegacy);
 
-                    Log::info('LearningAnalyticsService: 已应用组卷策略(兼容模式)', [
-                        'exam_type' => $examTypeLegacy,
-                        'enhanced_params_keys' => array_keys($params)
+                    Log::debug('LearningAnalyticsService: 已应用组卷策略(兼容模式)', [
+                        'exam_type' => $examTypeLegacy
                     ]);
                 } catch (Exception $e) {
                     Log::warning('LearningAnalyticsService: 组卷策略应用失败,使用默认策略', [
@@ -1296,32 +1292,23 @@ class LearningAnalyticsService
             Log::info("generateIntelligentExam 开始", [
                 'student_id' => $studentId,
                 'total_questions' => $totalQuestions,
-                'kp_codes' => $kpCodes,
-                'skills' => $skills,
                 'assemble_type' => $assembleType,
-                'exam_type_legacy' => $examTypeLegacy,
-                'question_category' => $questionCategory,
+                'kp_count' => count($kpCodes),
             ]);
 
             // 1. 如果指定了学生,获取学生的薄弱点
             $weaknessFilter = [];
             if ($studentId) {
-                Log::info("获取学生薄弱点: $studentId");
+                Log::debug("获取学生薄弱点", ['student_id' => $studentId]);
 
                 $weaknesses = $this->getStudentWeaknesses($studentId, 20);
-                Log::info("薄弱点数量: " . count($weaknesses), [
-                    '薄弱点' => $weaknesses,
-                ]);
+                Log::debug("薄弱点数量", ['count' => count($weaknesses)]);
 
                 $weaknessFilter = array_column($weaknesses, 'kp_code');
 
                 // 【修复】教材出卷(assemble_type=3)不使用薄弱点,严格按章节获取知识点
                 if ($assembleType == 3) {
-                    Log::info("LearningAnalyticsService: 教材出卷不使用薄弱点", [
-                        'assemble_type' => $assembleType,
-                        'weakness_count' => count($weaknessFilter),
-                        'action' => '将使用ExamTypeStrategy从章节关联获取的知识点'
-                    ]);
+                    Log::debug("LearningAnalyticsService: 教材出卷不使用薄弱点");
                     // 教材组卷不使用薄弱点
                 } else {
                     // 如果用户没有指定知识点,使用学生的薄弱点(非教材组卷)
@@ -1334,9 +1321,9 @@ class LearningAnalyticsService
                 }
             }
 
-            Log::info("准备调用 getQuestionsFromBank", [
-                'kp_codes' => $kpCodes,
-                'skills' => $skills,
+            Log::debug("准备调用 getQuestionsFromBank", [
+                'kp_codes_count' => count($kpCodes),
+                'skills_count' => count($skills),
             ]);
 
             // 2. 优先使用学生错题(如果存在)
@@ -1420,12 +1407,9 @@ class LearningAnalyticsService
                 try {
                     // 【优化】教材出卷(assemble_type=3)保留知识点筛选,但额外使用章节筛选
                     if ($assembleType == 3) {
-                        Log::info('LearningAnalyticsService: 教材出卷模式,保留知识点筛选', [
-                            'assemble_type' => $assembleType,
-                            'kp_codes_count' => count($kpCodes),
-                            'chapter_ids' => $params['chapter_id_list'] ?? [],
-                            'textbook_catalog_node_ids' => $params['textbook_catalog_node_ids'] ?? [],
-                        ]);
+                    Log::debug('LearningAnalyticsService: 教材出卷模式,保留知识点筛选', [
+                        'kp_codes_count' => count($kpCodes),
+                    ]);
                     }
 
                     // 【优化】获取textbook_catalog_node_ids参数(教材组卷时使用)
@@ -1434,16 +1418,10 @@ class LearningAnalyticsService
                     $textbookId = isset($params['textbook_id']) ? (int) $params['textbook_id'] : null;
                     $difficultyCategory = (int) ($params['difficulty_category'] ?? 1);
 
-                    Log::info('开始调用 getQuestionsFromBank 补充题目', [
-                        'kp_codes_count' => count($kpCodes),
-                        'skills_count' => count($skills),
-                        'has_mistake_priority' => !empty($mistakeQuestionIds),
+                    Log::debug('开始调用 getQuestionsFromBank 补充题目', [
                         'need_more' => $totalQuestions - count($priorityQuestions),
                         'assemble_type' => $assembleType,
                         'grade' => $grade,
-                        'textbook_id' => $textbookId,
-                        'difficulty_category' => $difficultyCategory,
-                        'note' => $textbookId ? '将使用 textbook_id 限制补充范围,避免超纲' : '未指定教材,使用整个年级范围'
                     ]);
 
                     // 【修复超纲问题】传入 grade 和 textbook_id,用于智能补充时限制范围
@@ -1466,14 +1444,7 @@ class LearningAnalyticsService
                     );
                     $allQuestions = array_merge($priorityQuestions, $additionalQuestions);
 
-                    Log::info('getQuestionsFromBank 调用完成', [
-                        'questions_count' => count($allQuestions),
-                        'is_array' => is_array($allQuestions),
-                        'first_question_id' => !empty($allQuestions) ? ($allQuestions[0]['id'] ?? 'N/A') : 'N/A',
-                        '耗时' => round((microtime(true) - $startTime) * 1000, 2) . 'ms',
-                    ]);
-
-                    Log::info('getQuestionsFromBank 返回', [
+                    Log::info('getQuestionsFromBank 完成', [
                         'questions_count' => count($allQuestions),
                         'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
                     ]);
@@ -1523,15 +1494,6 @@ class LearningAnalyticsService
             // 错题本类型:使用所有错题,但不超过最大值限制
             $targetQuestionCount = $strictMistakeBook ? min(count($allQuestions), $maxQuestions) : $totalQuestions;
 
-            Log::info('开始调用 selectQuestionsByMastery', [
-                'input_count' => count($allQuestions),
-                'target_count' => $targetQuestionCount,
-                'is_mistake_book' => $isMistakeBook,
-                'strict_mistake_book' => $strictMistakeBook,
-                'assemble_type' => $assembleType,
-                'total_questions_param' => $totalQuestions
-            ]);
-
             $startTime = microtime(true);
             $selectedQuestions = $this->selectQuestionsByMastery(
                 $allQuestions,
@@ -1544,11 +1506,10 @@ class LearningAnalyticsService
             );
             $selectTime = (microtime(true) - $startTime) * 1000;
 
-            Log::info('题目筛选结果', [
+            Log::debug('题目筛选结果', [
                 'input_count' => count($allQuestions),
                 'selected_count' => count($selectedQuestions),
                 'target_count' => $targetQuestionCount,
-                'is_mistake_book' => $isMistakeBook,
                 'select_time_ms' => round($selectTime, 2)
             ]);
 
@@ -1565,24 +1526,7 @@ class LearningAnalyticsService
             $enableDistribution = $params['enable_difficulty_distribution'] ?? false;
             $isExcludedType = false; // 统一允许应用难度分布(错题本类型也允许)
 
-            // 【重要】添加详细的difficulty_category追踪日志
-            Log::info('LearningAnalyticsService: 难度分布检查开始', [
-                'input_difficulty_category' => $difficultyCategory,
-                'assemble_type' => $assembleType,
-                'enable_distribution' => $enableDistribution,
-                'is_excluded_type' => $isExcludedType,
-                'selected_questions_before' => count($selectedQuestions),
-                'params_keys' => array_keys($params)
-            ]);
-
             if ($enableDistribution && !$isExcludedType) {
-                Log::info('LearningAnalyticsService: 应用题型感知的难度分布', [
-                    'difficulty_category' => $difficultyCategory,
-                    'assemble_type' => $assembleType,
-                    'before_count' => count($selectedQuestions),
-                    'question_type_ratio' => $questionTypeRatio,
-                ]);
-
                 try {
                     $selectedQuestions = $this->applyTypeAwareDifficultyDistribution(
                         $allQuestions,
@@ -1592,7 +1536,7 @@ class LearningAnalyticsService
                         $questionTypeRatio
                     );
 
-                    Log::info('LearningAnalyticsService: 题型感知难度分布完成', [
+                    Log::debug('LearningAnalyticsService: 题型感知难度分布完成', [
                         'after_count' => count($selectedQuestions),
                         'type_breakdown' => $this->countByType($selectedQuestions),
                     ]);
@@ -1688,15 +1632,6 @@ class LearningAnalyticsService
                 }
             }
 
-            // 从本地数据库查询题目
-            Log::info('getQuestionsFromBank: 从本地数据库查询题目', [
-                'kp_codes' => $kpCodes,
-                'skills' => $skills,
-                'total_needed' => $totalNeeded,
-                'question_type_ratio' => $questionTypeRatio,
-                'note' => $totalNeeded > 0 ? '难度筛选由 QuestionLocalService 处理' : '不限制题库池大小,难度筛选由 QuestionLocalService 处理'
-            ]);
-
             $query = \App\Models\Question::query();
 
             // 【新增】只获取审核通过的题目(audit_status = 0 表示合格)
@@ -1705,7 +1640,6 @@ class LearningAnalyticsService
             // 按知识点筛选
             if (!empty($kpCodes)) {
                 $query->whereIn('kp_code', $kpCodes);
-                Log::info('应用知识点筛选', ['kp_codes' => $kpCodes]);
             }
 
             // 按学段筛选(题库 grade: 2=初中, 3=高中)
@@ -1713,10 +1647,6 @@ class LearningAnalyticsService
                 $stageGrade = $this->normalizeQuestionStageGrade((int) $grade);
                 if ($stageGrade !== null) {
                     $query->where('grade', $stageGrade);
-                    Log::info('应用学段筛选', [
-                        'input_grade' => $grade,
-                        'stage_grade' => $stageGrade
-                    ]);
                 }
             }
 
@@ -1727,19 +1657,16 @@ class LearningAnalyticsService
                         $q->orWhere('tags', 'like', "%{$skill}%");
                     }
                 });
-                Log::info('应用技能筛选', ['skills' => $skills]);
             }
 
             // 排除学生已做过的题目
             if (!empty($excludeQuestionIds)) {
                 $query->whereNotIn('id', $excludeQuestionIds);
-                Log::info('应用排除筛选', ['exclude_count' => count($excludeQuestionIds)]);
             }
 
             // 按题目分类筛选(如果指定了 question_category)
             if ($questionCategory !== null) {
                 $query->where('question_category', $questionCategory);
-                Log::info('应用题目分类筛选', ['question_category' => $questionCategory]);
             }
 
             // 筛选有解题思路的题目
@@ -1759,19 +1686,8 @@ class LearningAnalyticsService
             Log::info('getQuestionsFromBank: 查询完成', [
                 'raw_count' => $questions->count(),
                 'total_needed' => $totalNeeded,
-                'note' => '移除limit限制,获取所有符合条件的题目',
-                'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
-            ]);
-
-            // 【重要】添加更详细的日志来追踪题目筛选过程
-            Log::info('getQuestionsFromBank: 题目筛选过程详情', [
-                'database_query_count' => $questions->count(),
-                'kp_codes_filter' => $kpCodes,
-                'skills_filter' => $skills,
                 'exclude_count' => count($excludeQuestionIds),
-                'question_category' => $questionCategory,
-                'textbook_catalog_node_ids_filter' => $textbookCatalogNodeIds,
-                'note' => '将返回所有符合条件的题目,不限制数量'
+                'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
             ]);
 
             // 转换为标准格式
@@ -2061,23 +1977,13 @@ class LearningAnalyticsService
         array $weaknessFilter,
         int $assembleType  // 新增assembleType参数
     ): array {
-        Log::info('selectQuestionsByMastery 开始', [
-            'question_count' => count($questions),
-            'student_id' => $studentId,
-            'total_questions' => $totalQuestions,
-            'unique_kp_count' => count(array_unique(array_column($questions, 'kp_code'))),
-            'assemble_type' => $assembleType,
-            'note' => '追踪输入题目数量和知识点多样性'
-        ]);
-
         // 【修复】题目数量处理逻辑:无论题目数量多少,都要进行权重分配和筛选
         // 如果题目数量超过目标,则截取到目标数量
         // 如果题目数量不足,则使用所有题目,并记录警告
         if (count($questions) > $totalQuestions) {
-            Log::info('题目数量超过目标,进行截取', [
+            Log::debug('selectQuestionsByMastery: 题目数量超过目标,进行截取', [
                 'question_count' => count($questions),
-                'total_questions' => $totalQuestions,
-                'note' => '将按权重选择最合适的题目'
+                'total_questions' => $totalQuestions
             ]);
         } else {
             Log::warning('题目数量不足,将使用所有可用题目', [
@@ -2091,16 +1997,7 @@ class LearningAnalyticsService
         // 题目本身就有难度系数,QuestionLocalService的难度分布系统会处理题目分布
         // 不需要额外的难度筛选,让题目保持原始的难度分布
 
-        Log::info('跳过多余的难度筛选,使用题目原始难度分布', [
-            'question_count' => count($questions),
-            'note' => '由QuestionLocalService处理难度分布'
-        ]);
-
         // 1. 按知识点分组
-        Log::info('开始按知识点分组', [
-            'question_count' => count($questions)
-        ]);
-
         $groupStartTime = microtime(true);
         $questionsByKp = [];
         foreach ($questions as $question) {
@@ -2112,19 +2009,9 @@ class LearningAnalyticsService
         }
         $groupTime = (microtime(true) - $groupStartTime) * 1000;
 
-        Log::info('按知识点分组完成', [
-            'kp_count' => count($questionsByKp),
-            'group_time_ms' => round($groupTime, 2)
-        ]);
-
         // 2. 为每个知识点计算权重(用于题型内的题目排序)
         $kpWeights = [];
         $kpCodes = array_keys($questionsByKp);
-        Log::info('开始计算知识点权重(用于题型内排序)', [
-            'kp_count' => count($kpCodes),
-            'student_id' => $studentId
-        ]);
-
         $startTime = microtime(true);
         $allMastery = [];
 
@@ -2176,13 +2063,6 @@ class LearningAnalyticsService
             }
         }
 
-        $totalWeightTime = (microtime(true) - $startTime) * 1000;
-        Log::info('知识点权重计算完成', [
-            'total_kp_count' => count($kpCodes),
-            'total_weight_time_ms' => round($totalWeightTime, 2),
-            'avg_time_per_kp_ms' => count($kpCodes) > 0 ? round($totalWeightTime / count($kpCodes), 2) : 0
-        ]);
-
         // 3. 按题型分配题目数量(权重用于题型内排序)
         $selectedQuestions = [];
 
@@ -2253,19 +2133,12 @@ class LearningAnalyticsService
                 $remainder--;
             }
 
-            Log::info('selectQuestionsByMastery: 题型目标分配', [
+            Log::debug('selectQuestionsByMastery: 题型目标分配', [
                 'total_questions' => $totalQuestions,
-                'ratio' => $questionTypeRatio,
                 'targets' => $typeTargets,
             ]);
         }
 
-        Log::info('selectQuestionsByMastery: 知识点优先策略', [
-            'assemble_type' => $assembleType,
-            'use_knowledge_point_priority' => $useKnowledgePointPriority,
-            'note' => $useKnowledgePointPriority ? '摸底测试:需要均衡分配知识点' : '按比例分配题型'
-        ]);
-
         // 确保每种题型至少选1题
         foreach (['choice', 'fill', 'answer'] as $type) {
             if (empty($questionsByType[$type])) {
@@ -2292,11 +2165,10 @@ class LearningAnalyticsService
                 $typeKpDistribution[$kpCode]++;
             }
 
-            Log::info('摸底测试题型基础分配开始', [
+            Log::debug('摸底测试题型基础分配', [
                 'type' => $type,
-                'total_questions_in_type' => count($questionsByType[$type]),
-                'kp_distribution_in_type' => $typeKpDistribution,
-                'available_kp_count' => count($typeKpDistribution)
+                'total_in_type' => count($questionsByType[$type]),
+                'kp_count' => count($typeKpDistribution)
             ]);
 
             // 根据策略选择题目
@@ -2318,10 +2190,9 @@ class LearningAnalyticsService
                         break; // 只选1题
                     }
                 }
-                Log::info('摸底测试题型基础分配完成', [
+                Log::debug('摸底测试题型基础分配完成', [
                     'type' => $type,
-                    'selected_count' => $selectedInThisType,
-                    'note' => $selectedInThisType > 0 ? '成功选择' : '无可用知识点'
+                    'selected_count' => $selectedInThisType
                 ]);
             } else {
                 // 非摸底:按目标数量从该题型池中选题
@@ -2339,11 +2210,10 @@ class LearningAnalyticsService
                 }
                 $typeTargets[$type . '_actual'] = $taken;
 
-                Log::info('题型按比例分配', [
+                Log::debug('题型按比例分配', [
                     'type' => $type,
                     'target' => $target,
-                    'actual' => $taken,
-                    'available' => count($questionsByType[$type]),
+                    'actual' => $taken
                 ]);
             }
         }
@@ -2361,10 +2231,9 @@ class LearningAnalyticsService
             $preSortKpDistribution[$kpCode]++;
         }
 
-        Log::info('selectQuestionsByMastery: 排序前知识点分布', [
+        Log::debug('selectQuestionsByMastery: 排序前知识点分布', [
             'total_questions' => count($allQuestions),
-            'kp_distribution' => $preSortKpDistribution,
-            'note' => '用于对比排序后的分布变化'
+            'kp_distribution' => $preSortKpDistribution
         ]);
 
         // 【修复】添加随机因子到排序中,避免因ID排序导致知识点分布不均
@@ -2400,15 +2269,13 @@ class LearningAnalyticsService
             }
         }
 
-        Log::info('selectQuestionsByMastery: 排序后知识点分布', [
+        Log::debug('selectQuestionsByMastery: 排序后知识点分布', [
             'total_questions' => count($allQuestions),
-            'kp_distribution' => $postSortKpDistribution,
-            'first_50_kp_codes' => $postSortFirst50,
-            'note' => '观察排序是否均衡分布A07和A08'
+            'kp_distribution' => $postSortKpDistribution
         ]);
 
         // 【修复】摸底测试继续选择题目:记录详细的选题过程
-        Log::info('摸底测试继续选择题目开始', [
+        Log::debug('摸底测试继续选择题目开始', [
             'selected_count_after_basic' => count($selectedQuestions),
             'total_questions' => $totalQuestions,
             'selected_kp_codes' => array_keys($kpSelected),
@@ -2437,7 +2304,7 @@ class LearningAnalyticsService
                 }
             }
 
-            Log::info('摸底测试优先阶段完成', [
+            Log::debug('摸底测试优先阶段完成', [
                 'priority_selected_count' => $prioritySelectedCount,
                 'total_selected' => count($selectedQuestions),
                 'unique_kp_count' => count($kpSelected),
@@ -2474,7 +2341,7 @@ class LearningAnalyticsService
                     }
                 }
 
-                Log::info('摸底测试降级阶段完成', [
+                Log::debug('摸底测试降级阶段完成', [
                     'fallback_selected_count' => $fallbackSelectedCount,
                     'total_selected' => count($selectedQuestions),
                     'note' => '允许重复选择知识点补充数量'
@@ -2490,7 +2357,7 @@ class LearningAnalyticsService
                 // 按补充优先级:填空 > 解答 > 选择(避免解答题独占缺口)
                 $supplementOrder = ['fill', 'answer', 'choice'];
 
-                Log::info('题型缺口补充开始', [
+                Log::debug('题型缺口补充开始', [
                     'deficit' => $deficit,
                     'current_count' => $totalSelected,
                     'target' => $totalQuestions,
@@ -2510,7 +2377,7 @@ class LearningAnalyticsService
                     }
                 }
 
-                Log::info('题型缺口补充完成', [
+                Log::debug('题型缺口补充完成', [
                     'final_count' => count($selectedQuestions),
                     'target' => $totalQuestions,
                 ]);
@@ -2520,7 +2387,7 @@ class LearningAnalyticsService
         // 【移除】删除步骤3的多余逻辑
         // 前面的逻辑已经能选够题目,不需要额外的补充步骤
 
-        Log::info('selectQuestionsByMastery: 题目选择完成', [
+        Log::info('selectQuestionsByMastery 完成', [
             'total_questions' => $totalQuestions,
             'selected_count' => count($selectedQuestions),
             'success' => count($selectedQuestions) === $totalQuestions,
@@ -2546,7 +2413,7 @@ class LearningAnalyticsService
         }
 
         // 【重要】添加最终数量验证日志
-        Log::info('selectQuestionsByMastery: 最终题目数量验证', [
+        Log::debug('selectQuestionsByMastery: 最终题目数量验证', [
             'before_final_check' => count($selectedQuestions),
             'target_count' => $totalQuestions,
             'is_array' => is_array($selectedQuestions)
@@ -2554,7 +2421,7 @@ class LearningAnalyticsService
 
         // 【新增】最终知识点分布统计
         $finalKpDistribution = array_count_values(array_column($selectedQuestions, 'kp_code'));
-        Log::info('摸底测试最终知识点分布', [
+        Log::debug('摸底测试最终知识点分布', [
             'final_total_count' => count($selectedQuestions),
             'target_count' => $totalQuestions,
             'final_kp_distribution' => $finalKpDistribution,
@@ -2620,13 +2487,13 @@ class LearningAnalyticsService
                 }
             }
 
-            Log::info('selectQuestionsByMastery: 补充完成', [
+            Log::debug('selectQuestionsByMastery: 补充完成', [
                 'supplement_added' => $supplementCount,
                 'final_count_after_supplement' => count($finalQuestions)
             ]);
         }
 
-        Log::info('最终排查完成', [
+        Log::debug('最终排查完成', [
             'original_count' => count($selectedQuestions),
             'final_count' => count($finalQuestions),
             'duplicate_removed' => $duplicateCount,
@@ -2637,7 +2504,7 @@ class LearningAnalyticsService
         // 【重要】确保返回的题目数量正确
         $finalQuestions = array_slice($finalQuestions, 0, $totalQuestions);
 
-        Log::info('selectQuestionsByMastery: 返回结果', [
+        Log::debug('selectQuestionsByMastery: 返回结果', [
             'return_count' => count($finalQuestions),
             'target_count' => $totalQuestions,
             'success' => count($finalQuestions) === $totalQuestions
@@ -2675,7 +2542,7 @@ class LearningAnalyticsService
      */
     private function adjustQuestionsByRatio(array $questions, array $typeRatio, int $targetCount): array
     {
-        Log::info('开始题型配比调整', [
+        Log::debug('开始题型配比调整', [
             'input_questions' => count($questions),
             'target_count' => $targetCount,
             'type_ratio' => $typeRatio
@@ -2713,7 +2580,7 @@ class LearningAnalyticsService
         $availableCount = count($questions);
         $targets = $this->computeTypeTargets($targetCount, $typeRatio);
 
-        Log::info('题型配比调整前桶统计', [
+        Log::debug('题型配比调整前桶统计', [
             'target_count' => $targetCount,
             'available_count' => $availableCount,
             'targets' => $targets,
@@ -2787,7 +2654,7 @@ class LearningAnalyticsService
             }
         }
 
-        Log::info('题型配比调整完成', [
+        Log::debug('题型配比调整完成', [
             'target_count' => $targetCount,
             'targets' => $targets,
             'selected_counts' => $selectedCounts,

+ 2 - 2
app/Services/PdfMerger.php

@@ -29,13 +29,13 @@ class PdfMerger
     {
         // 优先检测pdfunite(更简单可靠)
         if ($this->commandExists('pdfunite')) {
-            Log::info('检测到pdfunite,将使用pdfunite进行PDF合并');
+            Log::debug('检测到pdfunite,将使用pdfunite进行PDF合并');
             return 'pdfunite';
         }
 
         // 备选qpdf
         if ($this->commandExists('qpdf')) {
-            Log::info('未检测到pdfunite,使用qpdf作为备选');
+            Log::debug('未检测到pdfunite,使用qpdf作为备选');
             return 'qpdf';
         }
 

+ 66 - 0
docs/log清理说明.md

@@ -0,0 +1,66 @@
+# Log 清理说明
+
+## 原则
+
+- **保留**:错误、警告、关键流程节点(组卷完成、试卷保存、PDF 生成成功)
+- **降为 DEBUG**:步骤明细、中间状态、每题/每请求的调试信息
+
+## 主要改动
+
+### LearningAnalyticsService
+
+| 原级别 | 现级别 | 说明 |
+|--------|--------|------|
+| INFO 应用知识点/学段/排除/题目分类筛选 | 移除 | 每步筛选冗余 |
+| INFO getQuestionsFromBank: 从本地数据库查询题目 | 移除 | 与「查询完成」重复 |
+| INFO 题目筛选过程详情 | 移除 | 与「查询完成」合并 |
+| INFO 开始/返回 getQuestionsFromBank | 合并为一条「完成」 | 减少重复 |
+| INFO 难度分布检查/应用/完成 | 部分移除,完成降为 DEBUG | 步骤过多 |
+| INFO selectQuestionsByMastery 各阶段 | 降为 DEBUG | 步骤明细 |
+| INFO 准备调用/开始调用 getQuestionsFromBank | 降为 DEBUG | 与主流程重复 |
+| INFO 薄弱点、教材出卷等 | 降为 DEBUG | 非关键路径 |
+
+### ExamTypeStrategy
+
+| 原级别 | 现级别 | 说明 |
+|--------|--------|------|
+| INFO 构建组卷参数、assembleType 映射 | 降为 DEBUG | 每次请求 |
+| INFO 知识点组卷/教材组卷参数构建 | 降为 DEBUG | 步骤明细 |
+| INFO 从 paper_questions 获取学生已做题目 | 降为 DEBUG | 与 exclude_count 重复 |
+| INFO 应用难度系数分布 | 降为 DEBUG | 步骤明细 |
+
+### ExamPdfController
+
+| 原级别 | 现级别 | 说明 |
+|--------|--------|------|
+| INFO paper_questions表原始数据(含 sample_questions) | 降为 DEBUG,仅 question_count | 避免泄露题目内容 |
+| INFO 题目类型判断 | 降为 DEBUG | 每题一条,量过大 |
+
+### PdfMerger
+
+| 原级别 | 现级别 | 说明 |
+|--------|--------|------|
+| INFO 检测到pdfunite | 降为 DEBUG | 每次 PDF 合并 |
+
+### IntelligentExamController
+
+| 原级别 | 现级别 | 说明 |
+|--------|--------|------|
+| INFO adjustQuestionScores、题目已按题型内难度排序 | 降为 DEBUG | 步骤明细 |
+
+## 保留的 INFO 日志(示例)
+
+- `generateIntelligentExam 开始`(精简版)
+- `getQuestionsFromBank: 查询完成`
+- `getQuestionsFromBank: 指定知识点题目不足,尝试智能补充`(WARNING)
+- `getSupplementaryQuestionsForGrade: 开始智能补充`
+- `getQuestionsFromBank: 智能补充完成`
+- `getQuestionsFromBank 完成`
+- `selectQuestionsByMastery 完成`
+- `试卷保存成功`
+- `generateUnifiedPdf 全部完成`
+- 所有 ERROR、关键 WARNING
+
+## 查看调试日志
+
+需要排查问题时,将 `.env` 中 `LOG_LEVEL=debug` 即可恢复上述 DEBUG 日志。