Browse Source

老师身份 email 创建的问题修复;错题本的问题修复等

yemeishu 1 day ago
parent
commit
af75e1d7de

+ 0 - 4
app/Http/Controllers/Api/IntelligentExamController.php

@@ -646,10 +646,6 @@ class IntelligentExamController extends Controller
             'subject' => '数学',
         ]);
 
-        if ($teacher->name !== $teacherName && $teacherName !== '') {
-            $teacher->update(['name' => $teacherName]);
-        }
-
         $student = Student::where('student_id', $studentId)->first();
         if ($student) {
             $updates = [];

+ 48 - 0
app/Http/Controllers/Api/TextbookApiController.php

@@ -376,6 +376,54 @@ class TextbookApiController extends Controller
         }
     }
 
+    /**
+     * 根据教材系列、年级和学期获取教材及其目录结构
+     *
+     * @param int $seriesId 教材系列ID
+     * @param int $grade 年级(1-12)
+     * @param int $semesterCode 学期代码(1=上册,2=下册)
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function getTextbookByFilter(int $seriesId, int $grade, int $semesterCode, Request $request): JsonResponse
+    {
+        try {
+            // 验证参数
+            if (!in_array($semesterCode, [1, 2])) {
+                return response()->json([
+                    'success' => false,
+                    'message' => '学期代码错误,应为 1(上册)或 2(下册)'
+                ], 400);
+            }
+
+            if ($grade < 1 || $grade > 12) {
+                return response()->json([
+                    'success' => false,
+                    'message' => '年级错误,应为 1-12'
+                ], 400);
+            }
+
+            $catalogFormat = $request->get('format', 'tree'); // tree 或 flat
+
+            $result = $this->textbookService->getTextbookByFilter($seriesId, $grade, $semesterCode, $catalogFormat);
+
+            return response()->json($result);
+
+        } catch (\Exception $e) {
+            Log::error('获取教材及目录失败', [
+                'series_id' => $seriesId,
+                'grade' => $grade,
+                'semester_code' => $semesterCode,
+                'error' => $e->getMessage()
+            ]);
+
+            return response()->json([
+                'success' => false,
+                'message' => '获取教材信息失败: ' . $e->getMessage()
+            ], 500);
+        }
+    }
+
     /**
      * 格式化教材列表
      */

+ 36 - 12
app/Services/ExamAnswerAnalysisService.php

@@ -1187,18 +1187,42 @@ class ExamAnswerAnalysisService
                     if (!empty($kpMappings)) {
                         foreach ($kpMappings as $kpMapping) {
                             try {
-                                DB::connection('mysql')->table('student_answer_steps')->insert([
-                                    'student_id' => $studentId,
-                                    'exam_id' => $examId,
-                                    'question_id' => $questionId,
-                                    'step_index' => $stepIndex,
-                                    'kp_id' => $kpMapping['kp_id'],
-                                    'is_correct' => $isCorrectBool ? 1 : 0,
-                                    'step_score' => $isCorrectBool ? $scorePerStep : 0,
-                                    'created_at' => now(),
-                                    'updated_at' => now(),
-                                ]);
-                                $stepsSavedCount++;
+                                // 【修复】先检查记录是否已存在,避免重复键错误
+                                $exists = DB::connection('mysql')->table('student_answer_steps')
+                                    ->where('student_id', $studentId)
+                                    ->where('exam_id', $examId)
+                                    ->where('question_id', $questionId)
+                                    ->where('step_index', $stepIndex)
+                                    ->where('kp_id', $kpMapping['kp_id'])
+                                    ->exists();
+
+                                if (!$exists) {
+                                    DB::connection('mysql')->table('student_answer_steps')->insert([
+                                        'student_id' => $studentId,
+                                        'exam_id' => $examId,
+                                        'question_id' => $questionId,
+                                        'step_index' => $stepIndex,
+                                        'kp_id' => $kpMapping['kp_id'],
+                                        'is_correct' => $isCorrectBool ? 1 : 0,
+                                        'step_score' => $isCorrectBool ? $scorePerStep : 0,
+                                        'created_at' => now(),
+                                        'updated_at' => now(),
+                                    ]);
+                                    $stepsSavedCount++;
+                                    Log::debug('步骤记录保存成功', [
+                                        'student_id' => $studentId,
+                                        'question_id' => $questionId,
+                                        'step_index' => $stepIndex,
+                                        'kp_id' => $kpMapping['kp_id'],
+                                    ]);
+                                } else {
+                                    Log::debug('步骤记录已存在,跳过保存', [
+                                        'student_id' => $studentId,
+                                        'question_id' => $questionId,
+                                        'step_index' => $stepIndex,
+                                        'kp_id' => $kpMapping['kp_id'],
+                                    ]);
+                                }
 
                                 // 收集错误知识点(不重复调用 saveMistakeRecord)
                                 if (!$isCorrectBool) {

+ 101 - 31
app/Services/ExternalIdService.php

@@ -59,47 +59,117 @@ class ExternalIdService
 
     /**
      * 处理教师外部ID
-     * 如果ID不存在,自动创建用户和教师记录
+     * 如果ID存在,更新name;如果不存在,创建用户和教师记录
+     * 【修复】避免邮箱重复导致的错误
      */
     public function handleTeacherExternalId(int|string $teacherId, array $teacherData = []): Teacher
     {
         // teacher_id是int类型,直接使用
-        // 检查教师是否已存在
-        $teacher = Teacher::where('teacher_id', $teacherId)->first();
+        // 【修复】使用事务确保并发安全
+        return DB::transaction(function () use ($teacherId, $teacherData) {
+            // 检查教师是否已存在(使用forUpdate防止并发问题)
+            $teacher = Teacher::where('teacher_id', $teacherId)
+                ->lockForUpdate()
+                ->first();
 
-        if ($teacher) {
-            Log::info('外部教师ID已存在', ['teacher_id' => $teacherId]);
-            return $teacher;
-        }
+            if ($teacher) {
+                // 教师已存在,检查是否需要更新name
+                $needsUpdate = false;
+                $updateData = [];
 
-        // 创建用户记录
-        $userData = [
-            'user_id' => (string) $teacherId,
-            'username' => $teacherData['phone'] ?? (string) $teacherId, // 如果有手机号就用手机号
-            'password_hash' => Hash::make(env('TEACHER_HASH', 'TeGiFkEim2')),
-            'role' => 'teacher',
-            'full_name' => $teacherData['name'] ?? '未知教师',
-            'email' => $teacherId . '@teacher.edu',
-            'is_active' => 1,
-        ];
+                if (!empty($teacherData['name']) && $teacher->name !== $teacherData['name']) {
+                    $updateData['name'] = $teacherData['name'];
+                    $needsUpdate = true;
+                    Log::info('外部教师ID存在,更新name', [
+                        'teacher_id' => $teacherId,
+                        'old_name' => $teacher->name,
+                        'new_name' => $teacherData['name']
+                    ]);
+                }
 
-        $user = User::create($userData);
+                if (!empty($teacherData['subject']) && $teacher->subject !== $teacherData['subject']) {
+                    $updateData['subject'] = $teacherData['subject'];
+                    $needsUpdate = true;
+                }
 
-        // 创建教师记录
-        $teacher = Teacher::create([
-            'teacher_id' => $teacherId,
-            'teacher_string_id' => $teacherData['teacher_string_id'] ?? null,
-            'user_id' => $user->user_id,
-            'name' => $teacherData['name'] ?? '未知教师',
-            'subject' => $teacherData['subject'] ?? '数学',
-        ]);
+                if ($needsUpdate) {
+                    $teacher->update($updateData);
+                    Log::info('外部教师信息更新成功', [
+                        'teacher_id' => $teacherId,
+                        'updated_fields' => array_keys($updateData)
+                    ]);
+                } else {
+                    Log::info('外部教师ID已存在,无需更新', ['teacher_id' => $teacherId]);
+                }
 
-        Log::info('外部教师ID自动创建成功', [
-            'teacher_id' => $teacherId,
-            'user_id' => $user->user_id
-        ]);
+                return $teacher;
+            }
+
+            // 【修复】教师不存在,创建用户和教师记录
+            // 先尝试创建用户,使用teacher_id作为邮箱
+            $user = null;
+            $email = $teacherId . '@teacher.edu';
 
-        return $teacher;
+            try {
+                // 创建用户记录
+                $userData = [
+                    'user_id' => (string) $teacherId,
+                    'username' => $teacherData['phone'] ?? (string) $teacherId,
+                    'password_hash' => Hash::make(env('TEACHER_HASH', 'TeGiFkEim2')),
+                    'role' => 'teacher',
+                    'full_name' => $teacherData['name'] ?? '未知教师',
+                    'email' => $email,
+                    'is_active' => 1,
+                ];
+
+                $user = User::create($userData);
+                Log::info('外部教师用户创建成功', [
+                    'teacher_id' => $teacherId,
+                    'user_id' => $user->user_id,
+                    'email' => $email
+                ]);
+
+            } catch (\Exception $e) {
+                // 如果邮箱重复,尝试使用teacher_id + 随机数
+                if (str_contains($e->getMessage(), 'Duplicate entry') && str_contains($e->getMessage(), '@teacher.edu')) {
+                    Log::warning('邮箱已存在,尝试使用随机邮箱', [
+                        'teacher_id' => $teacherId,
+                        'original_email' => $email
+                    ]);
+
+                    // 生成唯一邮箱
+                    $uniqueEmail = $teacherId . '_' . uniqid() . '@teacher.edu';
+                    $userData['email'] = $uniqueEmail;
+
+                    $user = User::create($userData);
+                    Log::info('外部教师用户创建成功(使用唯一邮箱)', [
+                        'teacher_id' => $teacherId,
+                        'user_id' => $user->user_id,
+                        'email' => $uniqueEmail
+                    ]);
+                } else {
+                    // 其他错误,重新抛出
+                    throw $e;
+                }
+            }
+
+            // 创建教师记录
+            $teacher = Teacher::create([
+                'teacher_id' => $teacherId,
+                'teacher_string_id' => $teacherData['teacher_string_id'] ?? null,
+                'user_id' => $user->user_id,
+                'name' => $teacherData['name'] ?? '未知教师',
+                'subject' => $teacherData['subject'] ?? '数学',
+            ]);
+
+            Log::info('外部教师ID自动创建成功', [
+                'teacher_id' => $teacherId,
+                'user_id' => $user->user_id,
+                'name' => $teacherData['name'] ?? '未知教师'
+            ]);
+
+            return $teacher;
+        });
     }
 
     /**

+ 52 - 6
app/Services/MistakeBookService.php

@@ -55,21 +55,58 @@ class MistakeBookService
             $studentId, $questionId, $paperId, $myAnswer, $correctAnswer,
             $questionText, $knowledgePoint, $explanation, $kpIds, $source, $happenedAt
         ) {
-            // 检查是否已存在相同错题(避免重复)
+            // 【修复】检查是否已存在相同错题(避免重复)
+            // 重复定义:同一学生 + 同一题目 + 同一卷子 的组合
+            // 这样允许同一题目在不同卷子中重复,也允许同一卷子中的新题目被记录
             $query = MistakeRecord::where('student_id', $studentId)
-                ->where('source', $source)
-                ->where('created_at', '>=', now()->subDay());
-
-            // 如果有 question_id,用它去重;否则用 question_text
-            if ($questionId) {
+                ->where('source', $source);
+
+            // 如果有 question_id 和 paper_id,用它们去重
+            // 否则用 question_text 去重
+            if ($questionId && $paperId) {
+                $query->where('question_id', $questionId)
+                      ->where('paper_id', $paperId);
+                Log::debug('错题记录重复检查', [
+                    'student_id' => $studentId,
+                    'question_id' => $questionId,
+                    'paper_id' => $paperId,
+                    'check_type' => 'question_id + paper_id'
+                ]);
+            } elseif ($questionId) {
                 $query->where('question_id', $questionId);
+                Log::debug('错题记录重复检查', [
+                    'student_id' => $studentId,
+                    'question_id' => $questionId,
+                    'check_type' => 'question_id only'
+                ]);
             } elseif ($questionText) {
                 $query->where('question_text', $questionText);
+                Log::debug('错题记录重复检查', [
+                    'student_id' => $studentId,
+                    'question_text' => substr($questionText, 0, 50) . '...',
+                    'check_type' => 'question_text'
+                ]);
+            } else {
+                Log::warning('错题记录保存失败:缺少题目ID和题目文本', [
+                    'student_id' => $studentId,
+                    'paper_id' => $paperId
+                ]);
+                return [
+                    'duplicate' => false,
+                    'error' => '缺少题目ID和题目文本,无法创建错题记录'
+                ];
             }
 
             $existingMistake = $query->first();
 
             if ($existingMistake) {
+                Log::debug('发现重复错题记录,合并知识点', [
+                    'student_id' => $studentId,
+                    'question_id' => $questionId,
+                    'paper_id' => $paperId,
+                    'existing_mistake_id' => $existingMistake->id
+                ]);
+
                 // 合并知识点到已有记录中
                 if ($kpIds) {
                     $existingKpIds = $existingMistake->kp_ids ?? [];
@@ -82,6 +119,15 @@ class MistakeBookService
                     $existingMistake->update([
                         'kp_ids' => $mergedKpIds,
                     ]);
+
+                    Log::info('错题记录知识点合并成功', [
+                        'student_id' => $studentId,
+                        'question_id' => $questionId,
+                        'paper_id' => $paperId,
+                        'existing_kp_ids' => $existingKpIds,
+                        'new_kp_ids' => $newKpIds,
+                        'merged_kp_ids' => $mergedKpIds
+                    ]);
                 }
 
                 return [

+ 193 - 0
app/Services/TextbookApiService.php

@@ -704,6 +704,199 @@ class TextbookApiService
         }
     }
 
+    /**
+     * 根据教材系列、年级和学期获取教材及其目录结构
+     *
+     * @param int $seriesId 教材系列ID
+     * @param int $grade 年级(1-12)
+     * @param int $semesterCode 学期代码(1=上册,2=下册)
+     * @param string $catalogFormat 目录格式(tree 或 flat)
+     * @return array 包含教材信息和目录结构的数组
+     */
+    public function getTextbookByFilter(int $seriesId, int $grade, int $semesterCode, string $catalogFormat = 'tree'): array
+    {
+        if ($this->useDatabase) {
+            try {
+                // 先查找符合条件且已发布的教材
+                $query = Textbook::query()
+                    ->select('textbooks.*')
+                    ->join('textbook_series', 'textbooks.series_id', '=', 'textbook_series.id')
+                    ->where('textbooks.series_id', $seriesId)
+                    ->where('textbooks.grade', $grade)
+                    ->where('textbooks.semester', $semesterCode)
+                    ->where('textbooks.status', 'published')
+                    ->where('textbook_series.is_active', true);
+
+                $textbook = $query->first();
+
+                if (!$textbook) {
+                    return [
+                        'success' => false,
+                        'message' => '未找到符合条件的教材',
+                        'data' => null,
+                        'meta' => [
+                            'series_id' => $seriesId,
+                            'grade' => $grade,
+                            'semester_code' => $semesterCode,
+                            'semester_label' => $this->getSemesterLabel($semesterCode),
+                        ]
+                    ];
+                }
+
+                // 获取教材目录
+                $catalog = $this->getTextbookCatalog($textbook->id, $catalogFormat);
+
+                // 格式化教材信息
+                $textbookData = [
+                    'id' => $textbook->id,
+                    'name' => $textbook->official_title ?? '',
+                    'display_name' => $textbook->official_title ?? '',
+                    'cover' => $this->formatCoverUrl($textbook->cover_path ?? ''),
+                    'series_id' => $textbook->series_id,
+                    'series_name' => $textbook->series->name ?? '',
+                    'publisher' => $textbook->series->publisher ?? '',
+                    'stage' => $this->getStageLabel($textbook->stage ?? ''),
+                    'stage_code' => $textbook->stage ?? '',
+                    'grade' => $textbook->grade,
+                    'grade_label' => $this->getGradeLabel($textbook->grade, $textbook->stage ?? ''),
+                    'semester' => $this->getSemesterLabel($textbook->semester),
+                    'semester_code' => $textbook->semester,
+                    'module_type' => $textbook->module_type ?? null,
+                    'volume_no' => $textbook->volume_no ?? null,
+                    'isbn' => $textbook->isbn ?? '',
+                    'approval_year' => $textbook->approval_year ?? null,
+                    'curriculum_standard_year' => $textbook->curriculum_standard_year ?? null,
+                    'status' => $textbook->status ?? 'draft',
+                    'sort_order' => $textbook->sort_order ?? 0,
+                ];
+
+                return [
+                    'success' => true,
+                    'data' => [
+                        'textbook' => $textbookData,
+                        'catalog' => $catalog,
+                    ],
+                    'meta' => [
+                        'series_id' => $seriesId,
+                        'grade' => $grade,
+                        'grade_label' => $textbookData['grade_label'],
+                        'semester_code' => $semesterCode,
+                        'semester_label' => $textbookData['semester'],
+                        'catalog_format' => $catalogFormat,
+                    ]
+                ];
+
+            } catch (\Exception $e) {
+                Log::error('获取教材及目录失败', [
+                    'series_id' => $seriesId,
+                    'grade' => $grade,
+                    'semester_code' => $semesterCode,
+                    'error' => $e->getMessage()
+                ]);
+
+                return [
+                    'success' => false,
+                    'message' => '获取教材信息失败: ' . $e->getMessage(),
+                    'data' => null,
+                ];
+            }
+        }
+
+        // 外部API调用(如果需要)
+        try {
+            $result = $this->request('GET', '/textbooks/filter', [
+                'series_id' => $seriesId,
+                'grade' => $grade,
+                'semester_code' => $semesterCode,
+                'catalog_format' => $catalogFormat,
+            ]);
+
+            return [
+                'success' => true,
+                'data' => $result['data'] ?? null,
+                'meta' => $result['meta'] ?? []
+            ];
+
+        } catch (\Exception $e) {
+            Log::warning('Failed to fetch textbook by filter, returning empty result', ['error' => $e->getMessage()]);
+            return [
+                'success' => false,
+                'message' => '获取教材信息失败: ' . $e->getMessage(),
+                'data' => null,
+            ];
+        }
+    }
+
+    /**
+     * 获取学期标签
+     */
+    private function getSemesterLabel(?int $semester): string
+    {
+        return match ($semester) {
+            1 => '上册',
+            2 => '下册',
+            default => '',
+        };
+    }
+
+    /**
+     * 获取学段标签
+     */
+    private function getStageLabel(string $stage): string
+    {
+        return match ($stage) {
+            'primary' => '小学',
+            'junior' => '初中',
+            'senior' => '高中',
+            default => $stage,
+        };
+    }
+
+    /**
+     * 格式化封面URL
+     */
+    private function formatCoverUrl(?string $coverPath): string
+    {
+        if (empty($coverPath)) {
+            return '';
+        }
+
+        // 如果已经是完整URL,直接返回
+        if (str_starts_with($coverPath, 'http://') || str_starts_with($coverPath, 'https://')) {
+            return $coverPath;
+        }
+
+        // 本地存储路径,添加域名
+        return url('/storage/' . ltrim($coverPath, '/'));
+    }
+
+    /**
+     * 获取年级标签
+     */
+    private function getGradeLabel(?int $grade, string $stage): string
+    {
+        if ($grade === null) {
+            return '';
+        }
+
+        return match ($stage) {
+            'primary' => $grade . '年级',
+            'junior' => match ($grade) {
+                7 => '七年级',
+                8 => '八年级',
+                9 => '九年级',
+                default => $grade . '年级',
+            },
+            'senior' => match ($grade) {
+                10 => '高一',
+                11 => '高二',
+                12 => '高三',
+                default => '高' . ($grade - 9),
+            },
+            default => $grade . '年级',
+        };
+    }
+
     private function buildCatalogTree(array $nodes): array
     {
         // 建立ID索引,包含children数组