Browse Source

fix: 继续调试修复pdf问题,把外部cdn资源改成本地

大侠咬超人 2 days ago
parent
commit
496116ca3d
2 changed files with 149 additions and 10 deletions
  1. 101 7
      app/Services/ExamPdfExportService.php
  2. 48 3
      docs/ops-commands.md

+ 101 - 7
app/Services/ExamPdfExportService.php

@@ -1059,7 +1059,7 @@ class ExamPdfExportService
             '--disable-web-security',
             '--disable-features=VizDisplayCompositor',
             '--disable-extensions',
-            '--disable-background-networking',
+            // '--disable-background-networking',  // 注释掉,可能阻止必要的网络请求
             '--disable-component-update',
             '--disable-client-side-phishing-detection',
             '--disable-default-apps',
@@ -1082,6 +1082,9 @@ class ExamPdfExportService
             '--disable-extensions-http-throttling',
             '--disable-ipc-flooding-protection',
             '--disable-features=Dbus',    // 禁用 dbus
+            // 【关键修复】添加虚拟时间预算,让Chrome有足够时间加载CDN资源和执行JS
+            '--virtual-time-budget=30000',  // 30秒虚拟时间用于加载外部资源
+            '--run-all-compositor-stages-before-draw',  // 确保所有渲染完成后再生成PDF
             '--user-data-dir=' . $userDataDir,
             '--print-to-pdf=' . $tmpPdf,
             '--print-to-pdf-no-header',
@@ -1094,7 +1097,7 @@ class ExamPdfExportService
             'XDG_RUNTIME_DIR' => $runtimeXdg,
         ]);
 
-        $process->setTimeout(60); // 【优化】减少超时到60秒
+        $process->setTimeout(90); // 【修复】增加超时时间到90秒
         $killSignal = \defined('SIGKILL') ? \SIGKILL : 9;
 
         Log::warning('ExamPdfExportService: [调试] Chrome命令准备执行', [
@@ -1111,9 +1114,9 @@ class ExamPdfExportService
             $process->start();
             $pdfGenerated = false;
 
-            // 轮询检测PDF是否生成(【优化】减少超时和间隔)
+            // 轮询检测PDF是否生成
             $pollStart = microtime(true);
-            $maxPollSeconds = 50; // 【优化】从90秒减少到50秒
+            $maxPollSeconds = 80; // 【修复】增加轮询超时到80秒
             while ($process->isRunning() && (microtime(true) - $pollStart) < $maxPollSeconds) {
                 if (file_exists($tmpPdf) && filesize($tmpPdf) > 0) {
                     $pdfGenerated = true;
@@ -1221,15 +1224,106 @@ class ExamPdfExportService
     }
 
     /**
-     * 确保HTML为UTF-8编码
+     * 确保HTML为UTF-8编码,并内联外部资源
      */
     private function ensureUtf8Html(string $html): string
     {
         $meta = '<meta charset="UTF-8">';
         if (stripos($html, '<head>') !== false) {
-            return preg_replace('/<head>/i', "<head>{$meta}", $html, 1);
+            $html = preg_replace('/<head>/i', "<head>{$meta}", $html, 1);
+        } else {
+            $html = $meta . $html;
+        }
+
+        // 【关键修复】内联KaTeX CSS,避免Chrome在容器中加载CDN资源超时
+        $html = $this->inlineExternalResources($html);
+
+        return $html;
+    }
+
+    /**
+     * 将CDN资源替换为内联资源
+     * 【关键修复】避免Chrome在容器中加载CDN资源超时
+     */
+    private function inlineExternalResources(string $html): string
+    {
+        // 检查是否包含KaTeX CDN链接
+        if (strpos($html, 'cdn.jsdelivr.net/npm/katex') === false) {
+            return $html;
+        }
+
+        try {
+            // 读取本地KaTeX CSS文件并内联
+            $katexCssPath = public_path('css/katex/katex.min.css');
+            if (file_exists($katexCssPath)) {
+                $katexCss = file_get_contents($katexCssPath);
+
+                // 修复字体路径:将相对路径改为绝对路径(使用data URI或绝对路径)
+                // KaTeX CSS中的字体引用格式: url(fonts/KaTeX_xxx.woff2)
+                $fontsDir = public_path('css/katex/fonts');
+                $katexCss = preg_replace_callback(
+                    '/url\(["\']?fonts\/([^"\')\s]+)["\']?\)/i',
+                    function ($matches) use ($fontsDir) {
+                        $fontFile = $fontsDir . '/' . $matches[1];
+                        if (file_exists($fontFile)) {
+                            // 将字体转换为data URI(适用于PDF生成)
+                            $fontData = base64_encode(file_get_contents($fontFile));
+                            $mimeType = str_ends_with($matches[1], '.woff2') ? 'font/woff2' : 'font/woff';
+                            return 'url(data:' . $mimeType . ';base64,' . $fontData . ')';
+                        }
+                        return $matches[0];
+                    },
+                    $katexCss
+                );
+
+                // 替换CDN CSS链接为内联样式
+                $html = preg_replace(
+                    '/<link[^>]*href=["\']https:\/\/cdn\.jsdelivr\.net\/npm\/katex[^"\']*katex\.min\.css["\'][^>]*>/i',
+                    '<style type="text/css">' . $katexCss . '</style>',
+                    $html
+                );
+
+                Log::info('ExamPdfExportService: KaTeX CSS已内联(含字体data URI)');
+            }
+
+            // 读取本地KaTeX JS并内联
+            $katexJsPath = public_path('js/katex.min.js');
+            $autoRenderJsPath = public_path('js/auto-render.min.js');
+
+            if (file_exists($katexJsPath)) {
+                $katexJs = file_get_contents($katexJsPath);
+                $html = preg_replace(
+                    '/<script[^>]*src=["\']https:\/\/cdn\.jsdelivr\.net\/npm\/katex[^"\']*katex\.min\.js["\'][^>]*><\/script>/i',
+                    '<script type="text/javascript">' . $katexJs . '</script>',
+                    $html
+                );
+            }
+
+            if (file_exists($autoRenderJsPath)) {
+                $autoRenderJs = file_get_contents($autoRenderJsPath);
+                $html = preg_replace(
+                    '/<script[^>]*src=["\']https:\/\/cdn\.jsdelivr\.net\/npm\/katex[^"\']*auto-render[^"\']*["\'][^>]*><\/script>/i',
+                    '<script type="text/javascript">' . $autoRenderJs . '</script>',
+                    $html
+                );
+            }
+
+            // 修改渲染脚本,确保在DOMContentLoaded后立即渲染(不使用setTimeout延迟)
+            $html = preg_replace(
+                '/setTimeout\s*\(\s*renderMath\s*,\s*\d+\s*\)\s*;?\s*setTimeout\s*\(\s*renderMath\s*,\s*\d+\s*\)\s*;?/i',
+                '/* 移除延迟渲染,PDF生成时立即渲染 */',
+                $html
+            );
+
+            Log::info('ExamPdfExportService: 已将KaTeX CDN资源替换为内联资源');
+
+        } catch (\Exception $e) {
+            Log::warning('ExamPdfExportService: 内联资源处理失败,保留原始HTML', [
+                'error' => $e->getMessage()
+            ]);
         }
-        return $meta . $html;
+
+        return $html;
     }
 
     /**

+ 48 - 3
docs/ops-commands.md

@@ -49,7 +49,7 @@ git pull
 
 # 2. 重新构建并启动
 docker compose down
-docker compose build
+docker compose build --no-cache
 docker compose up -d
 
 # 或一键操作
@@ -182,9 +182,12 @@ docker update --cpus=1 --memory=1g math_cms_pdf
 ### 常用日志查看
 
 ```
-docker exec math_cms_app grep -A 10 "exam-answer-analysis\|ERROR" storage/logs/laravel.log | tail -50
+   docker exec math_cms_app grep -A 10 "exam-answer-analysis\|ERROR" storage/logs/laravel.log | tail -50
 
-tail -200 storage/logs/laravel.log
+  tail -200 storage/logs/laravel.log
+
+  # 清空日志
+  docker exec math_cms_app truncate -s 0 storage/logs/laravel.log
 
   # 实时监控(自动刷新)
   docker exec math_cms_app tail -f storage/logs/laravel.log
@@ -192,3 +195,45 @@ tail -200 storage/logs/laravel.log
   # 组合:先显示最近 200 行,然后实时监控
   docker exec math_cms_app tail -200f storage/logs/laravel.log
 ```
+
+## 11. PDF 生成问题排查
+
+### 常见问题
+
+**问题1: PDF生成超时(50秒后失败)**
+- 原因:HTML模板中引用了CDN资源(KaTeX CSS/JS),容器内网络请求慢
+- 解决:代码已优化,自动移除CDN依赖,使用系统字体渲染
+
+**问题2: dbus 连接错误**
+- 表现:日志中出现 `Failed to connect to the bus` 错误
+- 解决:docker-entrypoint.sh 已配置自动启动 dbus-daemon
+
+**问题3: Chrome 进程积累导致 CPU 满载**
+- 原因:PDF生成任务积压,Chrome进程未正常退出
+- 解决:
+  ```bash
+  # 强制清理Chrome进程
+  docker exec math_cms_pdf pkill -9 chromium
+
+  # 清理临时文件
+  docker exec math_cms_pdf rm -rf /tmp/chrome-profile-* /tmp/exam_*
+
+  # 重启PDF服务
+  docker compose restart pdf-worker
+  ```
+
+### PDF 生成调试
+
+```bash
+# 查看PDF生成日志
+docker exec math_cms_app grep -i "ExamPdfExportService" storage/logs/laravel.log | tail -50
+
+# 测试Chrome是否正常工作
+docker exec math_cms_pdf chromium-browser --headless --disable-gpu --no-sandbox --print-to-pdf=/tmp/test.pdf https://www.baidu.com
+
+# 检查Chrome版本
+docker exec math_cms_pdf chromium-browser --version
+
+# 检查dbus状态
+docker exec math_cms_pdf pgrep -a dbus
+```