ExamAnalysisApiController.php 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. <?php
  2. namespace App\Http\Controllers\Api;
  3. use App\Http\Controllers\Controller;
  4. use App\Services\ExamAnalysisService;
  5. use App\Services\TaskManager;
  6. use Illuminate\Http\JsonResponse;
  7. use Illuminate\Http\Request;
  8. use Illuminate\Support\Facades\Log;
  9. class ExamAnalysisApiController extends Controller
  10. {
  11. public function __construct(
  12. private readonly ExamAnalysisService $examAnalysisService,
  13. private readonly TaskManager $taskManager
  14. ) {}
  15. /**
  16. * 生成学情报告(异步模式)
  17. * 立即返回任务ID,PDF生成在后台进行
  18. */
  19. public function store(Request $request): JsonResponse
  20. {
  21. $data = $request->validate([
  22. 'paper_id' => 'required|string',
  23. 'student_id' => 'nullable|string',
  24. 'record_id' => 'nullable|string',
  25. 'callback_url' => 'nullable|url',
  26. ]);
  27. $paperId = $data['paper_id'];
  28. $studentId = $data['student_id'] ?? null;
  29. $recordId = $data['record_id'] ?? null;
  30. try {
  31. // 如果没有提供student_id,尝试从paper_id获取
  32. if (!$studentId) {
  33. $studentId = $this->getStudentIdFromPaper($paperId);
  34. }
  35. if (!$studentId) {
  36. return response()->json([
  37. 'success' => false,
  38. 'message' => '缺少 student_id',
  39. ], 422);
  40. }
  41. // 使用ExamAnalysisService生成报告
  42. $taskId = $this->examAnalysisService->generateReport($paperId, $studentId, $recordId);
  43. // 构建返回数据
  44. $payload = [
  45. 'success' => true,
  46. 'message' => '学情报告任务已创建,正在后台生成PDF...',
  47. 'data' => [
  48. 'task_id' => $taskId,
  49. 'paper_id' => $paperId,
  50. 'student_id' => $studentId,
  51. 'record_id' => $recordId,
  52. 'status' => 'processing',
  53. 'analysis_url' => route('filament.admin.pages.exam-analysis', [
  54. 'paperId' => $paperId,
  55. 'studentId' => $studentId,
  56. 'recordId' => $recordId,
  57. ]),
  58. 'pdf_url' => null, // 稍后生成
  59. 'created_at' => now()->toISOString(),
  60. ],
  61. ];
  62. return response()->json($payload, 200, [], JSON_UNESCAPED_SLASHES);
  63. } catch (\Exception $e) {
  64. Log::error('学情报告API失败', [
  65. 'paper_id' => $paperId,
  66. 'student_id' => $studentId,
  67. 'record_id' => $recordId,
  68. 'error' => $e->getMessage(),
  69. ]);
  70. return response()->json([
  71. 'success' => false,
  72. 'message' => '服务异常:' . $e->getMessage(),
  73. ], 500);
  74. }
  75. }
  76. /**
  77. * 轮询任务状态
  78. */
  79. public function status(string $taskId): JsonResponse
  80. {
  81. try {
  82. $task = $this->taskManager->getTaskStatus($taskId);
  83. if (!$task) {
  84. return response()->json([
  85. 'success' => false,
  86. 'message' => '任务不存在',
  87. ], 404);
  88. }
  89. return response()->json([
  90. 'success' => true,
  91. 'data' => $task,
  92. ]);
  93. } catch (\Exception $e) {
  94. Log::error('查询学情报告任务状态失败', [
  95. 'task_id' => $taskId,
  96. 'error' => $e->getMessage(),
  97. ]);
  98. return response()->json([
  99. 'success' => false,
  100. 'message' => '查询失败:' . $e->getMessage(),
  101. ], 500);
  102. }
  103. }
  104. /**
  105. * 获取PDF报告URL
  106. * 查询指定试卷是否已有生成好的学情分析报告
  107. */
  108. public function getPdfUrl(string $paperId): JsonResponse
  109. {
  110. try {
  111. // 首先尝试从 student_reports 表直接查询(最快速的方式)
  112. $report = \App\Models\StudentReport::where('exam_id', $paperId)
  113. ->where('report_type', 'exam_analysis')
  114. ->first();
  115. if ($report && $report->pdf_url && $report->generation_status === 'completed') {
  116. Log::info('学情报告PDF URL查询成功(从数据库)', [
  117. 'paper_id' => $paperId,
  118. 'pdf_url' => $report->pdf_url,
  119. ]);
  120. return response()->json([
  121. 'success' => true,
  122. 'data' => [
  123. 'paper_id' => $paperId,
  124. 'status' => 'completed',
  125. 'pdf_url' => $report->pdf_url,
  126. 'message' => '报告已生成',
  127. 'generated_at' => $report->generated_at?->toISOString(),
  128. ]
  129. ], 200, [], JSON_UNESCAPED_SLASHES);
  130. }
  131. // 如果数据库中没有,尝试从任务系统查找
  132. $task = $this->taskManager->findAnalysisTaskByPaperId($paperId);
  133. if ($task) {
  134. $status = $task['status'];
  135. $pdfUrl = $task['pdf_url'] ?? null;
  136. if ($status === \App\Services\TaskManager::STATUS_COMPLETED && $pdfUrl) {
  137. // 任务已完成且有PDF URL
  138. Log::info('学情报告PDF URL查询成功(从任务系统)', [
  139. 'paper_id' => $paperId,
  140. 'task_id' => $task['task_id'],
  141. 'pdf_url' => $pdfUrl,
  142. ]);
  143. return response()->json([
  144. 'success' => true,
  145. 'data' => [
  146. 'paper_id' => $paperId,
  147. 'status' => 'completed',
  148. 'pdf_url' => $pdfUrl,
  149. 'message' => '报告已生成',
  150. 'generated_at' => $task['completed_at'] ?? null,
  151. ]
  152. ], 200, [], JSON_UNESCAPED_SLASHES);
  153. } elseif ($status === \App\Services\TaskManager::STATUS_PROCESSING) {
  154. // 任务正在处理中
  155. Log::info('学情报告正在生成中', [
  156. 'paper_id' => $paperId,
  157. 'task_id' => $task['task_id'],
  158. 'progress' => $task['progress'] ?? 0,
  159. ]);
  160. return response()->json([
  161. 'success' => true,
  162. 'data' => [
  163. 'paper_id' => $paperId,
  164. 'status' => 'processing',
  165. 'pdf_url' => null,
  166. 'message' => '报告正在生成中,请稍后刷新页面查看',
  167. 'progress' => $task['progress'] ?? 0,
  168. ]
  169. ], 200, [], JSON_UNESCAPED_SLASHES);
  170. } elseif ($status === \App\Services\TaskManager::STATUS_FAILED) {
  171. // 任务失败
  172. Log::warning('学情报告生成失败', [
  173. 'paper_id' => $paperId,
  174. 'task_id' => $task['task_id'],
  175. 'error' => $task['error'] ?? '未知错误',
  176. ]);
  177. return response()->json([
  178. 'success' => false,
  179. 'data' => [
  180. 'paper_id' => $paperId,
  181. 'status' => 'failed',
  182. 'pdf_url' => null,
  183. 'message' => '报告生成失败:' . ($task['error'] ?? '未知错误'),
  184. ]
  185. ], 200, [], JSON_UNESCAPED_SLASHES);
  186. }
  187. }
  188. // 既没有完成的任务,也没有正在进行的任务
  189. Log::info('未找到学情报告任务', ['paper_id' => $paperId]);
  190. return response()->json([
  191. 'success' => true,
  192. 'data' => [
  193. 'paper_id' => $paperId,
  194. 'status' => 'not_found',
  195. 'pdf_url' => null,
  196. 'message' => '报告尚未生成,请先提交试卷进行分析',
  197. ]
  198. ], 200, [], JSON_UNESCAPED_SLASHES);
  199. } catch (\Exception $e) {
  200. Log::error('查询学情报告PDF URL失败', [
  201. 'paper_id' => $paperId,
  202. 'error' => $e->getMessage(),
  203. ]);
  204. return response()->json([
  205. 'success' => false,
  206. 'message' => '查询失败:' . $e->getMessage(),
  207. ], 500);
  208. }
  209. }
  210. /**
  211. * 从试卷ID获取学生ID
  212. */
  213. private function getStudentIdFromPaper(string $paperId): ?string
  214. {
  215. try {
  216. $paper = \App\Models\Paper::find($paperId);
  217. return $paper?->student_id;
  218. } catch (\Exception $e) {
  219. Log::warning('获取试卷学生ID失败', [
  220. 'paper_id' => $paperId,
  221. 'error' => $e->getMessage(),
  222. ]);
  223. return null;
  224. }
  225. }
  226. }