Jelajahi Sumber

doc: 增加pdf生成的文档说明

过卫栋 4 hari lalu
induk
melakukan
9cb39ad677
1 mengubah file dengan 206 tambahan dan 0 penghapusan
  1. 206 0
      docs/pdf-generation.md

+ 206 - 0
docs/pdf-generation.md

@@ -0,0 +1,206 @@
+# 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行
+
+```php
+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-chrome` 或 `which chromium`
+2. 检查超时设置(当前90秒)
+3. 检查外部资源加载(KaTeX CDN等)
+
+### 问题4:HTML结构问题
+**症状**:页面样式异常或部分内容不显示
+
+**排查点**:
+1. 检查div标签是否正确闭合
+2. 使用 `PDF_DEBUG_SAVE_HTML=true` 保存HTML后在浏览器中检查
+
+## 调试方法
+
+### 1. 保存HTML副本
+```bash
+# 在 .env 中添加
+PDF_DEBUG_SAVE_HTML=true
+```
+HTML会保存到 `storage/app/debug_pdf_*.html`
+
+### 2. 查看队列日志
+```bash
+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. 检查题目数据
+```sql
+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)
+```php
+// 正确的正则(要求选项标记在行首或空白后)
+$pattern = '/(?:^|\s)([A-D])[\.、:.:]\s*(.+?)(?=(?:^|\s)[A-D][\.、:.:]|$)/su';
+
+// 错误的正则(会匹配SVG中的 BD:DC 等内容)
+$pattern = '/([A-D])[\.、:.:]\s*(.+?)(?=\s*[A-D][\.、:.:]|$)/su';
+```
+
+### 题干分离
+```php
+// 正确的正则
+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)
+
+```bash
+# 重启PDF队列
+docker compose restart pdf-worker
+
+# 查看队列状态
+docker exec math_cms_app php artisan queue:failed
+```
+
+## 配置文件
+
+- `config/pdf.php` - PDF调试设置