| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- <?php
- namespace App\Http\Controllers\Api;
- use App\Http\Controllers\Controller;
- use App\Models\StudentKnowledgeMastery;
- use App\Models\KnowledgePoint;
- use Illuminate\Http\JsonResponse;
- use Illuminate\Http\Request;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Log;
- class StudentKnowledgeController extends Controller
- {
- /**
- * 获取学生知识点掌握详情
- *
- * @param string $studentId
- * @param Request $request
- * @return JsonResponse
- */
- public function getKnowledgePointsDetail(string $studentId, Request $request): JsonResponse
- {
- try {
- // 验证学生是否存在
- $student = \App\Models\Student::where('student_id', $studentId)->first();
- if (!$student) {
- return response()->json([
- 'success' => false,
- 'message' => '学生不存在',
- ], 404);
- }
- // 获取查询参数
- $level = $request->query('level'); // 筛选特定层级的知识点
- $sort = $request->query('sort', 'mastery_asc'); // 排序方式
- // 构建查询(解决字符集不匹配问题)
- // 【修复】显式选择mastery_level和mastery_change字段,避免访问器转换
- $query = StudentKnowledgeMastery::forStudent($studentId)
- ->with('knowledgePoint') // 预加载知识点信息
- ->select([
- 'student_knowledge_mastery.*',
- 'knowledge_points.name as kp_name',
- 'knowledge_points.parent_kp_code',
- // 显式选择原始字段,避免访问器
- DB::raw('CAST(student_knowledge_mastery.mastery_level AS DECIMAL(10,4)) as mastery_level_raw'),
- DB::raw('CAST(student_knowledge_mastery.mastery_change AS DECIMAL(10,4)) as mastery_change_raw'),
- ])
- ->join('knowledge_points', 'student_knowledge_mastery.kp_code', '=', DB::raw('CAST(knowledge_points.kp_code AS CHAR)'));
- // 按层级筛选
- if ($level) {
- if ($level === 'top') {
- // 只查询顶级知识点(没有父级)
- $query->whereNull('knowledge_points.parent_kp_code');
- } elseif ($level === 'leaf') {
- // 只查询叶子知识点(没有子级)
- $query->whereNotIn('knowledge_points.kp_code', function ($q) {
- $q->select('parent_kp_code')
- ->from('knowledge_points')
- ->whereNotNull('parent_kp_code');
- });
- }
- }
- // 排序
- switch ($sort) {
- case 'mastery_desc':
- $query->orderBy('mastery_level', 'desc');
- break;
- case 'name_asc':
- $query->orderBy('kp_name', 'asc');
- break;
- case 'name_desc':
- $query->orderBy('kp_name', 'desc');
- break;
- case 'attempts_desc':
- $query->orderBy('total_attempts', 'desc');
- break;
- case 'mastery_asc':
- default:
- $query->orderBy('mastery_level', 'asc');
- break;
- }
- $masteryRecords = $query->get();
- // 构建返回数据
- $knowledgePoints = [];
- foreach ($masteryRecords as $record) {
- // 计算正确率
- $correctRate = $record->total_attempts > 0
- ? round(($record->correct_attempts / $record->total_attempts) * 100, 2) / 100
- : 0.0;
- // 计算稳定度(基于近期的变化趋势)
- $stability = $this->calculateStability($record);
- // 获取层级信息
- $level = $record->knowledgePoint->parent_kp_code ? '子级' : '顶级';
- // 获取父子关系
- $parentName = null;
- if ($record->knowledgePoint->parent_kp_code) {
- $parentKp = KnowledgePoint::where('kp_code', $record->knowledgePoint->parent_kp_code)->first();
- $parentName = $parentKp?->name ?? $record->knowledgePoint->parent_kp_code;
- }
- // 【修复】使用CAST后的原始字段,避免访问器转换
- $rawMasteryLevel = $record->mastery_level_raw;
- $rawMasteryChange = $record->mastery_change_raw;
- $masteryLevel = is_numeric($rawMasteryLevel) ? (float)$rawMasteryLevel : 0.0;
- $masteryChange = is_numeric($rawMasteryChange) ? (float)$rawMasteryChange : 0.0;
- $knowledgePoints[] = [
- 'kp_code' => $record->kp_code,
- 'knowledge_point' => $record->kp_name ?: $record->kp_code, // 使用名称,如果没有名称则使用代码
- 'mastery' => round($masteryLevel, 4),
- 'stability' => $stability,
- 'level' => $level,
- 'parent_kp_code' => $record->knowledgePoint->parent_kp_code,
- 'parent_kp_name' => $parentName,
- 'last_updated' => $record->last_mastery_update?->toISOString(),
- 'practice_count' => (int)$record->total_attempts,
- 'correct_rate' => $correctRate,
- 'mastery_change' => $masteryChange,
- 'mastery_trend' => $record->mastery_trend,
- 'trend_label' => $record->trend_label ?? '稳定',
- ];
- }
- // 【新功能】获取父节点掌握度
- $parentMasteryLevels = $this->getParentMasteryLevels($studentId, $knowledgePoints);
- Log::info('获取学生知识点掌握详情', [
- 'student_id' => $studentId,
- 'count' => count($knowledgePoints),
- 'parent_count' => count($parentMasteryLevels),
- 'level' => $level,
- 'sort' => $sort,
- ]);
- return response()->json([
- 'success' => true,
- 'data' => [
- 'student_id' => $studentId,
- 'student_name' => $student->name,
- 'knowledge_points' => array_values($knowledgePoints), // 重新索引
- 'parent_mastery_levels' => $parentMasteryLevels, // 新增:父节点掌握度
- 'total_count' => count($knowledgePoints),
- ],
- ]);
- } catch (\Exception $e) {
- Log::error('获取学生知识点掌握详情失败', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString(),
- ]);
- return response()->json([
- 'success' => false,
- 'message' => '获取失败:' . $e->getMessage(),
- ], 500);
- }
- }
- /**
- * 计算稳定度
- */
- private function calculateStability(StudentKnowledgeMastery $record): float
- {
- // 如果数据不足,返回默认值
- if ($record->total_attempts < 3) {
- return 0.5;
- }
- // 基于掌握度变化计算稳定度
- // 变化越小,稳定度越高
- $changeAbs = abs($record->mastery_change ?? 0);
- // 稳定度 = 1 - (变化幅度 * 10),范围0-1
- $stability = max(0, min(1, 1 - ($changeAbs * 10)));
- return round($stability, 4);
- }
- /**
- * 获取学生知识点层级关系
- *
- * @param string $studentId
- * @return JsonResponse
- */
- public function getKnowledgeHierarchy(string $studentId): JsonResponse
- {
- try {
- // 获取学生的所有知识点
- $masteryRecords = StudentKnowledgeMastery::forStudent($studentId)
- ->with('knowledgePoint')
- ->get();
- // 构建父子关系
- $hierarchy = [];
- $processed = [];
- foreach ($masteryRecords as $record) {
- $kpCode = $record->kp_code;
- // 避免重复处理
- if (isset($processed[$kpCode])) {
- continue;
- }
- $kp = $record->knowledgePoint;
- if (!$kp) {
- continue;
- }
- $processed[$kpCode] = true;
- // 如果是顶级知识点
- if (!$kp->parent_kp_code) {
- $hierarchy[] = [
- 'kp_code' => $kpCode,
- 'kp_name' => $kp->name ?: $kpCode,
- 'mastery' => round($record->mastery_level, 4),
- 'level' => 'top',
- 'children' => $this->getChildKnowledgePoints($masteryRecords, $kpCode),
- ];
- }
- }
- // 如果没有顶级知识点,尝试构建其他层级的结构
- if (empty($hierarchy)) {
- foreach ($masteryRecords as $record) {
- $kpCode = $record->kp_code;
- $kp = $record->knowledgePoint;
- if (!$kp) {
- continue;
- }
- $hierarchy[] = [
- 'kp_code' => $kpCode,
- 'kp_name' => $kp->name ?: $kpCode,
- 'mastery' => round($record->mastery_level, 4),
- 'level' => $kp->parent_kp_code ? 'child' : 'top',
- 'parent_kp_code' => $kp->parent_kp_code,
- ];
- }
- }
- return response()->json([
- 'success' => true,
- 'data' => [
- 'student_id' => $studentId,
- 'hierarchy' => $hierarchy,
- 'total_count' => count($hierarchy),
- ],
- ]);
- } catch (\Exception $e) {
- Log::error('获取学生知识点层级关系失败', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage(),
- ]);
- return response()->json([
- 'success' => false,
- 'message' => '获取失败:' . $e->getMessage(),
- ], 500);
- }
- }
- /**
- * 获取子知识点
- */
- private function getChildKnowledgePoints($masteryRecords, string $parentKpCode): array
- {
- $children = [];
- foreach ($masteryRecords as $record) {
- $kp = $record->knowledgePoint;
- if ($kp && $kp->parent_kp_code === $parentKpCode) {
- $children[] = [
- 'kp_code' => $kp->kp_code,
- 'kp_name' => $kp->name ?: $kp->kp_code,
- 'mastery' => round($record->mastery_level, 4),
- 'level' => 'child',
- 'children' => $this->getChildKnowledgePoints($masteryRecords, $kp->kp_code),
- ];
- }
- }
- return $children;
- }
- /**
- * 【新功能】获取父节点掌握度
- */
- private function getParentMasteryLevels(string $studentId, array $knowledgePoints): array
- {
- try {
- $parentMasteryLevels = [];
- // 收集所有父节点代码
- $parentKpCodes = [];
- foreach ($knowledgePoints as $kp) {
- if (!empty($kp['parent_kp_code'])) {
- $parentKpCodes[$kp['parent_kp_code']] = true;
- }
- }
- if (empty($parentKpCodes)) {
- return $parentMasteryLevels;
- }
- // 查询父节点的掌握度
- $parentRecords = StudentKnowledgeMastery::forStudent($studentId)
- ->whereIn('kp_code', array_keys($parentKpCodes))
- ->get();
- foreach ($parentRecords as $record) {
- // 使用CAST后的原始字段
- $rawMasteryLevel = $record->getAttributeValue('mastery_level');
- $rawMasteryChange = $record->getAttributeValue('mastery_change');
- $masteryLevel = is_numeric($rawMasteryLevel) ? (float)$rawMasteryLevel : 0.0;
- $masteryChange = is_numeric($rawMasteryChange) ? (float)$rawMasteryChange : 0.0;
- // 获取父节点的父节点
- $kp = $record->knowledgePoint;
- $grandParentKpCode = $kp?->parent_kp_code;
- $parentMasteryLevels[] = [
- 'kp_code' => $record->kp_code,
- 'knowledge_point' => $kp?->name ?: $record->kp_code,
- 'mastery' => round($masteryLevel, 4),
- 'mastery_change' => $masteryChange,
- 'mastery_trend' => $record->mastery_trend,
- 'trend_label' => $record->trend_label ?? '稳定',
- 'level' => '父级',
- 'parent_kp_code' => $grandParentKpCode,
- 'practice_count' => (int)$record->total_attempts,
- 'correct_rate' => $record->total_attempts > 0
- ? round(($record->correct_attempts / $record->total_attempts), 4)
- : 0.0,
- 'is_calculated' => ($record->mastery_trend === 'calculated'), // 标记为计算得出
- 'last_updated' => $record->last_mastery_update?->toISOString(),
- ];
- }
- return $parentMasteryLevels;
- } catch (\Exception $e) {
- Log::error('获取父节点掌握度失败', [
- 'student_id' => $studentId,
- 'error' => $e->getMessage(),
- ]);
- return [];
- }
- }
- }
|