|
|
@@ -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;
|
|
|
}
|
|
|
|
|
|
/**
|