| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Cache;
- class StudentProgressService
- {
- /**
- * 获取单个学生的学习进度
- */
- public function getProgress(string $studentId): array
- {
- Log::info('获取学生学习进度', ['student_id' => $studentId]);
- // 获取知识图谱结构
- $graphData = $this->getKnowledgeGraphStructure();
- $leafKpCodes = $graphData['leafKpCodes'];
- $leafKpCodesSet = $graphData['leafKpCodesSet'];
- $kpCodes = $graphData['kpCodes'];
- $maxChildScore = count($leafKpCodes) * 1.0;
- // 获取学生掌握度数据
- $mergedData = $this->getStudentMasteryData($studentId);
- // 如果没有数据,返回空结构(和批量接口行为一致)
- if (empty($mergedData)) {
- return [
- 'success' => true,
- 'data' => [
- 'student_id' => $studentId,
- 'learning_progress' => 0,
- 'learning_progress_percentage' => 0,
- 'mastered_child_count' => 0,
- 'total_child_count' => count($leafKpCodes),
- 'child_mastery_sum' => 0,
- 'has_data' => false,
- 'calculated_at' => now()->toISOString()
- ],
- 'message' => '该学生暂无学习数据'
- ];
- }
- // 筛选叶子节点
- $childMasteryData = collect($mergedData)->filter(function ($item) use ($leafKpCodesSet) {
- return isset($leafKpCodesSet[$item['kp_code']]);
- });
- // 如果没有子知识点数据,也返回空结构
- if ($childMasteryData->isEmpty()) {
- return [
- 'success' => true,
- 'data' => [
- 'student_id' => $studentId,
- 'learning_progress' => 0,
- 'learning_progress_percentage' => 0,
- 'mastered_child_count' => 0,
- 'total_child_count' => count($leafKpCodes),
- 'child_mastery_sum' => 0,
- 'has_data' => false,
- 'calculated_at' => now()->toISOString()
- ],
- 'message' => '该学生暂无子知识点学习数据'
- ];
- }
- // 计算学习进度
- $totalChildMasterySum = $childMasteryData->sum('mastery_level');
- $learningProgress = $maxChildScore > 0 ? ($totalChildMasterySum / $maxChildScore) : 0.0;
- // 统计信息
- $statistics = [
- 'total_knowledge_points' => count($kpCodes),
- 'child_knowledge_points' => count($leafKpCodes),
- 'student_mastered_child_count' => $childMasteryData->count(),
- 'student_mastered_child_percentage' => round(($childMasteryData->count() / count($leafKpCodes)) * 100, 2),
- 'child_mastery_sum' => round($totalChildMasterySum, 4),
- 'max_child_score' => round($maxChildScore, 4),
- 'learning_progress_percentage' => round($learningProgress * 100, 2),
- 'data_source' => implode(', ', collect($mergedData)->pluck('source_table')->unique()->toArray()),
- 'child_mastery_max' => round($childMasteryData->max('mastery_level'), 4),
- 'child_mastery_min' => round($childMasteryData->min('mastery_level'), 4),
- 'child_mastery_avg' => round($childMasteryData->avg('mastery_level'), 4),
- ];
- Log::info('学生学习进度计算成功', [
- 'student_id' => $studentId,
- 'learning_progress' => $learningProgress,
- ]);
- return [
- 'success' => true,
- 'data' => [
- 'student_id' => $studentId,
- 'learning_progress' => round($learningProgress, 6),
- 'learning_progress_percentage' => round($learningProgress * 100, 2),
- 'mastered_child_count' => $childMasteryData->count(),
- 'total_child_count' => count($leafKpCodes),
- 'child_mastery_sum' => round($totalChildMasterySum, 4),
- 'has_data' => true,
- 'child_knowledge_points' => $childMasteryData->values()->toArray(),
- 'statistics' => $statistics,
- 'calculated_at' => now()->toISOString()
- ],
- 'message' => '学习进度计算成功'
- ];
- }
- /**
- * 批量获取学生学习进度
- */
- public function getBatchProgress(array $studentIds): array
- {
- if (empty($studentIds)) {
- return [
- 'success' => false,
- 'error' => 'student_ids 不能为空'
- ];
- }
- if (count($studentIds) > 100) {
- return [
- 'success' => false,
- 'error' => '单次最多查询 100 个学生'
- ];
- }
- Log::info('批量获取学生学习进度', ['count' => count($studentIds)]);
- // 获取知识图谱结构(所有学生共用)
- $graphData = $this->getKnowledgeGraphStructure();
- $leafKpCodes = $graphData['leafKpCodes'];
- $leafKpCodesSet = $graphData['leafKpCodesSet'];
- $maxChildScore = count($leafKpCodes) * 1.0;
- // 批量获取掌握度数据
- $batchData = $this->getBatchStudentMasteryData($studentIds);
- $detailedData = $batchData['detailed'];
- $simpleData = $batchData['simple'];
- // 为每个学生计算学习进度
- $results = [];
- foreach ($studentIds as $studentId) {
- $studentId = (string) $studentId;
- $mergedData = [];
- // 从 student_knowledge_mastery 获取
- if (isset($detailedData[$studentId])) {
- foreach ($detailedData[$studentId] as $item) {
- $mergedData[$item->kp_code] = [
- 'kp_code' => $item->kp_code,
- 'mastery_level' => (float) $item->mastery_level,
- ];
- }
- }
- // 从 student_mastery 补充或更新
- if (isset($simpleData[$studentId])) {
- foreach ($simpleData[$studentId] as $item) {
- $kpCode = $item->kp_code;
- $masteryLevel = (float) $item->mastery;
- if (isset($mergedData[$kpCode])) {
- if ($masteryLevel > $mergedData[$kpCode]['mastery_level']) {
- $mergedData[$kpCode]['mastery_level'] = $masteryLevel;
- }
- } else {
- $mergedData[$kpCode] = [
- 'kp_code' => $kpCode,
- 'mastery_level' => $masteryLevel,
- ];
- }
- }
- }
- // 计算学习进度
- if (empty($mergedData)) {
- $results[$studentId] = [
- 'student_id' => $studentId,
- 'learning_progress' => 0,
- 'learning_progress_percentage' => 0,
- 'mastered_child_count' => 0,
- 'total_child_count' => count($leafKpCodes),
- 'has_data' => false,
- ];
- continue;
- }
- // 筛选叶子节点并计算
- $childMasterySum = 0;
- $masteredChildCount = 0;
- foreach ($mergedData as $item) {
- if (isset($leafKpCodesSet[$item['kp_code']])) {
- $childMasterySum += $item['mastery_level'];
- $masteredChildCount++;
- }
- }
- $learningProgress = $maxChildScore > 0 ? ($childMasterySum / $maxChildScore) : 0.0;
- $results[$studentId] = [
- 'student_id' => $studentId,
- 'learning_progress' => round($learningProgress, 6),
- 'learning_progress_percentage' => round($learningProgress * 100, 2),
- 'mastered_child_count' => $masteredChildCount,
- 'total_child_count' => count($leafKpCodes),
- 'child_mastery_sum' => round($childMasterySum, 4),
- 'has_data' => true,
- ];
- }
- Log::info('批量学习进度计算完成', ['count' => count($results)]);
- return [
- 'success' => true,
- 'data' => $results,
- 'meta' => [
- 'total_students' => count($studentIds),
- 'total_child_knowledge_points' => count($leafKpCodes),
- 'calculated_at' => now()->toISOString(),
- ]
- ];
- }
- /**
- * 获取知识图谱结构(缓存 5 分钟)
- */
- private function getKnowledgeGraphStructure(): array
- {
- return Cache::remember('knowledge_graph_structure', 300, function () {
- $allKps = DB::connection('remote_mysql')
- ->table('knowledge_points')
- ->select(['kp_code', 'parent_kp_code'])
- ->get();
- $kpCodes = $allKps->pluck('kp_code')->toArray();
- $parentCodes = $allKps->whereNotNull('parent_kp_code')->pluck('parent_kp_code')->unique()->toArray();
- $leafKpCodes = array_values(array_diff($kpCodes, $parentCodes));
- return [
- 'kpCodes' => $kpCodes,
- 'leafKpCodes' => $leafKpCodes,
- 'leafKpCodesSet' => array_flip($leafKpCodes),
- ];
- });
- }
- /**
- * 获取单个学生的掌握度数据
- */
- private function getStudentMasteryData(string $studentId): array
- {
- $mergedData = [];
- try {
- $detailedData = DB::connection('remote_mysql')
- ->table('student_knowledge_mastery')
- ->where('student_id', $studentId)
- ->select(['kp_code', 'mastery_level', 'total_attempts', 'correct_attempts', 'updated_at'])
- ->get();
- foreach ($detailedData as $item) {
- $mergedData[$item->kp_code] = [
- 'kp_code' => $item->kp_code,
- 'mastery_level' => (float) $item->mastery_level,
- 'total_attempts' => $item->total_attempts,
- 'correct_attempts' => $item->correct_attempts,
- 'source_table' => 'student_knowledge_mastery',
- 'updated_at' => $item->updated_at
- ];
- }
- } catch (\Exception $e) {
- Log::warning('从 student_knowledge_mastery 获取数据失败', ['error' => $e->getMessage()]);
- }
- try {
- $simpleData = DB::connection('remote_mysql')
- ->table('student_mastery')
- ->where('student_id', $studentId)
- ->select(['kp as kp_code', 'mastery', 'attempts as total_attempts', 'correct as correct_attempts', 'updated_at'])
- ->get();
- foreach ($simpleData as $item) {
- $kpCode = $item->kp_code;
- $masteryLevel = (float) $item->mastery;
- if (isset($mergedData[$kpCode])) {
- if ($masteryLevel > $mergedData[$kpCode]['mastery_level']) {
- $mergedData[$kpCode]['mastery_level'] = $masteryLevel;
- $mergedData[$kpCode]['source_table'] = 'student_mastery (updated)';
- }
- } else {
- $mergedData[$kpCode] = [
- 'kp_code' => $kpCode,
- 'mastery_level' => $masteryLevel,
- 'total_attempts' => $item->total_attempts ?? 0,
- 'correct_attempts' => $item->correct_attempts ?? 0,
- 'source_table' => 'student_mastery',
- 'updated_at' => $item->updated_at ?? null
- ];
- }
- }
- } catch (\Exception $e) {
- Log::warning('从 student_mastery 获取数据失败', ['error' => $e->getMessage()]);
- }
- return $mergedData;
- }
- /**
- * 批量获取学生掌握度数据
- */
- private function getBatchStudentMasteryData(array $studentIds): array
- {
- $detailedData = DB::connection('remote_mysql')
- ->table('student_knowledge_mastery')
- ->whereIn('student_id', $studentIds)
- ->select(['student_id', 'kp_code', 'mastery_level'])
- ->get()
- ->groupBy('student_id');
- $simpleData = DB::connection('remote_mysql')
- ->table('student_mastery')
- ->whereIn('student_id', $studentIds)
- ->select(['student_id', 'kp as kp_code', 'mastery'])
- ->get()
- ->groupBy('student_id');
- return [
- 'detailed' => $detailedData->toArray(),
- 'simple' => $simpleData->toArray(),
- ];
- }
- }
|