api.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. use App\Services\QuestionServiceApi;
  3. use Illuminate\Support\Facades\Route;
  4. use Illuminate\Support\Facades\Log;
  5. use App\Events\QuestionGenerationCompleted;
  6. use App\Events\QuestionGenerationFailed;
  7. /*
  8. |--------------------------------------------------------------------------
  9. | 题库管理 API 路由
  10. |--------------------------------------------------------------------------
  11. */
  12. // 接收题目生成回调
  13. Route::post('/questions/callback', function () {
  14. try {
  15. $data = request()->all();
  16. Log::info('Received question generation callback', $data);
  17. // 验证回调数据
  18. if (!isset($data['task_id']) || !isset($data['status'])) {
  19. return response()->json(['error' => 'Invalid callback data'], 400);
  20. }
  21. // 存储回调结果到 session 中,供前端查询
  22. session(['question_gen_callback_' . $data['task_id'] => $data]);
  23. // ✅ 记录任务状态,10秒后自动清除
  24. cache([$data['task_id'] => $data], now()->addSeconds(10));
  25. // ✅ 使用事件替代轮询 - 直接分发事件给前端
  26. if ($data['status'] === 'completed') {
  27. QuestionGenerationCompleted::dispatch(
  28. $data['task_id'],
  29. $data['kp_code'] ?? '',
  30. $data['total'] ?? 0
  31. );
  32. } elseif ($data['status'] === 'failed') {
  33. QuestionGenerationFailed::dispatch(
  34. $data['task_id'],
  35. $data['kp_code'] ?? '',
  36. $data['error'] ?? '未知错误'
  37. );
  38. }
  39. return response()->json([
  40. 'success' => true,
  41. 'message' => 'Callback received',
  42. 'status' => $data['status']
  43. ]);
  44. } catch (\Exception $e) {
  45. Log::error('Callback processing failed: ' . $e->getMessage());
  46. return response()->json(['error' => $e->getMessage()], 500);
  47. }
  48. })->name('api.questions.callback');
  49. // 获取题目生成回调结果
  50. Route::get('/questions/callback/{taskId}', function (string $taskId) {
  51. $callbackData = session('question_gen_callback_' . $taskId);
  52. if ($callbackData) {
  53. // 清除已读取的回调数据
  54. session()->forget('question_gen_callback_' . $taskId);
  55. return response()->json($callbackData);
  56. }
  57. return response()->json(['status' => 'pending'], 202);
  58. })->name('api.questions.callback.get');
  59. // 题目相关 API
  60. Route::get('/questions', function (QuestionServiceApi $service) {
  61. try {
  62. $page = (int) request()->get('page', 1);
  63. $perPage = (int) request()->get('per_page', 25);
  64. $filters = [
  65. 'kp_code' => request()->get('kp_code'),
  66. 'difficulty' => request()->get('difficulty'),
  67. 'search' => request()->get('search'),
  68. ];
  69. $response = $service->listQuestions($page, $perPage, $filters);
  70. return response()->json($response);
  71. } catch (\Exception $e) {
  72. \Log::error('Failed to fetch questions: ' . $e->getMessage());
  73. return response()->json([
  74. 'data' => [],
  75. 'meta' => [
  76. 'page' => 1,
  77. 'per_page' => 25,
  78. 'total' => 0,
  79. 'total_pages' => 0,
  80. ],
  81. 'error' => $e->getMessage(),
  82. ], 500);
  83. }
  84. });
  85. // 获取题目统计信息
  86. Route::get('/questions/statistics', function (QuestionServiceApi $service) {
  87. try {
  88. $stats = $service->getStatistics();
  89. return response()->json($stats);
  90. } catch (\Exception $e) {
  91. \Log::error('Failed to get question statistics: ' . $e->getMessage());
  92. return response()->json(['error' => $e->getMessage()], 500);
  93. }
  94. });
  95. // 语义搜索题目
  96. Route::post('/questions/search', function (QuestionServiceApi $service) {
  97. try {
  98. $data = request()->only(['query', 'limit']);
  99. $results = $service->searchQuestions($data['query'], $data['limit'] ?? 20);
  100. return response()->json($results);
  101. } catch (\Exception $e) {
  102. \Log::error('Question search failed: ' . $e->getMessage());
  103. return response()->json(['error' => $e->getMessage()], 500);
  104. }
  105. });
  106. // 获取单个题目详情
  107. Route::get('/questions/{id}', function (int $id, QuestionServiceApi $service) {
  108. try {
  109. $question = $service->getQuestionById($id);
  110. if (!$question) {
  111. return response()->json(['error' => 'Question not found'], 404);
  112. }
  113. return response()->json($question);
  114. } catch (\Exception $e) {
  115. \Log::error("Failed to get question {$id}: " . $e->getMessage());
  116. return response()->json(['error' => $e->getMessage()], 500);
  117. }
  118. });
  119. // AI 生成题目
  120. Route::post('/questions/generate', function (QuestionServiceApi $service) {
  121. try {
  122. $data = request()->only(['kp_code', 'keyword', 'count', 'strategy']);
  123. $result = $service->generateQuestions($data);
  124. return response()->json($result);
  125. } catch (\Exception $e) {
  126. \Log::error('Question generation failed: ' . $e->getMessage());
  127. return response()->json([
  128. 'success' => false,
  129. 'message' => $e->getMessage(),
  130. ], 500);
  131. }
  132. });
  133. // 删除题目
  134. Route::delete('/questions/{id}', function (int $id, QuestionServiceApi $service) {
  135. try {
  136. $deleted = $service->deleteQuestion($id);
  137. return response()->json([
  138. 'success' => $deleted,
  139. 'message' => $deleted ? 'Question deleted' : 'Failed to delete',
  140. ]);
  141. } catch (\Exception $e) {
  142. \Log::error("Failed to delete question {$id}: " . $e->getMessage());
  143. return response()->json([
  144. 'success' => false,
  145. 'message' => $e->getMessage(),
  146. ], 500);
  147. }
  148. });
  149. // 获取知识点选项
  150. Route::get('/knowledge-points', function (QuestionServiceApi $service) {
  151. try {
  152. $points = $service->getKnowledgePointOptions();
  153. return response()->json($points);
  154. } catch (\Exception $e) {
  155. \Log::error('Failed to get knowledge points: ' . $e->getMessage());
  156. return response()->json([], 500);
  157. }
  158. });
  159. /*
  160. |--------------------------------------------------------------------------
  161. | MathRecSys 集成 API 路由
  162. |--------------------------------------------------------------------------
  163. */
  164. use App\Http\Controllers\Api\StudentController;
  165. // 健康检查
  166. Route::get('/mathrecsys/health', [StudentController::class, 'checkServiceHealth'])->name('api.mathrecsys.health');
  167. // 学生相关 API
  168. Route::prefix('mathrecsys/students')->name('api.mathrecsys.students.')->group(function () {
  169. // 获取学生完整信息
  170. Route::get('{studentId}', [StudentController::class, 'show'])->name('show');
  171. // 获取个性化推荐
  172. Route::get('{studentId}/recommendations', [StudentController::class, 'getRecommendations'])->name('recommendations');
  173. // 获取学习轨迹
  174. Route::get('{studentId}/trajectory', [StudentController::class, 'getTrajectory'])->name('trajectory');
  175. // 获取学习建议
  176. Route::get('{studentId}/suggestions', [StudentController::class, 'getSuggestions'])->name('suggestions');
  177. // 智能分析题目
  178. Route::post('{studentId}/analyze', [StudentController::class, 'analyzeQuestion'])->name('analyze');
  179. // 更新掌握度
  180. Route::put('{studentId}/mastery', [StudentController::class, 'updateMastery'])->name('update-mastery');
  181. });
  182. // 班级分析 API
  183. Route::prefix('mathrecsys/classes')->name('api.mathrecsys.classes.')->group(function () {
  184. Route::get('{classId}/analysis', [StudentController::class, 'classAnalysis'])->name('analysis');
  185. });
  186. // 测试 API
  187. Route::get('/mathrecsys/test', function () {
  188. return response()->json([
  189. 'success' => true,
  190. 'message' => 'MathRecSys API integration is working',
  191. 'timestamp' => now()->toISOString()
  192. ]);
  193. })->name('api.mathrecsys.test');