pdf-generation.md 6.1 KB

PDF 生成系统文档

本文档记录试卷PDF生成的完整流程、涉及的文件和常见问题排查点。

生成流程概览

API请求 /api/intelligent-exams
    ↓
IntelligentExamController::store()
    ↓
保存试卷到数据库 (papers + paper_questions 表)
    ↓
dispatch(GenerateExamPdfJob)  → 加入 pdf 队列
    ↓
GenerateExamPdfJob::handle()
    ↓
ExamPdfExportService::generateUnifiedPdf()
    ↓
renderExamHtml() × 2  (试卷HTML + 判卷HTML)
    ↓
HTTP请求 → ExamPdfController::show() / showGrading()
    ↓
Blade模板渲染 (exam-paper.blade.php + paper-body.blade.php)
    ↓
mergeHtmlWithPageBreak()  合并两个HTML
    ↓
buildPdf() → renderWithChrome()  Chrome Headless 转 PDF
    ↓
上传到OSS,返回URL

涉及的关键文件

1. API入口

  • app/Http/Controllers/Api/IntelligentExamController.php
    • store() - 创建试卷的API入口
    • triggerPdfGeneration() - 触发PDF生成队列任务

2. 队列任务

  • app/Jobs/GenerateExamPdfJob.php
    • pdf 队列中执行
    • 调用 ExamPdfExportService::generateUnifiedPdf()

3. PDF生成服务

  • app/Services/ExamPdfExportService.php
    • generateUnifiedPdf() - 生成统一PDF(试卷+判卷)
    • renderExamHtml() - 通过HTTP请求获取渲染后的HTML
    • mergeHtmlWithPageBreak() - 合并两个HTML页面
    • buildPdf() - 调用Chrome生成PDF
    • renderWithChrome() - Chrome Headless渲染

4. HTML渲染控制器

  • app/Http/Controllers/ExamPdfController.php
    • show() - 渲染试卷HTML(路由:/admin/intelligent-exam/pdf/{paper_id}
    • showGrading() - 渲染判卷HTML(路由:/admin/intelligent-exam/grading/{paper_id}
    • extractOptions() - 从题目内容中提取选项
    • separateStemAndOptions() - 分离题干和选项
    • normalizeQuestionTypeValue() - 标准化题目类型
    • determineQuestionType() - 根据内容推断题目类型

5. Blade模板

  • resources/views/pdf/exam-paper.blade.php - 试卷主模板
  • resources/views/pdf/exam-grading.blade.php - 判卷主模板
  • resources/views/components/exam/paper-body.blade.php - 题目渲染组件(核心)

6. 数学公式处理

  • app/Services/MathFormulaProcessor.php - LaTeX公式处理

7. 路由配置

  • routes/web.php 第15-16行

    Route::get('/admin/intelligent-exam/pdf/{paper_id}', [ExamPdfController::class, 'show'])
    ->name('filament.admin.auth.intelligent-exam.pdf');
    Route::get('/admin/intelligent-exam/grading/{paper_id}', [ExamPdfController::class, 'showGrading'])
    ->name('filament.admin.auth.intelligent-exam.grading');
    

数据流

数据库表

  • papers - 试卷基本信息
  • paper_questions - 试卷题目关联
    • question_number - 题目序号
    • question_type - 题目类型 (choice/fill/answer)
    • question_bank_id - 题库题目ID
    • question_text - 题目内容

题目分类流程

paper_questions 表数据
    ↓
ExamPdfController::show()
    ↓
按 question_type 字段分类到 ['choice' => [], 'fill' => [], 'answer' => []]
    ↓
传递给 Blade 模板
    ↓
paper-body.blade.php 按类型渲染

常见问题排查

问题1:题目丢失

症状:网页能正常显示所有题目,但PDF中缺少部分题目

排查点

  1. 检查 ExamPdfController 中的正则表达式是否误匹配SVG内容
  2. 检查 paper-body.blade.php 中的正则表达式
  3. 开启调试:设置环境变量 PDF_DEBUG_SAVE_HTML=true,查看生成的HTML

已修复的问题(2026-01-05):

  • SVG中的坐标标注(如BD:DC)被误识别为选项标记(如D:
  • 修复方法:正则表达式添加 (?:^|\s) 前缀,要求选项标记在行首或空白后

问题2:题目类型分类错误

症状:选择题被分到填空题,或反之

排查点

  1. 检查 paper_questions.question_type 字段是否有值
  2. 检查 normalizeQuestionTypeValue() 方法
  3. 检查 determineQuestionType() 方法的推断逻辑

已修复的问题(2026-01-05):

  • 选择题因含有()被误判为填空题
  • 修复方法:优先使用 question_type 字段,而非根据内容推断

问题3:Chrome渲染问题

症状:PDF生成失败或内容不完整

排查点

  1. 检查Chrome是否安装:which google-chromewhich chromium
  2. 检查超时设置(当前90秒)
  3. 检查外部资源加载(KaTeX CDN等)

问题4:HTML结构问题

症状:页面样式异常或部分内容不显示

排查点

  1. 检查div标签是否正确闭合
  2. 使用 PDF_DEBUG_SAVE_HTML=true 保存HTML后在浏览器中检查

调试方法

1. 保存HTML副本

# 在 .env 中添加
PDF_DEBUG_SAVE_HTML=true

HTML会保存到 storage/app/debug_pdf_*.html

2. 查看队列日志

docker exec math_cms_app tail -100 storage/logs/laravel.log | grep -E "(PDF|ExamPdf|题目)"

3. 手动测试HTML渲染

直接访问URL查看HTML渲染结果:

/admin/intelligent-exam/pdf/{paper_id}?answer=false  # 试卷
/admin/intelligent-exam/grading/{paper_id}           # 判卷

4. 检查题目数据

SELECT question_number, question_type, question_bank_id
FROM paper_questions
WHERE paper_id = 'paper_xxx'
ORDER BY question_number;

关键正则表达式

选项提取(ExamPdfController + paper-body.blade.php)

// 正确的正则(要求选项标记在行首或空白后)
$pattern = '/(?:^|\s)([A-D])[\.、:.:]\s*(.+?)(?=(?:^|\s)[A-D][\.、:.:]|$)/su';

// 错误的正则(会匹配SVG中的 BD:DC 等内容)
$pattern = '/([A-D])[\.、:.:]\s*(.+?)(?=\s*[A-D][\.、:.:]|$)/su';

题干分离

// 正确的正则
preg_match('/^(.+?)(?=(?:^|\s)[A-D][\.、:.:])/su', $content, $match);

Docker服务

  • math_cms_app - 主应用 (Nginx + PHP-FPM)
  • math_cms_queue - 默认队列 worker
  • math_cms_pdf - PDF队列 worker(带Chrome)

    # 重启PDF队列
    docker compose restart pdf-worker
    
    # 查看队列状态
    docker exec math_cms_app php artisan queue:failed
    

配置文件

  • config/pdf.php - PDF调试设置