| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858 |
- <?php
- namespace App\Services;
- use Illuminate\Support\Facades\Http;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\DB;
- class LearningAnalyticsService
- {
- protected string $baseUrl;
- protected int $timeout = 10;
- public function __construct()
- {
- $this->baseUrl = config('services.learning_analytics.url', env('LEARNING_ANALYTICS_API_BASE', 'http://localhost:5016'));
- }
- /**
- * 获取学生掌握度
- */
- public function getStudentMastery(string $studentId, string $kpCode = null): array
- {
- try {
- $endpoint = $kpCode
- ? "/api/v1/mastery/student/{$studentId}/kp/{$kpCode}"
- : "/api/v1/mastery/student/{$studentId}";
- $response = Http::timeout($this->timeout)->get($this->baseUrl . $endpoint);
-
- if ($response->successful()) {
- return $response->json();
- }
- Log::error('LearningAnalytics API Error', [
- 'endpoint' => $endpoint,
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- return [
- 'error' => true,
- 'message' => 'Failed to fetch mastery data'
- ];
- } catch (\Exception $e) {
- Log::error('LearningAnalytics Service Exception', [
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString()
- ]);
- return [
- 'error' => true,
- 'message' => $e->getMessage()
- ];
- }
- }
- /**
- * 更新学生掌握度
- */
- public function updateMastery(array $data): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->post($this->baseUrl . '/api/v1/mastery/student/' . $data['student_id'] . '/update', $data);
- if ($response->successful()) {
- return $response->json();
- }
- Log::error('LearningAnalytics Update Error', [
- 'data' => $data,
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- return [
- 'error' => true,
- 'message' => 'Failed to update mastery'
- ];
- } catch (\Exception $e) {
- Log::error('LearningAnalytics Update Exception', [
- 'error' => $e->getMessage(),
- 'data' => $data
- ]);
- return [
- 'error' => true,
- 'message' => $e->getMessage()
- ];
- }
- }
- /**
- * 获取老师名下的所有学生
- */
- public function getTeacherStudents(string $teacherId): array
- {
- try {
- // 从本地MySQL获取学生
- $students = DB::connection('remote_mysql')
- ->table('students as s')
- ->leftJoin('users as u', 's.student_id', '=', 'u.user_id')
- ->where('s.teacher_id', $teacherId)
- ->select(
- 's.student_id',
- 's.name',
- 's.grade',
- 's.class_name',
- 'u.username',
- 'u.email'
- )
- ->get()
- ->toArray();
- return $students;
- } catch (\Exception $e) {
- Log::error('Get Teacher Students Error', [
- 'teacher_id' => $teacherId,
- 'error' => $e->getMessage()
- ]);
- return [];
- }
- }
- /**
- * 获取学生学习分析
- */
- public function getStudentAnalysis(string $studentId): array
- {
- // 从LearningAnalytics获取掌握度
- $masteryData = $this->getStudentMastery($studentId);
-
- // 从MySQL获取练习历史
- $exercises = DB::connection('remote_mysql')
- ->table('student_exercises')
- ->where('student_id', $studentId)
- ->orderBy('created_at', 'desc')
- ->limit(50)
- ->get()
- ->toArray();
- // 从MySQL获取掌握度记录
- $masteryRecords = DB::connection('remote_mysql')
- ->table('student_mastery')
- ->where('student_id', $studentId)
- ->get()
- ->toArray();
- return [
- 'student_id' => $studentId,
- 'mastery_from_la' => $masteryData,
- 'exercises' => $exercises,
- 'mastery_records' => $masteryRecords,
- 'total_exercises' => count($exercises),
- 'total_mastery_records' => count($masteryRecords),
- ];
- }
- /**
- * 生成学习测试数据
- */
- public function generateLearningData(string $studentId, array $params): array
- {
- $results = [];
-
- foreach ($params as $param) {
- $data = [
- 'student_id' => $studentId,
- 'kp_code' => $param['kp_code'],
- 'is_correct' => $param['is_correct'],
- 'time_spent_seconds' => $param['time_spent_seconds'] ?? 120,
- 'difficulty_level' => $param['difficulty_level'] ?? 3,
- ];
-
- $result = $this->updateMastery($data);
- $results[] = $result;
- }
-
- return $results;
- }
- /**
- * 获取学习推荐
- */
- public function getLearningRecommendations(string $studentId): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . "/api/v1/learning-path/student/{$studentId}/recommend");
- if ($response->successful()) {
- return $response->json();
- }
- return ['error' => true, 'message' => 'Failed to fetch recommendations'];
- } catch (\Exception $e) {
- return ['error' => true, 'message' => $e->getMessage()];
- }
- }
- /**
- * 获取知识点列表(从知识图谱API)
- */
- public function getKnowledgePoints(): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . '/knowledge-points/');
- if ($response->successful()) {
- return $response->json()['data'] ?? [];
- }
- return [];
- } catch (\Exception $e) {
- Log::error('LearningAnalytics Knowledge Points Error', [
- 'error' => $e->getMessage()
- ]);
- return [];
- }
- }
- /**
- * 获取学生技能熟练度
- */
- public function getStudentSkillProficiency(string $studentId): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . "/api/v1/skill/proficiency/student/{$studentId}");
- if ($response->successful()) {
- return $response->json();
- }
- Log::error('LearningAnalytics Skill Proficiency Error', [
- 'student_id' => $studentId,
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- return [
- 'error' => true,
- 'message' => 'Failed to fetch skill proficiency'
- ];
- } catch (\Exception $e) {
- Log::error('LearningAnalytics Skill Proficiency Exception', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'error' => true,
- 'message' => $e->getMessage()
- ];
- }
- }
- /**
- * 获取学生掌握度列表(别名方法)
- */
- public function getStudentMasteryList(string $studentId): array
- {
- return $this->getStudentMastery($studentId);
- }
- /**
- * 获取知识点依赖关系
- */
- public function getKnowledgeDependencies(): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . '/knowledge-dependencies/');
- if ($response->successful()) {
- return $response->json()['data'] ?? [];
- }
- return [];
- } catch (\Exception $e) {
- Log::error('LearningAnalytics Knowledge Dependencies Error', [
- 'error' => $e->getMessage()
- ]);
- return [];
- }
- }
- /**
- * 提交学生答题记录
- */
- public function submitAttempt(string $studentId, array $attemptData): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->post($this->baseUrl . "/api/v1/attempts/student/{$studentId}", $attemptData);
- if ($response->successful()) {
- return $response->json();
- }
- Log::error('Submit Attempt Error', [
- 'student_id' => $studentId,
- 'data' => $attemptData,
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- return [
- 'error' => true,
- 'message' => 'Failed to submit attempt'
- ];
- } catch (\Exception $e) {
- Log::error('Submit Attempt Exception', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage(),
- 'data' => $attemptData
- ]);
- return [
- 'error' => true,
- 'message' => $e->getMessage()
- ];
- }
- }
- /**
- * 检查服务健康状态
- */
- public function checkHealth(): bool
- {
- try {
- $response = Http::timeout(5)->get($this->baseUrl . '/health');
- return $response->successful();
- } catch (\Exception $e) {
- return false;
- }
- }
- /**
- * 获取学生掌握度概览
- */
- public function getStudentMasteryOverview(string $studentId): array
- {
- try {
- $mastery = $this->getStudentMastery($studentId);
- if (isset($mastery['error'])) {
- return [
- 'total_knowledge_points' => 0,
- 'average_mastery_level' => 0,
- 'mastered_knowledge_points' => 0,
- 'good_knowledge_points' => 0,
- 'weak_knowledge_points' => 0,
- 'weak_knowledge_points_list' => [],
- 'details' => []
- ];
- }
- $data = $mastery['data'] ?? [];
- // 过滤出有实际答题记录的知识点
- $attemptedData = array_filter($data, function($item) {
- return ($item['total_attempts'] ?? 0) > 0;
- });
- $total = count($data); // 总知识点数(包括未学习的)
- $attemptedCount = count($attemptedData); // 已学习的知识点数
- $average = $attemptedCount > 0
- ? array_sum(array_column($attemptedData, 'mastery_level')) / $attemptedCount
- : 0;
- // 分类知识点(只对有记录的知识点进行分类)
- $mastered = []; // ≥ 85%
- $good = []; // 70-84%
- $weak = []; // < 70%
- foreach ($attemptedData as $item) {
- $level = $item['mastery_level'] ?? 0;
- if ($level >= 0.85) {
- $mastered[] = $item;
- } elseif ($level >= 0.70) {
- $good[] = $item;
- } else {
- $weak[] = $item;
- }
- }
- return [
- 'total_knowledge_points' => $total,
- 'average_mastery_level' => $average,
- 'mastered_knowledge_points' => count($mastered),
- 'good_knowledge_points' => count($good),
- 'weak_knowledge_points' => count($weak),
- 'weak_knowledge_points_list' => $weak,
- 'details' => $data
- ];
- } catch (\Exception $e) {
- Log::error('Get Student Mastery Overview Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'total_knowledge_points' => 0,
- 'average_mastery_level' => 0,
- 'mastered_knowledge_points' => 0,
- 'good_knowledge_points' => 0,
- 'weak_knowledge_points' => 0,
- 'weak_knowledge_points_list' => [],
- 'details' => []
- ];
- }
- }
- /**
- * 获取学生技能摘要
- */
- public function getStudentSkillSummary(string $studentId): array
- {
- try {
- $proficiency = $this->getStudentSkillProficiency($studentId);
- if (isset($proficiency['error'])) {
- return [
- 'total_skills' => 0,
- 'average_proficiency_level' => 0,
- 'total_questions_attempted' => 0,
- 'skill_list' => []
- ];
- }
- $data = $proficiency['data'] ?? [];
- $totalSkills = count($data);
- $averageLevel = $totalSkills > 0 ? array_sum(array_column($data, 'proficiency_level')) / $totalSkills : 0;
- // 计算总答题数
- $totalQuestions = 0;
- foreach ($data as $skill) {
- $totalQuestions += $skill['total_questions_attempted'] ?? 0;
- }
- return [
- 'total_skills' => $totalSkills,
- 'average_proficiency_level' => $averageLevel,
- 'total_questions_attempted' => $totalQuestions,
- 'skill_list' => $data
- ];
- } catch (\Exception $e) {
- Log::error('Get Student Skill Summary Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'total_skills' => 0,
- 'average_proficiency_level' => 0,
- 'total_questions_attempted' => 0,
- 'skill_list' => []
- ];
- }
- }
- /**
- * 获取学生预测数据
- */
- public function getStudentPredictions(string $studentId, int $count = 5): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . "/api/v1/prediction/student/{$studentId}?count={$count}");
- if ($response->successful()) {
- $data = $response->json();
- $predictions = $data['predictions'] ?? $data['data'] ?? [];
- return [
- 'predictions' => $predictions
- ];
- }
- return [
- 'predictions' => []
- ];
- } catch (\Exception $e) {
- Log::error('Get Student Predictions Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'predictions' => []
- ];
- }
- }
- /**
- * 获取学生学习路径
- */
- public function getStudentLearningPaths(string $studentId, int $count = 3): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . "/api/v1/learning-path/student/{$studentId}?count={$count}");
- if ($response->successful()) {
- $data = $response->json()['data'] ?? [];
- return [
- 'paths' => $data
- ];
- }
- return [
- 'paths' => []
- ];
- } catch (\Exception $e) {
- Log::error('Get Student Learning Paths Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'paths' => []
- ];
- }
- }
- /**
- * 获取预测分析数据
- */
- public function getPredictionAnalytics(string $studentId): array
- {
- try {
- $predictions = $this->getStudentPredictions($studentId, 10);
- if (empty($predictions)) {
- return ['accuracy' => 0, 'trend' => 'stable', 'confidence' => 0];
- }
- $accuracy = 0;
- $confidence = 0;
- if (!empty($predictions)) {
- $accuracy = rand(75, 95); // 模拟准确率
- $confidence = rand(70, 90); // 模拟置信度
- }
- $trend = 'improving'; // improving, stable, declining
- return [
- 'accuracy' => $accuracy,
- 'trend' => $trend,
- 'confidence' => $confidence,
- 'sample_size' => count($predictions)
- ];
- } catch (\Exception $e) {
- Log::error('Get Prediction Analytics Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return ['accuracy' => 0, 'trend' => 'stable', 'confidence' => 0];
- }
- }
- /**
- * 获取学习路径分析数据
- */
- public function getLearningPathAnalytics(string $studentId): array
- {
- try {
- $paths = $this->getStudentLearningPaths($studentId, 5);
- if (empty($paths)) {
- return [
- 'active_paths' => 0,
- 'completed_paths' => 0,
- 'average_efficiency_score' => 0,
- 'completion_rate' => 0,
- 'average_time' => 0,
- 'total_paths' => 0
- ];
- }
- $activePaths = 0;
- $completedPaths = 0;
- $efficiencyScores = [];
- foreach ($paths as $path) {
- if (($path['status'] ?? '') === 'active') {
- $activePaths++;
- }
- if (($path['status'] ?? '') === 'completed') {
- $completedPaths++;
- }
- if (isset($path['efficiency_score'])) {
- $efficiencyScores[] = $path['efficiency_score'];
- }
- }
- $averageEfficiency = !empty($efficiencyScores)
- ? array_sum($efficiencyScores) / count($efficiencyScores)
- : rand(60, 85) / 100;
- $completionRate = count($paths) > 0
- ? ($completedPaths / count($paths)) * 100
- : 0;
- $averageTime = rand(30, 60); // 模拟平均时间(分钟)
- return [
- 'active_paths' => $activePaths,
- 'completed_paths' => $completedPaths,
- 'average_efficiency_score' => $averageEfficiency,
- 'completion_rate' => $completionRate,
- 'average_time' => $averageTime,
- 'total_paths' => count($paths)
- ];
- } catch (\Exception $e) {
- Log::error('Get Learning Path Analytics Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'active_paths' => 0,
- 'completed_paths' => 0,
- 'average_efficiency_score' => 0,
- 'completion_rate' => 0,
- 'average_time' => 0,
- 'total_paths' => 0
- ];
- }
- }
- /**
- * 快速分数预测
- */
- public function quickScorePrediction(string $studentId): array
- {
- Log::info('开始调用快速预测API', ['student_id' => $studentId]);
- try {
- $response = Http::timeout($this->timeout)
- ->post($this->baseUrl . "/api/v1/prediction/student/{$studentId}/quick-prediction", [
- 'student_id' => $studentId
- ]);
- Log::info('快速预测API响应', [
- 'student_id' => $studentId,
- 'status' => $response->status(),
- 'body' => $response->body()
- ]);
- if ($response->successful()) {
- $data = $response->json();
- Log::info('快速预测API返回数据', ['student_id' => $studentId, 'data' => $data]);
- // API直接返回数据,没有嵌套在'data'键中
- $quickPredictionData = $data['quick_prediction'] ?? [];
- return [
- 'quick_prediction' => [
- 'current_score' => $quickPredictionData['current_score'] ?? 70,
- 'predicted_score' => $quickPredictionData['predicted_score'] ?? 75,
- 'improvement_potential' => $quickPredictionData['improvement_potential'] ?? 5,
- 'estimated_study_hours' => $quickPredictionData['estimated_study_hours'] ?? 15,
- 'confidence_level' => $quickPredictionData['confidence_level'] ?? 0.75,
- 'priority_topics' => $quickPredictionData['priority_topics'] ?? [],
- 'recommended_actions' => $quickPredictionData['recommended_actions'] ?? [],
- 'weak_knowledge_points_count' => $quickPredictionData['weak_knowledge_points_count'] ?? 0,
- 'total_knowledge_points' => $quickPredictionData['total_knowledge_points'] ?? 0
- ],
- 'predicted_score' => $quickPredictionData['predicted_score'] ?? 75,
- 'confidence' => $quickPredictionData['confidence_level'] ? $quickPredictionData['confidence_level'] * 100 : 75,
- 'time_estimate' => $quickPredictionData['estimated_study_hours'] ?? 15
- ];
- }
- Log::warning('快速预测API调用失败', [
- 'student_id' => $studentId,
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- return [
- 'quick_prediction' => [
- 'improvement_potential' => 0,
- 'estimated_study_hours' => 0,
- 'confidence_level' => 0
- ],
- 'predicted_score' => 0,
- 'confidence' => 0,
- 'time_estimate' => 0
- ];
- } catch (\Exception $e) {
- Log::error('Quick Score Prediction Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'quick_prediction' => [
- 'improvement_potential' => 0,
- 'estimated_study_hours' => 0,
- 'confidence_level' => 0
- ],
- 'predicted_score' => 0,
- 'confidence' => 0,
- 'time_estimate' => 0
- ];
- }
- }
- /**
- * 推荐学习路径
- */
- public function recommendLearningPaths(string $studentId, int $count = 3): array
- {
- try {
- $response = Http::timeout($this->timeout)
- ->get($this->baseUrl . "/api/v1/learning-path/recommend/{$studentId}?count={$count}");
- if ($response->successful()) {
- $data = $response->json()['data'] ?? [];
- return [
- 'recommendations' => $data
- ];
- }
- return [
- 'recommendations' => []
- ];
- } catch (\Exception $e) {
- Log::error('Recommend Learning Paths Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return [
- 'recommendations' => []
- ];
- }
- }
- /**
- * 重新计算掌握度
- */
- public function recalculateMastery(string $studentId, string $kpCode): bool
- {
- try {
- $response = Http::timeout($this->timeout)
- ->post($this->baseUrl . "/api/v1/mastery/recalculate/{$studentId}", [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode
- ]);
- return $response->successful();
- } catch (\Exception $e) {
- Log::error('Recalculate Mastery Error', [
- 'student_id' => $studentId,
- 'kp_code' => $kpCode,
- 'error' => $e->getMessage()
- ]);
- return false;
- }
- }
- /**
- * 批量更新技能熟练度
- */
- public function batchUpdateSkillProficiency(string $studentId): bool
- {
- try {
- $response = Http::timeout($this->timeout)
- ->post($this->baseUrl . "/api/v1/skill/proficiency/student/{$studentId}/batch-update", [
- 'student_id' => $studentId
- ]);
- return $response->successful();
- } catch (\Exception $e) {
- Log::error('Batch Update Skill Proficiency Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- return false;
- }
- }
- /**
- * 清空学生所有答题数据
- */
- public function clearStudentData(string $studentId): bool
- {
- try {
- // 清空LearningAnalytics中的数据(通过API)
- $response = Http::timeout($this->timeout)
- ->delete($this->baseUrl . "/api/v1/student/{$studentId}/clear");
- if (!$response->successful()) {
- Log::error('Clear LearningAnalytics Data Failed', [
- 'student_id' => $studentId,
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- }
- // 清空MySQL中的数据
- $this->clearStudentMySQLData($studentId);
- Log::info('Student Data Cleared Successfully', [
- 'student_id' => $studentId,
- 'api_success' => $response->successful()
- ]);
- return true;
- } catch (\Exception $e) {
- Log::error('Clear Student Data Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- // 即使API失败,也要尝试清空本地数据
- try {
- $this->clearStudentMySQLData($studentId);
- return true;
- } catch (\Exception $localError) {
- Log::error('Clear Local Data Also Failed', [
- 'student_id' => $studentId,
- 'error' => $localError->getMessage()
- ]);
- return false;
- }
- }
- }
- /**
- * 清空学生MySQL中的答题数据
- */
- private function clearStudentMySQLData(string $studentId): void
- {
- try {
- // 清空student_exercises表
- DB::connection('remote_mysql')
- ->table('student_exercises')
- ->where('student_id', $studentId)
- ->delete();
- // 清空student_mastery表
- DB::connection('remote_mysql')
- ->table('student_mastery')
- ->where('student_id', $studentId)
- ->delete();
- Log::info('Student MySQL Data Cleared', [
- 'student_id' => $studentId
- ]);
- } catch (\Exception $e) {
- Log::error('Clear Student MySQL Data Error', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage()
- ]);
- throw $e; // 重新抛出异常,让上层处理
- }
- }
- }
|