|
@@ -1795,10 +1795,16 @@ class ExamPdfExportService
|
|
|
*/
|
|
*/
|
|
|
private function fetchKnowledgeExplanationHtml(string $paperId): ?string
|
|
private function fetchKnowledgeExplanationHtml(string $paperId): ?string
|
|
|
{
|
|
{
|
|
|
|
|
+ $html = $this->renderKnowledgeExplanationHtmlFromView($paperId);
|
|
|
|
|
+ if ($html !== null) {
|
|
|
|
|
+ return $html;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
try {
|
|
try {
|
|
|
$url = route('filament.admin.auth.intelligent-exam.knowledge-explanation', ['paper_id' => $paperId]);
|
|
$url = route('filament.admin.auth.intelligent-exam.knowledge-explanation', ['paper_id' => $paperId]);
|
|
|
|
|
+ $timeout = max(1, (int) config('pdf.kp_explain_fetch_timeout_seconds', 2));
|
|
|
|
|
|
|
|
- $response = Http::get($url);
|
|
|
|
|
|
|
+ $response = Http::timeout($timeout)->get($url);
|
|
|
if ($response->successful()) {
|
|
if ($response->successful()) {
|
|
|
$html = $response->body();
|
|
$html = $response->body();
|
|
|
if (! empty(trim($html))) {
|
|
if (! empty(trim($html))) {
|
|
@@ -1817,6 +1823,7 @@ class ExamPdfExportService
|
|
|
Log::warning('ExamPdfExportService: 获取知识点讲解HTML失败', [
|
|
Log::warning('ExamPdfExportService: 获取知识点讲解HTML失败', [
|
|
|
'paper_id' => $paperId,
|
|
'paper_id' => $paperId,
|
|
|
'url' => $url,
|
|
'url' => $url,
|
|
|
|
|
+ 'timeout_seconds' => $timeout,
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
return null;
|
|
return null;
|
|
@@ -1825,6 +1832,92 @@ class ExamPdfExportService
|
|
|
Log::warning('ExamPdfExportService: 获取知识点讲解HTML异常', [
|
|
Log::warning('ExamPdfExportService: 获取知识点讲解HTML异常', [
|
|
|
'paper_id' => $paperId,
|
|
'paper_id' => $paperId,
|
|
|
'error' => $e->getMessage(),
|
|
'error' => $e->getMessage(),
|
|
|
|
|
+ 'timeout_seconds' => config('pdf.kp_explain_fetch_timeout_seconds', 2),
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function renderKnowledgeExplanationHtmlFromView(string $paperId): ?string
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $paper = Paper::query()->where('paper_id', $paperId)->first();
|
|
|
|
|
+ if (! $paper) {
|
|
|
|
|
+ Log::warning('ExamPdfExportService: 本地渲染知识点讲解失败,试卷不存在', ['paper_id' => $paperId]);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $studentModel = Student::find($paper->student_id);
|
|
|
|
|
+ $studentName = $studentModel->name ?? ($paper->student_id ?? '________');
|
|
|
|
|
+ $examCode = PaperNaming::extractExamCode((string) $paper->paper_id);
|
|
|
|
|
+ try {
|
|
|
|
|
+ $assembleTypeLabel = PaperNaming::assembleTypeLabel((int) $paper->paper_type);
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ $assembleTypeLabel = '未知类型';
|
|
|
|
|
+ }
|
|
|
|
|
+ $pdfMeta = [
|
|
|
|
|
+ 'student_name' => $studentName,
|
|
|
|
|
+ 'exam_code' => $examCode,
|
|
|
|
|
+ 'assemble_type_label' => $assembleTypeLabel,
|
|
|
|
|
+ 'header_title' => $examCode,
|
|
|
|
|
+ 'exam_pdf_title' => '试卷_'.$examCode,
|
|
|
|
|
+ 'grading_pdf_title' => '判卷_'.$examCode,
|
|
|
|
|
+ 'knowledge_pdf_title' => '知识点梳理_'.$examCode,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $kpCodes = $paper->explanation_kp_codes ?? [];
|
|
|
|
|
+ if (empty($kpCodes)) {
|
|
|
|
|
+ $paperQuestions = \App\Models\PaperQuestion::where('paper_id', $paperId)->get();
|
|
|
|
|
+ $seen = [];
|
|
|
|
|
+ $questionBankIds = $paperQuestions
|
|
|
|
|
+ ->pluck('question_bank_id')
|
|
|
|
|
+ ->filter()
|
|
|
|
|
+ ->unique()
|
|
|
|
|
+ ->values();
|
|
|
|
|
+ $questionKpMap = [];
|
|
|
|
|
+ if ($questionBankIds->isNotEmpty()) {
|
|
|
|
|
+ $questionKpMap = Question::whereIn('id', $questionBankIds)
|
|
|
|
|
+ ->pluck('kp_code', 'id')
|
|
|
|
|
+ ->toArray();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($paperQuestions as $paperQuestion) {
|
|
|
|
|
+ $kpCode = trim((string) ($paperQuestion->knowledge_point ?? ''));
|
|
|
|
|
+ if ($kpCode === '' && ! empty($paperQuestion->question_bank_id)) {
|
|
|
|
|
+ $kpCode = trim((string) ($questionKpMap[$paperQuestion->question_bank_id] ?? ''));
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($kpCode === '' || isset($seen[$kpCode])) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $seen[$kpCode] = true;
|
|
|
|
|
+ $kpCodes[] = $kpCode;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $html = view('pdf.exam-knowledge-explanation', [
|
|
|
|
|
+ 'paperId' => $paperId,
|
|
|
|
|
+ 'examCode' => $examCode,
|
|
|
|
|
+ 'studentName' => $studentName,
|
|
|
|
|
+ 'generateDateTime' => now()->format('Y年m月d日 H:i:s'),
|
|
|
|
|
+ 'knowledgePoints' => $this->buildExplanations($kpCodes),
|
|
|
|
|
+ 'pdfMeta' => $pdfMeta,
|
|
|
|
|
+ ])->render();
|
|
|
|
|
+
|
|
|
|
|
+ if (empty(trim($html))) {
|
|
|
|
|
+ Log::warning('ExamPdfExportService: 本地渲染知识点讲解结果为空', ['paper_id' => $paperId]);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $html = $this->ensureUtf8Html($html);
|
|
|
|
|
+
|
|
|
|
|
+ return $this->renderKpExplainMarkdown($html);
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ Log::warning('ExamPdfExportService: 本地渲染知识点讲解异常', [
|
|
|
|
|
+ 'paper_id' => $paperId,
|
|
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
return null;
|
|
return null;
|
|
@@ -1851,18 +1944,16 @@ class ExamPdfExportService
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 【新增】渲染试卷HTML(通过HTTP调用路由)
|
|
|
|
|
- */
|
|
|
|
|
private function renderExamHtml(string $paperId, bool $includeAnswer, bool $useGradingView): ?string
|
|
private function renderExamHtml(string $paperId, bool $includeAnswer, bool $useGradingView): ?string
|
|
|
{
|
|
{
|
|
|
- // 判卷部分启用答案详情页时,优先本地渲染,避免跨进程配置不一致。
|
|
|
|
|
- if ($useGradingView && config('exam.pdf_grading_append_scan_sheet', false)) {
|
|
|
|
|
- return $this->renderExamHtmlFromView($paperId, $includeAnswer, $useGradingView);
|
|
|
|
|
|
|
+ // PDF worker 已经运行在 Laravel 进程内,优先直接渲染 Blade,避免 HTTP 自调用吞掉数秒。
|
|
|
|
|
+ $html = $this->renderExamHtmlFromView($paperId, $includeAnswer, $useGradingView);
|
|
|
|
|
+ if ($html !== null) {
|
|
|
|
|
+ return $html;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 通过HTTP客户端获取渲染后的HTML(与知识点讲解相同的逻辑)
|
|
|
|
|
|
|
+ // 兜底:保留原 HTTP 路由渲染路径,避免特殊页面上下文下直接视图失败。
|
|
|
$routeName = $useGradingView
|
|
$routeName = $useGradingView
|
|
|
? 'filament.admin.auth.intelligent-exam.grading'
|
|
? 'filament.admin.auth.intelligent-exam.grading'
|
|
|
: 'filament.admin.auth.intelligent-exam.pdf';
|
|
: 'filament.admin.auth.intelligent-exam.pdf';
|