| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- <?php
- use App\Http\Controllers\Api\IntelligentExamController;
- use App\Services\QuestionServiceApi;
- use Illuminate\Support\Facades\Log;
- use Illuminate\Support\Facades\Route;
- use App\Events\QuestionGenerationCompleted;
- use App\Events\QuestionGenerationFailed;
- use Illuminate\Auth\Middleware\Authenticate;
- use App\Http\Controllers\Api\ExamAnalysisApiController;
- /*
- |--------------------------------------------------------------------------
- | 题库管理 API 路由
- |--------------------------------------------------------------------------
- */
- // 接收题目生成回调
- Route::post('/questions/callback', function () {
- try {
- $data = request()->all();
- Log::info('Received question generation callback', $data);
- // 验证回调数据
- if (!isset($data['task_id']) || !isset($data['status'])) {
- return response()->json(['error' => 'Invalid callback data'], 400);
- }
- // 处理回调数据并存储通知到session
- if ($data['status'] === 'completed') {
- $result = $data['result'] ?? [];
- $total = $result['total'] ?? $data['total'] ?? ($result['saved'] ?? 0);
- $kpCode = $result['kp_code'] ?? $data['kp_code'] ?? '';
- // 将成功通知存储到session,供下次页面刷新时显示
- session()->flash('notification', [
- 'type' => 'success',
- 'title' => '✅ 题目生成完成',
- 'body' => "任务 ID: {$data['task_id']}\n生成题目: {$total} 道" . ($kpCode ? "\n知识点: {$kpCode}" : ''),
- 'color' => 'success'
- ]);
- Log::info("题目生成成功通知已存储", [
- 'task_id' => $data['task_id'],
- 'total' => $total,
- 'kp_code' => $kpCode
- ]);
- } elseif ($data['status'] === 'failed') {
- $error = $data['error'] ?? '未知错误';
- // 将失败通知存储到session
- session()->flash('notification', [
- 'type' => 'error',
- 'title' => '❌ 题目生成失败',
- 'body' => "任务 ID: {$data['task_id']}\n错误: {$error}",
- 'color' => 'danger'
- ]);
- Log::error("题目生成失败通知已存储", [
- 'task_id' => $data['task_id'],
- 'error' => $error
- ]);
- }
- return response()->json([
- 'success' => true,
- 'message' => 'Callback received and notification stored',
- 'status' => $data['status']
- ]);
- } catch (\Exception $e) {
- Log::error('Callback processing failed: ' . $e->getMessage());
- return response()->json(['error' => $e->getMessage()], 500);
- }
- })->name('api.questions.callback');
- // 接收OCR题目生成回调
- Route::post('/ocr-question-callback', function () {
- try {
- $data = request()->all();
- Log::info('Received OCR question generation callback', $data);
- // 验证必要的回调数据
- if (!isset($data['task_id']) || !isset($data['status']) || !isset($data['ocr_record_id'])) {
- Log::error('OCR callback missing required fields', $data);
- return response()->json([
- 'success' => false,
- 'error' => 'Missing required fields: task_id, status, ocr_record_id'
- ], 400);
- }
- $taskId = $data['task_id'];
- $ocrRecordId = $data['ocr_record_id'];
- $status = $data['status'];
- // 将回调结果存储到缓存中,供前端查询(保留30秒)
- $cacheKey = "ocr_callback_{$ocrRecordId}_{$taskId}";
- cache([$cacheKey => $data], now()->addSeconds(30));
- Log::info("OCR callback cached with key: {$cacheKey}", [
- 'ocr_record_id' => $ocrRecordId,
- 'task_id' => $taskId,
- 'status' => $status,
- 'total_generated' => $data['result']['total_generated'] ?? 0,
- 'total_saved' => $data['result']['total_saved'] ?? 0
- ]);
- // 处理题目关联逻辑
- if ($status === 'completed') {
- $updatedCount = 0;
- // 从result中提取question_mappings(QuestionBank API将它放在result字段中)
- $mappings = $data['result']['question_mappings'] ?? $data['question_mappings'] ?? [];
- Log::info("Processing OCR question associations", [
- 'ocr_record_id' => $ocrRecordId,
- 'task_id' => $taskId,
- 'mappings_count' => count($mappings)
- ]);
- // 更新ocr_question_results表中的关联关系
- foreach ($mappings as $mapping) {
- try {
- $ocrQuestionNumber = $mapping['ocr_question_number'] ?? null;
- $questionBankId = $mapping['question_bank_id'] ?? null;
- $questionCode = $mapping['question_code'] ?? null;
- if ($ocrQuestionNumber && $questionBankId) {
- // 查找对应的OCR题目结果并更新
- $updated = DB::table('ocr_question_results')
- ->where('ocr_record_id', $ocrRecordId)
- ->where('question_number', $ocrQuestionNumber)
- ->update([
- 'question_bank_id' => $questionBankId,
- 'generation_status' => 'completed',
- 'generation_task_id' => $taskId,
- 'generation_error' => null,
- ]);
- if ($updated) {
- $updatedCount++;
- Log::info("Updated OCR question association", [
- 'ocr_record_id' => $ocrRecordId,
- 'question_number' => $ocrQuestionNumber,
- 'question_bank_id' => $questionBankId,
- 'question_code' => $questionCode
- ]);
- } else {
- Log::warning("No OCR question result found for association", [
- 'ocr_record_id' => $ocrRecordId,
- 'question_number' => $ocrQuestionNumber
- ]);
- }
- }
- } catch (\Exception $e) {
- Log::error("Failed to update OCR question association", [
- 'mapping' => $mapping,
- 'error' => $e->getMessage()
- ]);
- }
- }
- Log::info("OCR question association completed", [
- 'ocr_record_id' => $ocrRecordId,
- 'task_id' => $taskId,
- 'total_mappings' => count($mappings),
- 'updated_count' => $updatedCount
- ]);
- // 更新OCR记录的整体状态为已完成
- try {
- DB::table('ocr_records')
- ->where('id', $ocrRecordId)
- ->update([
- 'status' => 'completed',
- 'processed_at' => now(),
- 'updated_at' => now()
- ]);
- Log::info("Updated OCR record status to completed", [
- 'ocr_record_id' => $ocrRecordId,
- 'task_id' => $taskId
- ]);
- } catch (\Exception $e) {
- Log::error("Failed to update OCR record status", [
- 'ocr_record_id' => $ocrRecordId,
- 'error' => $e->getMessage()
- ]);
- }
- } elseif ($status === 'failed') {
- // 更新所有相关的OCR题目结果为失败状态
- try {
- $updated = DB::table('ocr_question_results')
- ->where('ocr_record_id', $ocrRecordId)
- ->where('generation_status', 'pending') // 只更新待处理的
- ->update([
- 'generation_status' => 'failed',
- 'generation_task_id' => $taskId,
- 'generation_error' => $data['error'] ?? 'Unknown error',
- ]);
- Log::info("Updated OCR questions to failed status", [
- 'ocr_record_id' => $ocrRecordId,
- 'task_id' => $taskId,
- 'updated_count' => $updated,
- 'error' => $data['error'] ?? 'Unknown error'
- ]);
- // 更新OCR记录的状态为失败
- DB::table('ocr_records')
- ->where('id', $ocrRecordId)
- ->update([
- 'status' => 'failed',
- 'error_message' => $data['error'] ?? 'Question generation failed',
- 'updated_at' => now()
- ]);
- Log::info("Updated OCR record status to failed", [
- 'ocr_record_id' => $ocrRecordId,
- 'task_id' => $taskId,
- 'error' => $data['error'] ?? 'Unknown error'
- ]);
- } catch (\Exception $e) {
- Log::error("Failed to update OCR questions to failed status", [
- 'ocr_record_id' => $ocrRecordId,
- 'error' => $e->getMessage()
- ]);
- }
- }
- return response()->json([
- 'success' => true,
- 'message' => 'OCR callback received and processed',
- 'data' => [
- 'task_id' => $taskId,
- 'ocr_record_id' => $ocrRecordId,
- 'status' => $status,
- 'cache_key' => $cacheKey,
- 'associations_processed' => $status === 'completed' ? count($data['question_mappings'] ?? []) : 0
- ]
- ]);
- } catch (\Exception $e) {
- Log::error('OCR callback processing failed: ' . $e->getMessage());
- Log::error('Exception details: ' . $e->getTraceAsString());
- return response()->json([
- 'success' => false,
- 'error' => 'Callback processing failed: ' . $e->getMessage()
- ], 500);
- }
- })->name('api.ocr.callback');
- // 获取题目生成回调结果
- Route::get('/questions/callback/{taskId}', function (string $taskId) {
- // ✅ 优先从缓存读取(跨域友好)
- $callbackData = cache($taskId);
- if ($callbackData) {
- // 清除已读取的回调数据
- cache()->forget($taskId);
- session()->forget('question_gen_callback_' . $taskId);
- return response()->json($callbackData);
- }
- // 备选:从session读取
- $sessionData = session('question_gen_callback_' . $taskId);
- if ($sessionData) {
- // 清除已读取的回调数据
- session()->forget('question_gen_callback_' . $taskId);
- return response()->json($sessionData);
- }
- // 未收到回调
- return response()->json(['status' => 'pending'], 202);
- })->name('api.questions.callback.get');
- // 获取OCR题目生成回调结果
- Route::get('/ocr-question-callback/{ocrRecordId}/{taskId}', function (int $ocrRecordId, string $taskId) {
- $cacheKey = "ocr_callback_{$ocrRecordId}_{$taskId}";
- $callbackData = cache($cacheKey);
- if ($callbackData) {
- // 清除已读取的回调数据
- cache()->forget($cacheKey);
- return response()->json([
- 'success' => true,
- 'data' => $callbackData
- ]);
- }
- return response()->json([
- 'success' => false,
- 'status' => 'pending',
- 'message' => 'OCR callback not received yet'
- ], 202);
- })->name('api.ocr.callback.get');
- // 题目相关 API
- Route::get('/questions', function (QuestionServiceApi $service) {
- try {
- $page = (int) request()->get('page', 1);
- $perPage = (int) request()->get('per_page', 25);
- $filters = [
- 'kp_code' => request()->get('kp_code'),
- 'difficulty' => request()->get('difficulty'),
- 'search' => request()->get('search'),
- ];
- $response = $service->listQuestions($page, $perPage, $filters);
- return response()->json($response);
- } catch (\Exception $e) {
- \Log::error('Failed to fetch questions: ' . $e->getMessage());
- return response()->json([
- 'data' => [],
- 'meta' => [
- 'page' => 1,
- 'per_page' => 25,
- 'total' => 0,
- 'total_pages' => 0,
- ],
- 'error' => $e->getMessage(),
- ], 500);
- }
- });
- // 获取题目统计信息
- Route::get('/questions/statistics', function (QuestionServiceApi $service) {
- try {
- $stats = $service->getStatistics();
- return response()->json($stats);
- } catch (\Exception $e) {
- \Log::error('Failed to get question statistics: ' . $e->getMessage());
- return response()->json(['error' => $e->getMessage()], 500);
- }
- });
- // 语义搜索题目
- Route::post('/questions/search', function (QuestionServiceApi $service) {
- try {
- $data = request()->only(['query', 'limit']);
- $results = $service->searchQuestions($data['query'], $data['limit'] ?? 20);
- return response()->json($results);
- } catch (\Exception $e) {
- \Log::error('Question search failed: ' . $e->getMessage());
- return response()->json(['error' => $e->getMessage()], 500);
- }
- });
- // 获取单个题目详情
- Route::get('/questions/{id}', function (int $id, QuestionServiceApi $service) {
- try {
- $question = $service->getQuestionById($id);
- if (!$question) {
- return response()->json(['error' => 'Question not found'], 404);
- }
- return response()->json($question);
- } catch (\Exception $e) {
- \Log::error("Failed to get question {$id}: " . $e->getMessage());
- return response()->json(['error' => $e->getMessage()], 500);
- }
- });
- // AI 生成题目
- Route::post('/questions/generate', function (QuestionServiceApi $service) {
- try {
- $data = request()->only(['kp_code', 'keyword', 'count', 'strategy']);
- $result = $service->generateQuestions($data);
- return response()->json($result);
- } catch (\Exception $e) {
- \Log::error('Question generation failed: ' . $e->getMessage());
- return response()->json([
- 'success' => false,
- 'message' => $e->getMessage(),
- ], 500);
- }
- });
- // 删除题目
- Route::delete('/questions/{id}', function (int $id, QuestionServiceApi $service) {
- try {
- $deleted = $service->deleteQuestion($id);
- return response()->json([
- 'success' => $deleted,
- 'message' => $deleted ? 'Question deleted' : 'Failed to delete',
- ]);
- } catch (\Exception $e) {
- \Log::error("Failed to delete question {$id}: " . $e->getMessage());
- return response()->json([
- 'success' => false,
- 'message' => $e->getMessage(),
- ], 500);
- }
- });
- // 获取知识点选项
- Route::get('/knowledge-points', function (QuestionServiceApi $service) {
- try {
- $points = $service->getKnowledgePointOptions();
- return response()->json($points);
- } catch (\Exception $e) {
- \Log::error('Failed to get knowledge points: ' . $e->getMessage());
- return response()->json([], 500);
- }
- });
- // 智能出卷对外接口:生成试卷并返回PDF/判卷地址
- Route::post('/intelligent-exams', [IntelligentExamController::class, 'store'])
- ->withoutMiddleware([
- Authenticate::class,
- 'auth',
- 'auth:sanctum',
- 'auth:api',
- ])
- ->name('api.intelligent-exams.store');
- // 学情报告对外接口:生成并返回学情报告 PDF
- Route::post('/exam-analysis/report', [ExamAnalysisApiController::class, 'store'])
- ->withoutMiddleware([
- Authenticate::class,
- 'auth',
- 'auth:sanctum',
- 'auth:api',
- ])
- ->name('api.exam-analysis.report');
- /*
- |--------------------------------------------------------------------------
- | MathRecSys 集成 API 路由
- |--------------------------------------------------------------------------
- */
- use App\Http\Controllers\Api\StudentController;
- // 健康检查
- Route::get('/mathrecsys/health', [StudentController::class, 'checkServiceHealth'])->name('api.mathrecsys.health');
- // 学生相关 API
- Route::prefix('mathrecsys/students')->name('api.mathrecsys.students.')->group(function () {
- // 获取学生完整信息
- Route::get('{studentId}', [StudentController::class, 'show'])->name('show');
- // 获取个性化推荐
- Route::get('{studentId}/recommendations', [StudentController::class, 'getRecommendations'])->name('recommendations');
- // 获取学习轨迹
- Route::get('{studentId}/trajectory', [StudentController::class, 'getTrajectory'])->name('trajectory');
- // 获取学习建议
- Route::get('{studentId}/suggestions', [StudentController::class, 'getSuggestions'])->name('suggestions');
- // 智能分析题目
- Route::post('{studentId}/analyze', [StudentController::class, 'analyzeQuestion'])->name('analyze');
- // 更新掌握度
- Route::put('{studentId}/mastery', [StudentController::class, 'updateMastery'])->name('update-mastery');
- });
- // 班级分析 API
- Route::prefix('mathrecsys/classes')->name('api.mathrecsys.classes.')->group(function () {
- Route::get('{classId}/analysis', [StudentController::class, 'classAnalysis'])->name('analysis');
- });
- // 测试 API
- Route::get('/mathrecsys/test', function () {
- return response()->json([
- 'success' => true,
- 'message' => 'MathRecSys API integration is working',
- 'timestamp' => now()->toISOString()
- ]);
- })->name('api.mathrecsys.test');
- // 测试OCR题目生成API调用
- Route::post('/test-ocr-generation', function () {
- try {
- $service = new \App\Services\QuestionBankService();
- // 模拟前端传递的OCR题目数据
- $questions = [
- [
- 'id' => 1,
- 'content' => '计算:2+3-4'
- ],
- [
- 'id' => 2,
- 'content' => '解方程:x+5=10'
- ]
- ];
- Log::info('开始测试OCR题目生成', [
- 'questions_count' => count($questions),
- 'ocr_record_id' => 12
- ]);
- // 使用异步API,系统自动生成回调URL
- $response = $service->generateQuestionsFromOcrAsync(
- $questions,
- '高一',
- '数学',
- 12, // OCR记录ID
- null, // 让系统自动生成回调URL
- 'api.ocr.callback' // 回调路由名称
- );
- Log::info('OCR题目生成响应', [
- 'response' => $response,
- 'status' => $response['status'] ?? 'unknown',
- 'task_id' => $response['task_id'] ?? 'N/A'
- ]);
- return response()->json([
- 'success' => true,
- 'message' => 'OCR题目生成测试完成',
- 'data' => $response
- ]);
- } catch (\Exception $e) {
- Log::error('测试OCR题目生成失败', [
- 'error' => $e->getMessage(),
- 'trace' => $e->getTraceAsString()
- ]);
- return response()->json([
- 'success' => false,
- 'error' => $e->getMessage()
- ], 500);
- }
- })->name('api.test.ocr.generation');
|