yemeishu 5 дней назад
Родитель
Сommit
ec74e23e17

+ 167 - 4
app/Filament/Pages/StudentDashboard.php

@@ -7,6 +7,7 @@ use App\Filament\Traits\HasUserRole;
 use App\Models\Student;
 use App\Models\Teacher;
 use App\Services\KnowledgeMasteryService;
+use App\Services\LearningAnalyticsService;
 use App\Services\MasteryCalculator;
 use BackedEnum;
 use Filament\Pages\Page;
@@ -23,9 +24,7 @@ use App\Services\MistakeBookService;
 
 class StudentDashboard extends Page
 {
-    use HasUserRole, HandlesMindmapDetails;
-
-    use \Filament\Pages\Concerns\InteractsWithFormActions;
+    use HasUserRole, HandlesMindmapDetails, \Filament\Pages\Concerns\InteractsWithFormActions;
 
     protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-chart-bar';
 
@@ -240,10 +239,18 @@ class StudentDashboard extends Page
             $recommendations = $service->recommendLearningPaths($this->studentId, 3);
 
             // 组合数据
+            $masteryList = $service->getStudentMasteryList($this->studentId);
+
+            // 如果没有掌握度数据,从错题记录中生成基于错题的掌握度
+            if (empty($masteryList['data'] ?? [])) {
+                Log::info('未找到掌握度数据,从错题记录生成', ['student_id' => $this->studentId]);
+                $masteryList = $this->generateMasteryFromMistakes($this->studentId);
+            }
+
             $this->dashboardData = [
                 'mastery' => [
                     'overview' => $masteryOverview,
-                    'list' => $service->getStudentMasteryList($this->studentId),
+                    'list' => $masteryList,
                 ],
                 'skill' => [
                     'proficiency' => $skillProficiency,
@@ -424,4 +431,160 @@ class StudentDashboard extends Page
         $this->studentId = $studentId;
         $this->loadDashboardData();
     }
+
+    /**
+     * 从错题记录生成掌握度数据
+     */
+    private function generateMasteryFromMistakes(string $studentId): array
+    {
+        try {
+            // 获取学生的错题记录
+            $mistakeRecords = \App\Models\MistakeRecord::forStudent($studentId)
+                ->get(['kp_ids', 'knowledge_point', 'is_corrected', 'review_status']);
+
+            // 统计每个知识点的错题数量
+            $kpStats = [];
+            foreach ($mistakeRecords as $record) {
+                $kpIds = $record->kp_ids ?? [];
+                if (is_string($kpIds)) {
+                    $kpIds = json_decode($kpIds, true) ?? [];
+                }
+
+                foreach ($kpIds as $kpCode) {
+                    if (empty($kpCode)) {
+                        continue;
+                    }
+
+                    if (!isset($kpStats[$kpCode])) {
+                        $kpStats[$kpCode] = [
+                            'kp_code' => $kpCode,
+                            'kp_name' => $record->knowledge_point ?? $kpCode,
+                            'mistake_count' => 0,
+                            'corrected_count' => 0,
+                            'mastery_level' => 0.0,
+                        ];
+                    }
+
+                    $kpStats[$kpCode]['mistake_count']++;
+                    if ($record->is_corrected) {
+                        $kpStats[$kpCode]['corrected_count']++;
+                    }
+                }
+            }
+
+            // 计算掌握度(基于错题数量和纠正情况)
+            $masteryData = [];
+            foreach ($kpStats as $kpCode => $stats) {
+                $total = $stats['mistake_count'];
+                $corrected = $stats['corrected_count'];
+
+                // 掌握度计算:已纠正的题目比例 + 基础分数
+                // 如果全部纠正,掌握度较高;如果有未纠正的,掌握度较低
+                $masteryLevel = $total > 0
+                    ? ($corrected / $total) * 0.7 + 0.1  // 基础分数0.1,最高0.8
+                    : 0.5; // 默认中等掌握度
+
+                // 确保掌握度在合理范围内
+                $masteryLevel = max(0.1, min(0.9, $masteryLevel));
+
+                $masteryData[] = [
+                    'kp_code' => $kpCode,
+                    'kp_name' => $stats['kp_name'],
+                    'mastery_level' => round($masteryLevel, 2),
+                    'total_attempts' => $total,
+                    'correct_attempts' => $corrected,
+                    'accuracy_rate' => $total > 0 ? round($corrected / $total, 2) : 0,
+                    'trend' => $corrected >= ($total * 0.5) ? 'improving' : 'needs_attention',
+                    'last_attempt' => now()->toISOString(),
+                ];
+            }
+
+            Log::info('从错题记录生成掌握度数据', [
+                'student_id' => $studentId,
+                'kp_count' => count($masteryData),
+                'mastery_data' => $masteryData
+            ]);
+
+            // 为生成的掌握度数据创建快照记录
+            $this->createMasterySnapshots($studentId, $masteryData);
+
+            return [
+                'student_id' => $studentId,
+                'total_count' => count($masteryData),
+                'data' => $masteryData,
+            ];
+
+        } catch (\Exception $e) {
+            Log::error('从错题记录生成掌握度失败', [
+                'student_id' => $studentId,
+                'error' => $e->getMessage()
+            ]);
+
+            return [
+                'student_id' => $studentId,
+                'total_count' => 0,
+                'data' => [],
+            ];
+        }
+    }
+
+    /**
+     * 创建掌握度快照记录
+     */
+    private function createMasterySnapshots(string $studentId, array $masteryData): void
+    {
+        try {
+            // 计算整体掌握度
+            $totalPoints = count($masteryData);
+            $averageMastery = $totalPoints > 0
+                ? array_sum(array_column($masteryData, 'mastery_level')) / $totalPoints
+                : 0;
+
+            // 统计强弱知识点数量
+            $weakCount = 0;
+            $strongCount = 0;
+            foreach ($masteryData as $data) {
+                $level = floatval($data['mastery_level']);
+                if ($level >= 0.7) {
+                    $strongCount++;
+                } elseif ($level < 0.5) {
+                    $weakCount++;
+                }
+            }
+
+            // 生成快照ID
+            $snapshotId = 'auto_' . $studentId . '_' . time();
+
+            // 创建快照记录
+            DB::table('knowledge_point_mastery_snapshots')->insert([
+                'snapshot_id' => $snapshotId,
+                'student_id' => $studentId,
+                'paper_id' => null, // 自动生成,没有关联试卷
+                'answer_record_id' => null,
+                'mastery_data' => json_encode($masteryData),
+                'overall_mastery' => round($averageMastery, 4),
+                'weak_knowledge_points_count' => $weakCount,
+                'strong_knowledge_points_count' => $strongCount,
+                'snapshot_time' => now(),
+                'analysis_id' => null,
+                'created_at' => now(),
+                'updated_at' => now(),
+            ]);
+
+            Log::info('创建掌握度快照', [
+                'student_id' => $studentId,
+                'snapshot_id' => $snapshotId,
+                'total_knowledge_points' => $totalPoints,
+                'average_mastery' => $averageMastery,
+                'weak_count' => $weakCount,
+                'strong_count' => $strongCount,
+            ]);
+
+        } catch (\Exception $e) {
+            Log::error('创建掌握度快照失败', [
+                'student_id' => $studentId,
+                'error' => $e->getMessage()
+            ]);
+        }
+    }
 }

+ 120 - 0
app/Http/Controllers/Api/HealthCheckController.php

@@ -0,0 +1,120 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Http\Controllers\Controller;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\Http;
+use Illuminate\Support\Facades\Log;
+
+/**
+ * 健康检查控制器
+ * 用于检查依赖服务状态
+ */
+class HealthCheckController extends Controller
+{
+    /**
+     * 检查所有依赖服务状态
+     */
+    public function index(): JsonResponse
+    {
+        $checks = [
+            'question_bank_api' => $this->checkQuestionBankService(),
+            'learning_analytics' => $this->checkLearningAnalyticsService(),
+            'database' => $this->checkDatabase(),
+        ];
+
+        $allHealthy = collect($checks)->every(fn($check) => $check['status'] === 'healthy');
+
+        return response()->json([
+            'success' => true,
+            'overall_status' => $allHealthy ? 'healthy' : 'unhealthy',
+            'checks' => $checks,
+            'timestamp' => now()->toISOString(),
+        ]);
+    }
+
+    /**
+     * 检查题库服务
+     */
+    private function checkQuestionBankService(): array
+    {
+        try {
+            $baseUrl = config('services.question_bank.base_url', 'http://localhost:5015');
+            $response = Http::timeout(5)->get($baseUrl . '/health');
+
+            return [
+                'status' => $response->successful() ? 'healthy' : 'unhealthy',
+                'message' => $response->body(),
+                'response_time' => $response->transferStats ? $response->transferStats->getTotalTime() : null,
+            ];
+        } catch (\Exception $e) {
+            Log::error('题库服务健康检查失败', ['error' => $e->getMessage()]);
+
+            return [
+                'status' => 'unhealthy',
+                'message' => $e->getMessage(),
+                'error_code' => $e->getCode(),
+            ];
+        }
+    }
+
+    /**
+     * 检查学习分析服务
+     */
+    private function checkLearningAnalyticsService(): array
+    {
+        try {
+            $baseUrl = config('services.learning_analytics.base_url', 'http://localhost:5016');
+            $response = Http::timeout(5)->get($baseUrl . '/health');
+
+            return [
+                'status' => $response->successful() ? 'healthy' : 'unhealthy',
+                'message' => $response->body(),
+                'response_time' => $response->transferStats ? $response->transferStats->getTotalTime() : null,
+            ];
+        } catch (\Exception $e) {
+            Log::error('学习分析服务健康检查失败', ['error' => $e->getMessage()]);
+
+            return [
+                'status' => 'unhealthy',
+                'message' => $e->getMessage(),
+                'error_code' => $e->getCode(),
+            ];
+        }
+    }
+
+    /**
+     * 检查数据库连接
+     */
+    private function checkDatabase(): array
+    {
+        try {
+            // 检查 MySQL 主库
+            $mysqlResult = \DB::connection('mysql')->select('SELECT 1 as test');
+
+            // 检查题库 PostgreSQL(如果存在)
+            $pgResult = null;
+            try {
+                $pgResult = \DB::connection('pgsql')->select('SELECT 1 as test');
+            } catch (\Exception $e) {
+                // PostgreSQL 连接失败但不影响主流程
+            }
+
+            return [
+                'status' => 'healthy',
+                'message' => 'MySQL连接正常',
+                'mysql' => 'healthy',
+                'postgresql' => $pgResult ? 'healthy' : 'not_configured',
+            ];
+        } catch (\Exception $e) {
+            Log::error('数据库健康检查失败', ['error' => $e->getMessage()]);
+
+            return [
+                'status' => 'unhealthy',
+                'message' => $e->getMessage(),
+                'error_code' => $e->getCode(),
+            ];
+        }
+    }
+}

+ 1 - 1
app/Http/Controllers/Api/PaperSubmitAnalysisController.php

@@ -418,7 +418,7 @@ class PaperSubmitAnalysisController extends Controller
             }
 
             // 从数据库获取分析结果
-            $result = DB::connection('pgsql')
+            $result = DB::connection('mysql')
                 ->table('exam_analysis_results')
                 ->where('exam_id', $paperId)
                 ->where('student_id', $paper->student_id)

+ 35 - 9
app/Services/KnowledgeMasteryService.php

@@ -91,8 +91,19 @@ class KnowledgeMasteryService
             return $data;
         }
 
+        // 确保 details 是数组格式(处理 stdClass 对象)
+        $details = $data['details'];
+        if (!is_array($details)) {
+            $details = [];
+        } elseif (isset($details[0]) && is_object($details[0])) {
+            // 将 stdClass 对象转换为关联数组
+            $details = array_map(function ($item) {
+                return (array) $item;
+            }, $details);
+        }
+
         // 收集所有kp_code
-        $kpCodes = array_column($data['details'], 'kp_code');
+        $kpCodes = array_column($details, 'kp_code');
         if (empty($kpCodes)) {
             return $data;
         }
@@ -101,7 +112,7 @@ class KnowledgeMasteryService
         $kpNames = $this->getKnowledgePointNames($kpCodes);
 
         // 丰富details数据
-        foreach ($data['details'] as &$detail) {
+        foreach ($details as &$detail) {
             $kpCode = $detail['kp_code'] ?? null;
             if ($kpCode && isset($kpNames[$kpCode])) {
                 $detail['kp_name'] = $kpNames[$kpCode];
@@ -110,6 +121,9 @@ class KnowledgeMasteryService
             }
         }
 
+        // 更新原始数据
+        $data['details'] = $details;
+
         return $data;
     }
 
@@ -291,16 +305,28 @@ class KnowledgeMasteryService
                 ->get();
 
             $snapshotList = $snapshots->map(function ($snapshot) {
+                // 解析掌握度数据
+                $masteryData = json_decode($snapshot->mastery_data, true) ?: [];
+
+                // 提取知识点信息
+                $knowledgePoints = [];
+                foreach ($masteryData as $kpData) {
+                    $knowledgePoints[] = [
+                        'kp_code' => $kpData['kp_code'] ?? '',
+                        'kp_name' => $kpData['kp_name'] ?? ($kpData['kp_code'] ?? ''),
+                        'mastery_level' => floatval($kpData['mastery_level'] ?? 0),
+                    ];
+                }
+
                 return [
-                    'snapshot_id' => $snapshot->id,
+                    'snapshot_id' => $snapshot->snapshot_id,
                     'student_id' => $snapshot->student_id,
-                    'kp_code' => $snapshot->kp_code,
-                    'mastery_level' => floatval($snapshot->mastery_level),
-                    'snapshot_type' => $snapshot->snapshot_type,
-                    'source_id' => $snapshot->source_id,
-                    'source_name' => $snapshot->source_name,
-                    'notes' => $snapshot->notes,
+                    'overall_mastery' => floatval($snapshot->overall_mastery),
+                    'weak_knowledge_points_count' => intval($snapshot->weak_knowledge_points_count),
+                    'strong_knowledge_points_count' => intval($snapshot->strong_knowledge_points_count),
+                    'knowledge_points' => $knowledgePoints,
                     'created_at' => $snapshot->created_at,
+                    'snapshot_time' => $snapshot->snapshot_time,
                 ];
             })->toArray();
 

+ 2 - 0
app/Services/MistakeBookService.php

@@ -654,6 +654,8 @@ class MistakeBookService
         $data = [
             'id' => $mistake->id,
             'student_id' => $mistake->student_id,
+            'question_id' => $mistake->question_id,
+            'paper_id' => $mistake->paper_id,
             'question_text' => $mistake->question_text,
             'student_answer' => $mistake->student_answer,
             'correct_answer' => $mistake->correct_answer,