|
|
@@ -562,9 +562,13 @@ class MasteryCalculator
|
|
|
|
|
|
/**
|
|
|
* 【增强】获取学生所有知识点的掌握度概览(支持父节点计算)
|
|
|
+ * 【优化】预加载所有数据到内存,避免 N+1 查询问题
|
|
|
*/
|
|
|
public function getStudentMasteryOverviewWithHierarchy(string $studentId): array
|
|
|
{
|
|
|
+ $startTime = microtime(true);
|
|
|
+
|
|
|
+ // 1. 一次性查询学生所有知识点的掌握度
|
|
|
$masteryList = DB::table('student_knowledge_mastery')
|
|
|
->where('student_id', $studentId)
|
|
|
->get();
|
|
|
@@ -578,14 +582,20 @@ class MasteryCalculator
|
|
|
'weak_knowledge_points' => 0,
|
|
|
'weak_knowledge_points_list' => [],
|
|
|
'details' => [],
|
|
|
- 'parent_mastery_levels' => [], // 新增:父节点掌握度
|
|
|
+ 'parent_mastery_levels' => [],
|
|
|
];
|
|
|
}
|
|
|
|
|
|
$masteryArray = $masteryList->toArray();
|
|
|
|
|
|
+ // 构建掌握度映射表(kp_code => mastery_level)
|
|
|
+ $masteryMap = [];
|
|
|
+ foreach ($masteryArray as $item) {
|
|
|
+ $masteryMap[$item->kp_code] = floatval($item->mastery_level);
|
|
|
+ }
|
|
|
+
|
|
|
$total = count($masteryArray);
|
|
|
- $average = $masteryArray ? array_sum(array_column($masteryArray, 'mastery_level')) / $total : 0;
|
|
|
+ $average = array_sum($masteryMap) / $total;
|
|
|
|
|
|
$mastered = [];
|
|
|
$good = [];
|
|
|
@@ -602,19 +612,55 @@ class MasteryCalculator
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // 【新功能】计算父节点掌握度
|
|
|
- $parentMasteryLevels = [];
|
|
|
- $parentKpCodes = DB::table('knowledge_points')
|
|
|
+ // 2. 一次性查询所有知识点的层级关系
|
|
|
+ $allKpRelations = DB::table('knowledge_points')
|
|
|
->whereNotNull('parent_kp_code')
|
|
|
- ->distinct()
|
|
|
- ->pluck('parent_kp_code')
|
|
|
- ->toArray();
|
|
|
+ ->select('kp_code', 'parent_kp_code')
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ // 构建父子关系映射(parent_kp_code => [child_kp_codes])
|
|
|
+ $childrenMap = [];
|
|
|
+ $allParentKpCodes = [];
|
|
|
+ foreach ($allKpRelations as $relation) {
|
|
|
+ $parentCode = $relation->parent_kp_code;
|
|
|
+ $childCode = $relation->kp_code;
|
|
|
|
|
|
- foreach ($parentKpCodes as $parentKpCode) {
|
|
|
- $parentMastery = $this->calculateParentMastery($studentId, $parentKpCode);
|
|
|
- $parentMasteryLevels[$parentKpCode] = $parentMastery;
|
|
|
+ if (!isset($childrenMap[$parentCode])) {
|
|
|
+ $childrenMap[$parentCode] = [];
|
|
|
+ }
|
|
|
+ $childrenMap[$parentCode][] = $childCode;
|
|
|
+ $allParentKpCodes[$parentCode] = true;
|
|
|
}
|
|
|
|
|
|
+ Log::debug('MasteryCalculator: 预加载数据完成', [
|
|
|
+ 'student_id' => $studentId,
|
|
|
+ 'mastery_count' => count($masteryMap),
|
|
|
+ 'parent_count' => count($allParentKpCodes),
|
|
|
+ 'time_ms' => round((microtime(true) - $startTime) * 1000, 2)
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 3. 在内存中计算所有父节点的掌握度(不再查询数据库)
|
|
|
+ $parentMasteryLevels = [];
|
|
|
+ foreach (array_keys($allParentKpCodes) as $parentKpCode) {
|
|
|
+ $parentMastery = $this->calculateParentMasteryInMemory(
|
|
|
+ $parentKpCode,
|
|
|
+ $childrenMap,
|
|
|
+ $masteryMap,
|
|
|
+ 1,
|
|
|
+ 3
|
|
|
+ );
|
|
|
+ if ($parentMastery > 0) {
|
|
|
+ $parentMasteryLevels[$parentKpCode] = $parentMastery;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Log::info('MasteryCalculator: getStudentMasteryOverviewWithHierarchy 完成', [
|
|
|
+ 'student_id' => $studentId,
|
|
|
+ 'total_kp' => $total,
|
|
|
+ 'parent_mastery_count' => count($parentMasteryLevels),
|
|
|
+ 'total_time_ms' => round((microtime(true) - $startTime) * 1000, 2)
|
|
|
+ ]);
|
|
|
+
|
|
|
return [
|
|
|
'total_knowledge_points' => $total,
|
|
|
'average_mastery_level' => round($average, 4),
|
|
|
@@ -623,7 +669,65 @@ class MasteryCalculator
|
|
|
'weak_knowledge_points' => count($weak),
|
|
|
'weak_knowledge_points_list' => $weak,
|
|
|
'details' => $masteryArray,
|
|
|
- 'parent_mastery_levels' => $parentMasteryLevels, // 新增:父节点掌握度
|
|
|
+ 'parent_mastery_levels' => $parentMasteryLevels,
|
|
|
];
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 【优化】在内存中递归计算父节点掌握度(不查询数据库)
|
|
|
+ *
|
|
|
+ * @param string $parentKpCode 父节点编码
|
|
|
+ * @param array $childrenMap 父子关系映射(parent => [children])
|
|
|
+ * @param array $masteryMap 掌握度映射(kp_code => mastery_level)
|
|
|
+ * @param int $currentDepth 当前递归深度
|
|
|
+ * @param int $maxDepth 最大递归深度
|
|
|
+ * @return float 父节点掌握度
|
|
|
+ */
|
|
|
+ private function calculateParentMasteryInMemory(
|
|
|
+ string $parentKpCode,
|
|
|
+ array $childrenMap,
|
|
|
+ array $masteryMap,
|
|
|
+ int $currentDepth,
|
|
|
+ int $maxDepth
|
|
|
+ ): float {
|
|
|
+ // 防止无限递归
|
|
|
+ if ($currentDepth > $maxDepth) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取子节点
|
|
|
+ $childKpCodes = $childrenMap[$parentKpCode] ?? [];
|
|
|
+ if (empty($childKpCodes)) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ $masteryLevels = [];
|
|
|
+ foreach ($childKpCodes as $childKpCode) {
|
|
|
+ // 如果子节点也是父节点,递归计算
|
|
|
+ if (isset($childrenMap[$childKpCode])) {
|
|
|
+ $childMastery = $this->calculateParentMasteryInMemory(
|
|
|
+ $childKpCode,
|
|
|
+ $childrenMap,
|
|
|
+ $masteryMap,
|
|
|
+ $currentDepth + 1,
|
|
|
+ $maxDepth
|
|
|
+ );
|
|
|
+ if ($childMastery > 0) {
|
|
|
+ $masteryLevels[] = $childMastery;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果子节点有掌握度数据,使用它
|
|
|
+ if (isset($masteryMap[$childKpCode])) {
|
|
|
+ $masteryLevels[] = $masteryMap[$childKpCode];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($masteryLevels)) {
|
|
|
+ return 0.0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算平均值
|
|
|
+ return round(array_sum($masteryLevels) / count($masteryLevels), 4);
|
|
|
+ }
|
|
|
}
|