Explorar o código

perform: 继续优化api/exam-answer-analysis接口

大侠咬超人 hai 1 semana
pai
achega
a2d34b3bac
Modificáronse 1 ficheiros con 56 adicións e 78 borrados
  1. 56 78
      app/Services/ExamAnswerAnalysisService.php

+ 56 - 78
app/Services/ExamAnswerAnalysisService.php

@@ -794,38 +794,22 @@ class ExamAnswerAnalysisService
     }
 
     /**
-     * 【公司要求】计算并更新父节点掌握度(支持多级递归)
-     *
-     * 公司要求:
-     * 1. 查询结构:通过 knowledge_points 表获取知识点的层级关系(parent_kp_code)
-     * 2. 平均值计算:如果一个知识点是父节点,它的掌握度不再从数据库直接读,
-     *    而是实时计算其下所有子节点掌握度的算术平均数
-     * 3. 多级递归:支持多级结构,能够从最底层逐级向上求平均
-     *
-     * 递归计算流程:
-     * - 从最底层节点开始,逐级向上递归计算父节点掌握度
-     * - 父节点掌握度 = 所有子节点掌握度的算术平均数
-     * - 支持任意层级结构,递归深度限制为10级
-     *
-     * @param string $studentId 学生ID
-     * @param array $childKpCodes 当前层的子节点编码列表
-     * @param int $maxDepth 最大递归深度(防止无限递归,默认10级)
-     * @return array 返回所有层级的父节点掌握度数据
+     * 更新父节点掌握度(优化版:批量查询+批量upsert)
+     * 父节点掌握度 = 所有子节点掌握度的算术平均数,支持多级递归
      */
     private function updateParentMasteryLevels(string $studentId, array $childKpCodes, int $maxDepth = 2): array
     {
         $allParentMasteryData = [];
         $currentLevelChildCodes = $childKpCodes;
-        $processedNodes = new \ArrayObject(); // 防止重复处理
+        $processedNodes = new \ArrayObject();
+        $now = now();
 
         try {
-            // 【公司要求】多级递归:逐级向上递归计算,支持多级结构
             for ($level = 0; $level < $maxDepth; $level++) {
                 if (empty($currentLevelChildCodes)) {
-                    break; // 没有更多父节点,退出循环
+                    break;
                 }
 
-                // 【公司要求】查询结构:通过 knowledge_points 表获取知识点的层级关系(parent_kp_code)
                 // 获取当前层子节点的直接父节点
                 $parentKpCodes = DB::connection('mysql')
                     ->table('knowledge_points')
@@ -836,49 +820,54 @@ class ExamAnswerAnalysisService
                     ->toArray();
 
                 if (empty($parentKpCodes)) {
-                    Log::debug("第{$level}层没有更多父节点,递归结束");
                     break;
                 }
 
-                $currentLevelParentData = [];
-
-                foreach ($parentKpCodes as $parentKpCode) {
-                    // 防止重复处理同一节点
-                    if (isset($processedNodes[$parentKpCode])) {
-                        continue;
+                // 过滤已处理的节点
+                $parentKpCodesToProcess = [];
+                foreach ($parentKpCodes as $code) {
+                    if (!isset($processedNodes[$code])) {
+                        $processedNodes[$code] = true;
+                        $parentKpCodesToProcess[] = $code;
                     }
-                    $processedNodes[$parentKpCode] = true;
+                }
 
-                    // 【公司要求】平均值计算:使用MasteryCalculator计算父节点掌握度(实时计算)
-                    // 父节点掌握度 = 所有子节点掌握度的算术平均数
-                    $parentMastery = $this->masteryCalculator->calculateParentMastery($studentId, $parentKpCode);
+                if (empty($parentKpCodesToProcess)) {
+                    $currentLevelChildCodes = $parentKpCodes;
+                    continue;
+                }
 
-                    // 获取父节点历史数据
-                    $historyParentMastery = DB::connection('mysql')
-                        ->table('student_knowledge_mastery')
-                        ->where('student_id', $studentId)
-                        ->where('kp_code', $parentKpCode)
-                        ->first();
+                // 【优化】批量查询历史掌握度(1次查询代替N次)
+                $historyRecords = DB::connection('mysql')
+                    ->table('student_knowledge_mastery')
+                    ->where('student_id', $studentId)
+                    ->whereIn('kp_code', $parentKpCodesToProcess)
+                    ->get()
+                    ->keyBy('kp_code');
+
+                // 计算每个父节点的掌握度并收集upsert数据
+                $upsertData = [];
+                $currentLevelParentData = [];
 
-                    $previousMastery = $historyParentMastery->mastery_level ?? 0.0;
+                foreach ($parentKpCodesToProcess as $parentKpCode) {
+                    // 计算父节点掌握度(这个暂时保留,后续可优化)
+                    $parentMastery = $this->masteryCalculator->calculateParentMastery($studentId, $parentKpCode);
 
-                    // 【自己要求】保存父节点掌握度到数据库(只保存核心数据)
-                    DB::connection('mysql')
-                        ->table('student_knowledge_mastery')
-                        ->updateOrInsert(
-                            ['student_id' => $studentId, 'kp_code' => $parentKpCode],
-                            [
-                                'mastery_level' => $parentMastery,
-                                'confidence_level' => 0.0, // 不再保存置信度
-                                'total_attempts' => 1, // 父节点不记录答题次数
-                                'correct_attempts' => 0, // 父节点不记录正确次数
-                                'mastery_trend' => 'stable', // 统一设为stable
-                                'last_mastery_update' => now(),
-                                'updated_at' => now(),
-                            ]
-                        );
+                    $historyMastery = $historyRecords->get($parentKpCode);
+                    $previousMastery = $historyMastery->mastery_level ?? 0.0;
+
+                    $upsertData[] = [
+                        'student_id' => $studentId,
+                        'kp_code' => $parentKpCode,
+                        'mastery_level' => $parentMastery,
+                        'confidence_level' => 0.0,
+                        'total_attempts' => 1,
+                        'correct_attempts' => 0,
+                        'mastery_trend' => 'stable',
+                        'last_mastery_update' => $now,
+                        'updated_at' => $now,
+                    ];
 
-                    // 【公司要求】记录当前层级数据(移除置信度)
                     $currentLevelParentData[$parentKpCode] = [
                         'kp_id' => $parentKpCode,
                         'current_mastery' => $parentMastery,
@@ -886,48 +875,37 @@ class ExamAnswerAnalysisService
                         'change' => $parentMastery - $previousMastery,
                         'weight' => 1,
                         'is_parent' => true,
-                        'level' => $level + 1 // 记录层级深度
+                        'level' => $level + 1
                     ];
+                }
 
-                    Log::debug("第" . ($level + 1) . "层父节点掌握度已更新", [
-                        'student_id' => $studentId,
-                        'parent_kp_code' => $parentKpCode,
-                        'parent_mastery' => $parentMastery,
-                        'child_nodes_count' => count($currentLevelChildCodes)
-                    ]);
+                // 【优化】批量upsert(1次查询代替N次updateOrInsert)
+                if (!empty($upsertData)) {
+                    DB::connection('mysql')
+                        ->table('student_knowledge_mastery')
+                        ->upsert(
+                            $upsertData,
+                            ['student_id', 'kp_code'],
+                            ['mastery_level', 'confidence_level', 'total_attempts', 'correct_attempts', 'mastery_trend', 'last_mastery_update', 'updated_at']
+                        );
                 }
 
-                // 合并到总结果中
                 $allParentMasteryData = array_merge($allParentMasteryData, $currentLevelParentData);
-
-                // 【公司要求】多级递归:准备下一轮递归,从最底层逐级向上求平均
-                // 当前层的父节点作为下一层的子节点
                 $currentLevelChildCodes = $parentKpCodes;
             }
 
             if (!empty($allParentMasteryData)) {
-                $totalParentCount = count($allParentMasteryData);
-                $maxLevel = 0;
-                foreach ($allParentMasteryData as $data) {
-                    $maxLevel = max($maxLevel, $data['level']);
-                }
-
                 Log::info('多级父节点掌握度计算完成', [
                     'student_id' => $studentId,
                     'child_count' => count($childKpCodes),
-                    'total_parent_count' => $totalParentCount,
-                    'max_level' => $maxLevel,
-                    'all_parent_kp_codes' => array_keys($allParentMasteryData)
+                    'total_parent_count' => count($allParentMasteryData),
                 ]);
             }
 
         } catch (\Exception $e) {
             Log::error('计算多级父节点掌握度失败', [
                 'student_id' => $studentId,
-                'child_kp_codes' => $childKpCodes,
-                'current_level' => $currentLevelChildCodes,
                 'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
             ]);
         }