mistakeBookService = $mistakeBookService; } /** * 获取错题列表 * * @param Request $request * @return JsonResponse */ public function listMistakes(Request $request): JsonResponse { try { $params = $request->only([ 'student_id', 'start_time', 'end_time', 'kp_ids', 'skill_ids', 'error_types', 'time_range', 'page', 'per_page', 'sort_by' ]); // 设置默认值 $params['page'] = (int) ($params['page'] ?? 1); $params['per_page'] = (int) ($params['per_page'] ?? 20); $params['sort_by'] = $params['sort_by'] ?? 'created_at_desc'; // 调用服务层获取错题列表 $result = $this->mistakeBookService->listMistakes($params); Log::info('获取错题列表成功', [ 'student_id' => $params['student_id'] ?? 'N/A', 'page' => $params['page'], 'per_page' => $params['per_page'], 'total' => $result['meta']['total'] ?? 0 ]); return response()->json([ 'success' => true, 'data' => $result['data'] ?? [], 'meta' => $result['meta'] ?? [], 'statistics' => $result['statistics'] ?? null ]); } catch (\Exception $e) { Log::error('获取错题列表失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'params' => $request->all() ]); return response()->json([ 'success' => false, 'message' => '获取错题列表失败: ' . $e->getMessage(), 'data' => [], 'meta' => [ 'total' => 0, 'page' => (int) ($request->get('page', 1)), 'per_page' => (int) ($request->get('per_page', 20)) ] ], 500); } } /** * 新增错题 */ public function addMistake(Request $request): JsonResponse { // 优先从body获取数据,不使用query params $payload = $request->json()->all(); if (empty($payload)) { $payload = $request->all(); } $validator = validator($payload, [ 'student_id' => 'required|string|min:1', 'question_id' => 'required|string|min:1', 'my_answer' => 'nullable|string', 'correct_answer' => 'nullable|string', 'source' => 'nullable|string|in:exam,homework,test,quiz,other', 'happened_at' => 'nullable|date', 'idempotency_key' => 'nullable|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => '参数错误', 'errors' => $validator->errors()->toArray(), ], 422); } try { $validated = $validator->validated(); $result = $this->mistakeBookService->createMistake($validated); return response()->json([ 'success' => true, 'data' => $result, 'duplicate' => $result['duplicate'] ?? false, ]); } catch (\Exception $e) { Log::error('新增错题失败', [ 'error' => $e->getMessage(), 'payload' => $payload, ]); return response()->json([ 'success' => false, 'message' => '新增错题失败: ' . $e->getMessage(), ], 500); } } /** * 获取单条错题详情 * * @param string $mistakeId * @param Request $request * @return JsonResponse */ public function getMistakeDetail(string $mistakeId, Request $request): JsonResponse { try { $studentId = $request->get('student_id'); $mistake = $this->mistakeBookService->getMistakeDetail($mistakeId, $studentId); if (empty($mistake)) { return response()->json([ 'success' => false, 'message' => '错题记录不存在' ], 404); } Log::info('获取错题详情成功', [ 'mistake_id' => $mistakeId, 'student_id' => $studentId ?? 'N/A' ]); return response()->json([ 'success' => true, 'data' => $mistake ]); } catch (\Exception $e) { Log::error('获取错题详情失败', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '获取错题详情失败: ' . $e->getMessage() ], 500); } } /** * 获取错题统计概要 * * @param Request $request * @return JsonResponse */ public function getSummary(Request $request): JsonResponse { try { $studentId = $request->get('student_id'); if (!$studentId) { return response()->json([ 'success' => false, 'message' => '缺少必要参数:student_id' ], 400); } $startTime = $request->get('start_time'); $endTime = $request->get('end_time'); // 构建查询参数 $params = ['student_id' => $studentId]; if ($startTime) { $params['start_time'] = $startTime; } if ($endTime) { $params['end_time'] = $endTime; } // 获取错题列表(用于计算统计信息) $mistakesResult = $this->mistakeBookService->listMistakes($params); // 获取概要统计 $summary = $this->mistakeBookService->summarize($studentId); Log::info('获取错题概要统计成功', [ 'student_id' => $studentId, 'total_mistakes' => $summary['total'] ?? 0 ]); return response()->json([ 'success' => true, 'data' => $summary // ✅ 直接返回统计信息,不嵌套 ]); } catch (\Exception $e) { Log::error('获取错题概要统计失败', [ 'student_id' => $request->get('student_id'), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '获取错题概要统计失败: ' . $e->getMessage() ], 500); } } /** * 获取错误模式分析 * * @param Request $request * @return JsonResponse */ public function getMistakePatterns(Request $request): JsonResponse { try { $studentId = $request->get('student_id'); if (!$studentId) { return response()->json([ 'success' => false, 'message' => '缺少必要参数:student_id' ], 400); } $patterns = $this->mistakeBookService->getMistakePatterns($studentId); Log::info('获取错误模式分析成功', [ 'student_id' => $studentId, 'top_kps_count' => count($patterns['top_kps'] ?? []) ]); return response()->json([ 'success' => true, 'data' => $patterns ]); } catch (\Exception $e) { Log::error('获取错误模式分析失败', [ 'student_id' => $request->get('student_id'), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '获取错误模式分析失败: ' . $e->getMessage() ], 500); } } /** * 收藏/取消收藏错题 * * @param string $mistakeId * @param Request $request * @return JsonResponse */ public function toggleFavorite(string $mistakeId, Request $request): JsonResponse { try { $data = $request->only(['favorite']); $favorite = $data['favorite'] ?? true; $result = $this->mistakeBookService->toggleFavorite($mistakeId, $favorite); Log::info(($favorite ? '收藏' : '取消收藏') . '错题', [ 'mistake_id' => $mistakeId, 'favorite' => $favorite, 'result' => $result ]); return response()->json([ 'success' => $result, 'message' => $result ? '操作成功' : '操作失败', 'data' => [ 'mistake_id' => $mistakeId, 'favorite' => $favorite ] ]); } catch (\Exception $e) { Log::error('收藏错题失败', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '收藏错题失败: ' . $e->getMessage() ], 500); } } /** * 标记错题已复习 * * @param string $mistakeId * @return JsonResponse */ public function markReviewed(string $mistakeId): JsonResponse { try { $result = $this->mistakeBookService->markReviewed($mistakeId); Log::info('标记错题已复习', [ 'mistake_id' => $mistakeId, 'result' => $result ]); return response()->json([ 'success' => $result, 'message' => $result ? '操作成功' : '操作失败', 'data' => [ 'mistake_id' => $mistakeId, 'reviewed' => $result, 'reviewed_at' => $result ? now()->toISOString() : null ] ]); } catch (\Exception $e) { Log::error('标记错题已复习失败', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '标记错题已复习失败: ' . $e->getMessage() ], 500); } } /** * 加入重练清单 * * @param string $mistakeId * @return JsonResponse */ public function addToRetryList(string $mistakeId): JsonResponse { try { $result = $this->mistakeBookService->addToRetryList($mistakeId); Log::info('加入重练清单', [ 'mistake_id' => $mistakeId, 'result' => $result ]); return response()->json([ 'success' => $result, 'message' => $result ? '操作成功' : '操作失败', 'data' => [ 'mistake_id' => $mistakeId, 'added' => $result ] ]); } catch (\Exception $e) { Log::error('加入重练清单失败', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '加入重练清单失败: ' . $e->getMessage() ], 500); } } /** * 获取题目正确率 * * @param string $questionId * @return JsonResponse */ public function getQuestionAccuracy(string $questionId): JsonResponse { try { $accuracy = $this->mistakeBookService->getQuestionAccuracy($questionId); Log::info('获取题目正确率成功', [ 'question_id' => $questionId, 'accuracy' => $accuracy ]); return response()->json([ 'success' => true, 'data' => [ 'question_id' => $questionId, 'accuracy' => $accuracy, 'correct_attempts' => null // LearningAnalytics不返回具体次数 ] ]); } catch (\Exception $e) { Log::error('获取题目正确率失败', [ 'question_id' => $questionId, 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '获取题目正确率失败: ' . $e->getMessage() ], 500); } } /** * 推荐练习题 * * @param Request $request * @return JsonResponse */ public function recommendPractice(Request $request): JsonResponse { try { $studentId = $request->get('student_id'); $kpIds = $request->get('kp_ids', []); $skillIds = $request->get('skill_ids', []); if (!$studentId) { return response()->json([ 'success' => false, 'message' => '缺少必要参数:student_id' ], 400); } $recommendations = $this->mistakeBookService->recommendPractice( $studentId, $kpIds, $skillIds ); Log::info('获取推荐练习题成功', [ 'student_id' => $studentId, 'kp_ids' => $kpIds, 'recommendations_count' => count($recommendations['data'] ?? []) ]); return response()->json([ 'success' => true, 'data' => $recommendations['data'] ?? [] ]); } catch (\Exception $e) { Log::error('获取推荐练习题失败', [ 'student_id' => $request->get('student_id'), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '获取推荐练习题失败: ' . $e->getMessage() ], 500); } } /** * 获取错题本快照数据(用于仪表板) * * @param Request $request * @return JsonResponse */ public function getSnapshot(Request $request): JsonResponse { try { $studentId = $request->get('student_id'); $limit = (int) ($request->get('limit', 5)); if (!$studentId) { return response()->json([ 'success' => false, 'message' => '缺少必要参数:student_id' ], 400); } $snapshot = $this->mistakeBookService->getPanelSnapshot($studentId, $limit); Log::info('获取错题本快照数据成功', [ 'student_id' => $studentId, 'limit' => $limit, 'recent_count' => count($snapshot['recent'] ?? []) ]); return response()->json([ 'success' => true, 'data' => $snapshot ]); } catch (\Exception $e) { Log::error('获取错题本快照数据失败', [ 'student_id' => $request->get('student_id'), 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return response()->json([ 'success' => false, 'message' => '获取错题本快照数据失败: ' . $e->getMessage() ], 500); } } /** * 修改错题复习状态 */ public function updateReviewStatus(Request $request, string $mistakeId): JsonResponse { try { // 优先从body获取数据,不使用query params $payload = $request->json()->all(); if (empty($payload)) { $payload = $request->all(); } $validator = validator($payload, [ 'action' => 'required|in:increment,reset,mastered', 'force_review' => 'boolean', ]); if ($validator->fails()) { return response()->json([ 'success' => false, 'message' => '参数错误', 'errors' => $validator->errors()->toArray(), ], 422); } $validated = $validator->validated(); $action = $validated['action']; $forceReview = $validated['force_review'] ?? false; $result = $this->mistakeBookService->updateReviewStatus($mistakeId, $action, $forceReview); if ($result['success'] ?? false) { return response()->json([ 'success' => true, 'data' => $result, 'message' => $result['message'] ?? '复习状态更新成功', ]); } return response()->json([ 'success' => false, 'message' => $result['error'] ?? '更新复习状态失败', ], 400); } catch (\Throwable $e) { Log::error('Update review status error', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'message' => '更新复习状态失败', 'error' => $e->getMessage(), ], 500); } } /** * 获取错题复习状态 */ public function getReviewStatus(string $mistakeId): JsonResponse { try { $result = $this->mistakeBookService->getReviewStatus($mistakeId); if ($result['success'] ?? false) { return response()->json([ 'success' => true, 'data' => $result, ]); } return response()->json([ 'success' => false, 'message' => $result['error'] ?? '获取复习状态失败', ], 400); } catch (\Throwable $e) { Log::error('Get review status error', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'message' => '获取复习状态失败', 'error' => $e->getMessage(), ], 500); } } /** * 增加复习次数 */ public function incrementReview(Request $request, string $mistakeId): JsonResponse { try { $validated = $request->validate([ 'force_review' => 'boolean', ]); $forceReview = $validated['force_review'] ?? false; $result = $this->mistakeBookService->incrementReviewCount($mistakeId, $forceReview); if ($result['success'] ?? false) { return response()->json([ 'success' => true, 'data' => $result, 'message' => $result['message'] ?? '复习次数增加成功', ]); } return response()->json([ 'success' => false, 'message' => $result['error'] ?? '增加复习次数失败', ], 400); } catch (\Throwable $e) { Log::error('Increment review count error', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'message' => '增加复习次数失败', 'error' => $e->getMessage(), ], 500); } } /** * 重置为强制复习状态 */ public function resetReview(string $mistakeId): JsonResponse { try { $result = $this->mistakeBookService->resetReviewStatus($mistakeId); if ($result['success'] ?? false) { return response()->json([ 'success' => true, 'data' => $result, 'message' => $result['message'] ?? '复习状态重置成功', ]); } return response()->json([ 'success' => false, 'message' => $result['error'] ?? '重置复习状态失败', ], 400); } catch (\Throwable $e) { Log::error('Reset review status error', [ 'mistake_id' => $mistakeId, 'error' => $e->getMessage(), ]); return response()->json([ 'success' => false, 'message' => '重置复习状态失败', 'error' => $e->getMessage(), ], 500); } } /** * 批量操作错题 */ public function batchOperation(Request $request): JsonResponse { try { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', 'operation' => 'required|string|in:favorite,reviewed,mastered,retry_list,remove_retry_list,set_error_type,set_importance', 'params' => 'array', ]); $mistakeIds = $validated['mistake_ids']; $operation = $validated['operation']; $params = $validated['params'] ?? []; $result = $this->mistakeBookService->batchOperation($mistakeIds, $operation, $params); if ($result['success'] ?? false) { return response()->json([ 'success' => true, 'data' => $result, 'message' => "批量操作成功,成功处理 {$result['success_count']} 条记录", ]); } return response()->json([ 'success' => false, 'message' => $result['error'] ?? '批量操作失败', ], 400); } catch (\Throwable $e) { Log::error('Batch operation error', [ 'error' => $e->getMessage(), 'request' => $request->all(), ]); return response()->json([ 'success' => false, 'message' => '批量操作失败', 'error' => $e->getMessage(), ], 500); } } /** * 批量标记为已复习 */ public function batchMarkReviewed(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'reviewed' ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已标记 {$result['success_count']} 道题为已复习" : ($result['error'] ?? '操作失败'), ]); } /** * 批量标记为已掌握 */ public function batchMarkMastered(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'mastered' ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已标记 {$result['success_count']} 道题为已掌握" : ($result['error'] ?? '操作失败'), ]); } /** * 批量加入重练清单 */ public function batchAddToRetryList(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'retry_list' ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已加入 {$result['success_count']} 道题到重练清单" : ($result['error'] ?? '操作失败'), ]); } /** * 批量从重练清单移除 */ public function batchRemoveFromRetryList(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'remove_retry_list' ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已从重练清单移除 {$result['success_count']} 道题" : ($result['error'] ?? '操作失败'), ]); } /** * 批量设置错误类型 */ public function batchSetErrorType(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', 'error_type' => 'required|string|in:concept,calculation,careless,logic,other', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'set_error_type', ['error_type' => $validated['error_type']] ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已为 {$result['success_count']} 道题设置错误类型" : ($result['error'] ?? '操作失败'), ]); } /** * 批量设置重要程度 */ public function batchSetImportance(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', 'importance' => 'required|integer|min:1|max:10', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'set_importance', ['importance' => $validated['importance']] ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已为 {$result['success_count']} 道题设置重要程度" : ($result['error'] ?? '操作失败'), ]); } /** * 批量切换收藏状态 */ public function batchToggleFavorite(Request $request): JsonResponse { $validated = $request->validate([ 'mistake_ids' => 'required|array|min:1', 'mistake_ids.*' => 'string', ]); $result = $this->mistakeBookService->batchOperation( $validated['mistake_ids'], 'favorite' ); return response()->json([ 'success' => $result['success'] ?? false, 'data' => $result, 'message' => $result['success'] ?? false ? "已为 {$result['success_count']} 道题切换收藏状态" : ($result['error'] ?? '操作失败'), ]); } }