Sfoglia il codice sorgente

优化合并 pdf 功能

yemeishu 7 ore fa
parent
commit
852b3b3f07
2 ha cambiato i file con 74 aggiunte e 660 eliminazioni
  1. 6 152
      app/Jobs/GenerateExamPdfJob.php
  2. 68 508
      app/Services/ExamPdfExportService.php

+ 6 - 152
app/Jobs/GenerateExamPdfJob.php

@@ -20,22 +20,12 @@ class GenerateExamPdfJob implements ShouldQueue
 
     public string $taskId;
     public string $paperId;
-
-    /**
-     * 【优化】增加任务超时时间,给Chrome充足渲染时间
-     */
-    public $timeout = 180;  // 从90秒增加到180秒(3分钟)
-
-    /**
-     * 【保持】重试次数,配合并发控制
-     */
-    public $tries = 3;
+    public int $maxAttempts = 3;
 
     public function __construct(string $taskId, string $paperId)
     {
         $this->taskId = $taskId;
         $this->paperId = $paperId;
-        $this->onQueue('pdf_generation');
     }
 
     public function handle(
@@ -44,47 +34,13 @@ class GenerateExamPdfJob implements ShouldQueue
         PaperPayloadService $paperPayloadService,
         TaskManager $taskManager
     ): void {
-        $jobId = $this->uuid ?? 'unknown';
-        $startTime = microtime(true);
-
         try {
             Log::info('开始处理PDF生成队列任务', [
-                'job_id' => $jobId,
                 'task_id' => $this->taskId,
                 'paper_id' => $this->paperId,
                 'attempt' => $this->attempts(),
             ]);
 
-            // 【并发控制】检查当前并发数量
-            if (!$this->acquireLock()) {
-                $waitTime = rand(5, 15); // 等待5-15秒后重试
-                Log::warning('GenerateExamPdfJob: 并发限制,等待后重试', [
-                    'job_id' => $jobId,
-                    'wait_time' => $waitTime,
-                    'current_concurrent' => $this->getConcurrentCount()
-                ]);
-
-                // 释放队列槽位,等待后重试
-                $this->release($waitTime);
-                return;
-            }
-
-            Log::info('GenerateExamPdfJob: 获得执行权限', [
-                'job_id' => $jobId,
-                'concurrent_count' => $this->getConcurrentCount()
-            ]);
-
-            // 【新增】快速检查:如果任务已完成,直接跳过
-            $task = $taskManager->getTaskStatus($this->taskId);
-            if ($task && $task['status'] === 'completed') {
-                Log::info('【跳过执行】任务已完成,无需重复生成PDF', [
-                    'task_id' => $this->taskId,
-                    'paper_id' => $this->paperId,
-                    'status' => $task['status']
-                ]);
-                return;
-            }
-
             // 【修复】首先检查试卷是否存在
             $paperModel = Paper::with('questions')->find($this->paperId);
             if (!$paperModel) {
@@ -95,7 +51,7 @@ class GenerateExamPdfJob implements ShouldQueue
                 ]);
 
                 // 如果试卷不存在,判断是否需要重试
-                if ($this->attempts() < $this->tries) {
+                if ($this->attempts() < $this->maxAttempts) {
                     Log::info('试卷不存在,将在2秒后重试', [
                         'task_id' => $this->taskId,
                         'paper_id' => $this->paperId,
@@ -124,7 +80,7 @@ class GenerateExamPdfJob implements ShouldQueue
                     'question_count' => 0,
                 ]);
 
-                if ($this->attempts() < $this->tries) {
+                if ($this->attempts() < $this->maxAttempts) {
                     Log::info('试卷没有题目,将在1秒后重试', [
                         'task_id' => $this->taskId,
                         'paper_id' => $this->paperId,
@@ -154,48 +110,8 @@ class GenerateExamPdfJob implements ShouldQueue
 
             $taskManager->updateTaskProgress($this->taskId, 70, '判卷PDF生成完成,开始合并PDF...');
 
-            // 【优化】生成合并PDF(试卷 + 判卷) - 使用快速合并模式
-            $mergedPdfUrl = $pdfExportService->generateMergedPdf($this->paperId, function($percentage, $message) use ($taskManager) {
-                // 进度更新:70% 开始,最高到 95%
-                $progress = 70 + ($percentage / 100) * 25;
-                $taskManager->updateTaskProgress($this->taskId, round($progress, 0), $message);
-            });
-
-            // 【新增】验证合并后的PDF URL
-            if (!$mergedPdfUrl) {
-                Log::error('PDF生成队列任务失败:合并PDF失败', [
-                    'task_id' => $this->taskId,
-                    'paper_id' => $this->paperId,
-                    'attempt' => $this->attempts(),
-                ]);
-
-                if ($this->attempts() < $this->tries) {
-                    Log::info('合并PDF失败,将在3秒后重试', [
-                        'task_id' => $this->taskId,
-                        'paper_id' => $this->paperId,
-                        'attempt' => $this->attempts(),
-                        'next_attempt' => $this->attempts() + 1,
-                    ]);
-                    // 延迟3秒后重试
-                    $this->release(3);
-                    return;
-                } else {
-                    Log::error('合并PDF失败且已达到最大重试次数,标记任务失败', [
-                        'task_id' => $this->taskId,
-                        'paper_id' => $this->paperId,
-                        'attempts' => $this->attempts(),
-                    ]);
-                    $taskManager->markTaskFailed($this->taskId, "合并PDF失败: {$this->paperId}");
-                    return;
-                }
-            }
-
-            Log::info('PDF合并成功验证', [
-                'task_id' => $this->taskId,
-                'paper_id' => $this->paperId,
-                'merged_pdf_url' => $mergedPdfUrl,
-                'url_length' => strlen($mergedPdfUrl)
-            ]);
+            // 生成合并PDF(试卷 + 判卷)
+            $mergedPdfUrl = $pdfExportService->generateMergedPdf($this->paperId);
 
             // 构建完整的试卷内容
             $examContent = $paperPayloadService->buildExamContent($paperModel);
@@ -223,21 +139,15 @@ class GenerateExamPdfJob implements ShouldQueue
             $taskManager->sendCallback($this->taskId);
 
         } catch (\Exception $e) {
-            $elapsed = microtime(true) - $startTime;
-
             Log::error('PDF生成队列任务失败', [
-                'job_id' => $jobId,
                 'task_id' => $this->taskId,
                 'paper_id' => $this->paperId,
-                'attempt' => $this->attempts(),
-                'max_attempts' => $this->tries,
-                'elapsed' => round($elapsed, 2) . 's',
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString(),
             ]);
 
             // 如果是第一次失败且试卷可能还在创建中,等待后重试
-            if ($this->attempts() < $this->tries && strpos($e->getMessage(), '不存在') !== false) {
+            if ($this->attempts() < $this->maxAttempts && strpos($e->getMessage(), '不存在') !== false) {
                 Log::info('检测到试卷不存在错误,将在2秒后重试', [
                     'task_id' => $this->taskId,
                     'paper_id' => $this->paperId,
@@ -248,62 +158,6 @@ class GenerateExamPdfJob implements ShouldQueue
             }
 
             $taskManager->markTaskFailed($this->taskId, $e->getMessage());
-        } finally {
-            // 【并发控制】释放锁
-            $this->releaseLock();
-            $currentCount = $this->getConcurrentCount();
-
-            Log::info('GenerateExamPdfJob: 任务完成', [
-                'job_id' => $jobId,
-                'total_elapsed' => round(microtime(true) - $startTime, 2) . 's',
-                'remaining_concurrent' => $currentCount
-            ]);
-        }
-    }
-
-    /**
-     * 【并发控制】获取执行锁
-     */
-    private function acquireLock(): bool
-    {
-        $currentCount = $this->getConcurrentCount();
-
-        if ($currentCount >= self::MAX_CONCURRENT_JOBS) {
-            return false;
         }
-
-        // 增加计数器
-        Redis::incr(self::REDIS_KEY_CONCURRENT);
-
-        // 设置锁过期时间(2小时)
-        $lockKey = self::REDIS_KEY_LOCK_PREFIX . $this->taskId;
-        Redis::setex($lockKey, 7200, 1);
-
-        return true;
-    }
-
-    /**
-     * 【并发控制】释放执行锁
-     */
-    private function releaseLock(): void
-    {
-        // 减少计数器
-        $currentCount = Redis::decr(self::REDIS_KEY_CONCURRENT);
-        if ($currentCount < 0) {
-            Redis::set(self::REDIS_KEY_CONCURRENT, 0);
-        }
-
-        // 删除锁
-        $lockKey = self::REDIS_KEY_LOCK_PREFIX . $this->taskId;
-        Redis::del($lockKey);
-    }
-
-    /**
-     * 【并发控制】获取当前并发数量
-     */
-    private function getConcurrentCount(): int
-    {
-        $count = Redis::get(self::REDIS_KEY_CONCURRENT);
-        return $count ? (int)$count : 0;
     }
 }

+ 68 - 508
app/Services/ExamPdfExportService.php

@@ -33,69 +33,20 @@ class ExamPdfExportService
         private readonly PdfMerger $pdfMerger
     ) {}
 
-    /**
-     * 确保exams目录存在
-     */
-    private function ensureExamsDirectory(): bool
-    {
-        $examsDir = storage_path("app/public/exams");
-
-        if (!is_dir($examsDir)) {
-            Log::info('ExamPdfExportService: 创建exams目录', ['path' => $examsDir]);
-            if (!mkdir($examsDir, 0755, true)) {
-                Log::error('ExamPdfExportService: 创建exams目录失败', ['path' => $examsDir]);
-                return false;
-            }
-            Log::info('ExamPdfExportService: exams目录创建成功', ['path' => $examsDir]);
-        }
-
-        return true;
-    }
-
     /**
      * 生成试卷 PDF(不含答案)
      */
     public function generateExamPdf(string $paperId): ?string
     {
         Log::info('generateExamPdf 开始:', ['paper_id' => $paperId]);
-
-        // 返回页面URL(用于数据库保存)
-        $pageUrl = route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $paperId, 'answer' => 'false']);
-        Log::info('generateExamPdf 页面URL:', ['paper_id' => $paperId, 'url' => $pageUrl]);
-        // 将页面URL写入数据库
-        $this->savePdfUrlToDatabase($paperId, 'exam_pdf_url', $pageUrl);
-
-        // 生成PDF文件(用于合并,不上传云存储)
-        $pdfPath = storage_path("app/public/exams/{$paperId}_exam.pdf");
-
-        // 【修复】确保exams目录存在
-        if (!$this->ensureExamsDirectory()) {
-            return null;
-        }
-
-        Log::info('ExamPdfExportService: 开始生成试卷PDF', ['path' => $pdfPath, 'url' => $pageUrl]);
-        $pdfBinary = $this->buildPdfFromUrl($pageUrl);
-        if (!$pdfBinary) {
-            Log::error('ExamPdfExportService: 生成试卷PDF失败', ['url' => $pageUrl]);
-            return null;
+        $url = $this->renderAndStoreExamPdf($paperId, includeAnswer: false, suffix: 'exam');
+        Log::info('generateExamPdf url 生成结果:', ['paper_id' => $paperId, 'url' => $url]);
+        // 如果生成成功,将 URL 写入数据库
+        if ($url) {
+            $this->savePdfUrlToDatabase($paperId, 'exam_pdf_url', $url);
         }
-        Log::info('ExamPdfExportService: PDF生成成功,开始写入文件', ['path' => $pdfPath, 'size' => strlen($pdfBinary)]);
-        $result = file_put_contents($pdfPath, $pdfBinary);
-        if ($result === false) {
-            Log::error('ExamPdfExportService: 写入试卷PDF文件失败', ['path' => $pdfPath]);
-            return null;
-        }
-
-        // 【关键修复】验证文件是否真的写入成功
-        if (!file_exists($pdfPath)) {
-            Log::error('ExamPdfExportService: 文件写入后不存在', ['path' => $pdfPath, 'result' => $result]);
-            return null;
-        }
-
-        Log::info('ExamPdfExportService: 试卷PDF文件写入成功', ['path' => $pdfPath, 'size' => $result]);
 
-        // 返回页面URL(不是PDF URL)
-        return $pageUrl;
+        return $url;
     }
 
     /**
@@ -104,75 +55,24 @@ class ExamPdfExportService
     public function generateGradingPdf(string $paperId): ?string
     {
         Log::info('generateGradingPdf 开始:', ['paper_id' => $paperId]);
-
-        // 返回页面URL(用于数据库保存)
-        $pageUrl = route('filament.admin.auth.intelligent-exam.pdf', ['paper_id' => $paperId, 'answer' => 'true']);
-        Log::info('generateGradingPdf 页面URL:', ['paper_id' => $paperId, 'url' => $pageUrl]);
-        // 将页面URL写入数据库
-        $this->savePdfUrlToDatabase($paperId, 'grading_pdf_url', $pageUrl);
-
-        // 生成PDF文件(用于合并,不上传云存储)
-        $pdfPath = storage_path("app/public/exams/{$paperId}_grading.pdf");
-
-        // 【修复】确保exams目录存在
-        if (!$this->ensureExamsDirectory()) {
-            return null;
-        }
-
-        Log::info('ExamPdfExportService: 开始生成判卷PDF', ['path' => $pdfPath, 'url' => $pageUrl]);
-        $pdfBinary = $this->buildPdfFromUrl($pageUrl);
-        if (!$pdfBinary) {
-            Log::error('ExamPdfExportService: 生成判卷PDF失败', ['url' => $pageUrl]);
-            return null;
-        }
-        Log::info('ExamPdfExportService: 判卷PDF生成成功,开始写入文件', ['path' => $pdfPath, 'size' => strlen($pdfBinary)]);
-        $result = file_put_contents($pdfPath, $pdfBinary);
-        if ($result === false) {
-            Log::error('ExamPdfExportService: 写入判卷PDF文件失败', ['path' => $pdfPath]);
-            return null;
-        }
-
-        // 【关键修复】验证文件是否真的写入成功
-        if (!file_exists($pdfPath)) {
-            Log::error('ExamPdfExportService: 判卷文件写入后不存在', ['path' => $pdfPath, 'result' => $result]);
-            return null;
+        $url = $this->renderAndStoreExamPdf($paperId, includeAnswer: true, suffix: 'grading', useGradingView: true);
+        Log::info('generateGradingPdf url 生成结果:', ['paper_id' => $paperId, 'url' => $url]);
+        // 如果生成成功,将 URL 写入数据库
+        if ($url) {
+            $this->savePdfUrlToDatabase($paperId, 'grading_pdf_url', $url);
         }
 
-        Log::info('ExamPdfExportService: 判卷PDF文件写入成功', ['path' => $pdfPath, 'size' => $result]);
-
-        // 返回页面URL(不是PDF URL)
-        return $pageUrl;
+        return $url;
     }
 
     /**
      * 生成合并PDF(试卷 + 判卷)
      * 先分别生成两个PDF,然后合并
-     * 【优化】添加进度回调支持和快速合并模式
-     * 【修复】优化临时文件清理逻辑,确保合并成功后才删除源文件
      */
-    public function generateMergedPdf(string $paperId, ?callable $progressCallback = null): ?string
+    public function generateMergedPdf(string $paperId): ?string
     {
         Log::info('generateMergedPdf 开始:', ['paper_id' => $paperId]);
 
-        // 【新增】快速幂等性检查:如果all_pdf_url已存在,直接返回
-        $existingPaper = \App\Models\Paper::where('paper_id', $paperId)->first();
-        if ($existingPaper && $existingPaper->all_pdf_url) {
-            Log::info('【快速返回】合并PDF已存在,无需重新生成', [
-                'paper_id' => $paperId,
-                'existing_url' => $existingPaper->all_pdf_url
-            ]);
-
-            if ($progressCallback) {
-                $progressCallback(100, '合并PDF已存在,直接返回');
-            }
-
-            return $existingPaper->all_pdf_url;
-        }
-
-        if ($progressCallback) {
-            $progressCallback(0, '准备合并PDF...');
-        }
-
         $tempDir = storage_path("app/temp");
         if (!is_dir($tempDir)) {
             mkdir($tempDir, 0755, true);
@@ -181,142 +81,65 @@ class ExamPdfExportService
         $examPdfPath = null;
         $gradingPdfPath = null;
         $mergedPdfPath = null;
-        $mergeSuccess = false;
-        $uploadSuccess = false;
 
         try {
-            // 【修复】不重复生成PDF,直接使用已有的文件
-            // 假设PDF已经通过generateExamPdf和generateGradingPdf生成过了
-            // 获取数据库中的页面URL(用于记录)
-            $paper = \App\Models\Paper::where('paper_id', $paperId)->first();
-            $examPdfUrl = $paper?->exam_pdf_url;
-            $gradingPdfUrl = $paper?->grading_pdf_url;
-
-            if (!$examPdfUrl || !$gradingPdfUrl) {
-                Log::warning('ExamPdfExportService: 未找到PDF页面URL,可能尚未生成', [
-                    'paper_id' => $paperId,
-                    'exam_pdf_url' => $examPdfUrl,
-                    'grading_pdf_url' => $gradingPdfUrl
-                ]);
+            // 先生成试卷PDF
+            $examPdfUrl = $this->generateExamPdf($paperId);
+            if (!$examPdfUrl) {
+                Log::error('ExamPdfExportService: 生成试卷PDF失败', ['paper_id' => $paperId]);
+                return null;
+            }
+
+            // 再生成判卷PDF
+            $gradingPdfUrl = $this->generateGradingPdf($paperId);
+            if (!$gradingPdfUrl) {
+                Log::error('ExamPdfExportService: 生成判卷PDF失败', ['paper_id' => $paperId]);
+                return null;
             }
 
-            Log::info('使用本地PDF文件进行合并', [
+            // 【修复】下载PDF文件到本地临时目录
+            Log::info('开始下载PDF文件到本地', [
                 'exam_url' => $examPdfUrl,
                 'grading_url' => $gradingPdfUrl
             ]);
 
-            if ($progressCallback) {
-                $progressCallback(10, '验证PDF文件...');
-            }
-
-            // 直接使用本地PDF文件
-            $examPdfPath = storage_path("app/public/exams/{$paperId}_exam.pdf");
-            $gradingPdfPath = storage_path("app/public/exams/{$paperId}_grading.pdf");
+            $examPdfPath = $tempDir . "/{$paperId}_exam.pdf";
+            $gradingPdfPath = $tempDir . "/{$paperId}_grading.pdf";
 
-            // 验证文件是否存在
-            Log::info('ExamPdfExportService: 检查PDF文件是否存在', [
-                'exam_pdf' => $examPdfPath,
-                'exam_exists' => file_exists($examPdfPath),
-                'grading_pdf' => $gradingPdfPath,
-                'grading_exists' => file_exists($gradingPdfPath)
-            ]);
-
-            if (!file_exists($examPdfPath)) {
-                Log::error('ExamPdfExportService: 试卷PDF文件不存在', [
-                    'path' => $examPdfUrl,
-                    'local_path' => $examPdfPath,
-                    'directory_exists' => is_dir(dirname($examPdfPath)),
-                    'directory_contents' => is_dir(dirname($examPdfPath)) ? scandir(dirname($examPdfPath)) : null
-                ]);
+            // 下载试卷PDF
+            $examContent = Http::get($examPdfUrl)->body();
+            if (empty($examContent)) {
+                Log::error('ExamPdfExportService: 下载试卷PDF失败', ['url' => $examPdfUrl]);
                 return null;
             }
+            file_put_contents($examPdfPath, $examContent);
 
-            if (!file_exists($gradingPdfPath)) {
-                Log::error('ExamPdfExportService: 判卷PDF文件不存在', [
-                    'path' => $gradingPdfUrl,
-                    'local_path' => $gradingPdfPath,
-                    'directory_exists' => is_dir(dirname($gradingPdfPath)),
-                    'directory_contents' => is_dir(dirname($gradingPdfPath)) ? scandir(dirname($gradingPdfPath)) : null
-                ]);
+            // 下载判卷PDF
+            $gradingContent = Http::get($gradingPdfUrl)->body();
+            if (empty($gradingContent)) {
+                Log::error('ExamPdfExportService: 下载判卷PDF失败', ['url' => $gradingPdfUrl]);
                 return null;
             }
+            file_put_contents($gradingPdfPath, $gradingContent);
 
-            $examSize = filesize($examPdfPath);
-            $gradingSize = filesize($gradingPdfPath);
-
-            Log::info('PDF文件验证成功', [
-                'exam_pdf' => $examPdfPath,
-                'grading_pdf' => $gradingPdfPath,
-                'exam_size' => $examSize,
-                'grading_size' => $gradingSize,
-                'total_size' => $examSize + $gradingSize
+            Log::info('PDF文件下载完成', [
+                'exam_size' => filesize($examPdfPath),
+                'grading_size' => filesize($gradingPdfPath)
             ]);
 
-            if ($examSize < 1000 || $gradingSize < 1000) {
-                Log::warning('ExamPdfExportService: PDF文件过小,可能生成不完整', [
-                    'exam_size' => $examSize,
-                    'grading_size' => $gradingSize
-                ]);
-            }
-
-            if ($progressCallback) {
-                $progressCallback(20, '开始合并PDF文件...');
-            }
-
-            // 【优化】合并PDF文件 - 使用快速合并模式
+            // 合并PDF文件
             $mergedPdfPath = $tempDir . "/{$paperId}_merged.pdf";
-            $merged = $this->pdfMerger->mergeWithProgress([$examPdfPath, $gradingPdfPath], $mergedPdfPath, $progressCallback);
+            $merged = $this->pdfMerger->merge([$examPdfPath, $gradingPdfPath], $mergedPdfPath);
 
             if (!$merged) {
                 Log::error('ExamPdfExportService: PDF文件合并失败', [
-                    'tool' => $this->pdfMerger->getMergeTool(),
-                    'exam_pdf' => $examPdfPath,
-                    'grading_pdf' => $gradingPdfPath,
-                    'output_pdf' => $mergedPdfPath
+                    'tool' => $this->pdfMerger->getMergeTool()
                 ]);
                 return null;
             }
 
-            // 【新增】验证合并后的PDF内容
-            if (!file_exists($mergedPdfPath)) {
-                Log::error('ExamPdfExportService: 合并后PDF文件不存在', ['path' => $mergedPdfPath]);
-                return null;
-            }
-
-            $mergedSize = filesize($mergedPdfPath);
-            Log::info('ExamPdfExportService: 合并PDF验证', [
-                'merged_pdf' => $mergedPdfPath,
-                'merged_size' => $mergedSize,
-                'expected_min_size' => max($examSize, $gradingSize) + 1000,
-                'size_valid' => $mergedSize > max($examSize, $gradingSize)
-            ]);
-
-            // 验证合并后的PDF大小是否合理(应该大于任一源文件)
-            if ($mergedSize <= max($examSize, $gradingSize)) {
-                Log::warning('ExamPdfExportService: 合并PDF大小异常,可能合并失败', [
-                    'merged_size' => $mergedSize,
-                    'exam_size' => $examSize,
-                    'grading_size' => $gradingSize,
-                    'max_source_size' => max($examSize, $gradingSize)
-                ]);
-            }
-
-            $mergeSuccess = true;
-
-            if ($progressCallback) {
-                $progressCallback(80, '上传合并PDF...');
-            }
-
             // 读取合并后的PDF内容并上传到云存储
             $mergedPdfContent = file_get_contents($mergedPdfPath);
-            if (strlen($mergedPdfContent) < 1000) {
-                Log::error('ExamPdfExportService: 合并PDF内容过小,上传失败', [
-                    'content_size' => strlen($mergedPdfContent),
-                    'file_size' => $mergedSize
-                ]);
-                return null;
-            }
-
             $path = "exams/{$paperId}_all.pdf";
             $mergedUrl = $this->pdfStorageService->put($path, $mergedPdfContent);
 
@@ -325,24 +148,13 @@ class ExamPdfExportService
                 return null;
             }
 
-            Log::info('ExamPdfExportService: 合并PDF上传成功', [
-                'url' => $mergedUrl,
-                'content_size' => strlen($mergedPdfContent),
-                'file_size' => $mergedSize
-            ]);
-
-            $uploadSuccess = true;
-
             // 保存到数据库的all_pdf_url字段
             $this->saveAllPdfUrlToDatabase($paperId, $mergedUrl);
 
             Log::info('generateMergedPdf 完成:', [
                 'paper_id' => $paperId,
                 'url' => $mergedUrl,
-                'tool' => $this->pdfMerger->getMergeTool(),
-                'exam_size' => $examSize,
-                'grading_size' => $gradingSize,
-                'merged_size' => $mergedSize
+                'tool' => $this->pdfMerger->getMergeTool()
             ]);
             return $mergedUrl;
 
@@ -352,61 +164,16 @@ class ExamPdfExportService
                 'error' => $e->getMessage(),
                 'trace' => $e->getTraceAsString(),
             ]);
-
-            if ($progressCallback) {
-                $progressCallback(-1, '合并PDF失败: ' . $e->getMessage());
-            }
-
             return null;
         } finally {
-            // 【修复】优化临时文件清理逻辑:
-            // 1. 合并失败时不删除源文件,便于重试
-            // 2. 合并成功后不立即删除源文件,保留2小时用于调试
-            // 3. 保留合并后的文件30分钟用于调试
-
-            if ($mergeSuccess && $uploadSuccess) {
-                // 【优化】合并成功且上传成功,不立即删除源文件
-                // 改为设置未来删除时间,让源文件保留2小时
-                $sourceFiles = [$examPdfPath, $gradingPdfPath];
-                foreach ($sourceFiles as $file) {
-                    if ($file && file_exists($file)) {
-                        // 设置2小时后删除
-                        $deletionTime = time() + 7200; // 2小时 = 7200秒
-                        @touch($file, $deletionTime);
-                        Log::info('源PDF文件将在2小时后自动删除', [
-                            'path' => $file,
-                            'deletion_time' => date('Y-m-d H:i:s', $deletionTime)
-                        ]);
-                    }
-                }
-
-                // 保留合并文件30分钟后删除
-                if ($mergedPdfPath && file_exists($mergedPdfPath)) {
-                    $deletionTime = time() + 1800; // 30分钟后
-                    @touch($mergedPdfPath, $deletionTime);
-                    Log::info('合并PDF文件保留30分钟用于调试', [
-                        'path' => $mergedPdfPath,
-                        'deletion_time' => date('Y-m-d H:i:s', $deletionTime)
-                    ]);
+            // 【修复】清理临时文件
+            $tempFiles = [$examPdfPath, $gradingPdfPath, $mergedPdfPath];
+            foreach ($tempFiles as $file) {
+                if ($file && file_exists($file)) {
+                    @unlink($file);
                 }
-            } else {
-                // 合并失败或上传失败,保留所有文件用于调试
-                Log::warning('PDF合并未完全成功,保留临时文件用于调试', [
-                    'merge_success' => $mergeSuccess,
-                    'upload_success' => $uploadSuccess,
-                    'exam_pdf' => $examPdfPath,
-                    'grading_pdf' => $gradingPdfPath,
-                    'merged_pdf' => $mergedPdfPath,
-                    'exam_exists' => $examPdfPath ? file_exists($examPdfPath) : false,
-                    'grading_exists' => $gradingPdfPath ? file_exists($gradingPdfPath) : false,
-                    'merged_exists' => $mergedPdfPath ? file_exists($mergedPdfPath) : false
-                ]);
             }
-
-            Log::debug('PDF合并流程完成', [
-                'merge_success' => $mergeSuccess,
-                'upload_success' => $uploadSuccess
-            ]);
+            Log::debug('清理临时文件完成');
         }
     }
 
@@ -1162,82 +929,16 @@ class ExamPdfExportService
      */
     private function buildPdf(string $html): ?string
     {
-        Log::info('ExamPdfExportService: buildPdf开始', ['html_size' => strlen($html)]);
-
         $tmpHtml = tempnam(sys_get_temp_dir(), 'exam_html_') . '.html';
-        Log::info('ExamPdfExportService: 创建临时HTML文件', ['tmp_html' => $tmpHtml]);
-
         $utf8Html = $this->ensureUtf8Html($html);
         file_put_contents($tmpHtml, $utf8Html);
 
-        Log::info('ExamPdfExportService: HTML文件已写入', ['tmp_html' => $tmpHtml, 'size' => filesize($tmpHtml)]);
-
         // 仅使用Chrome渲染
-        Log::info('ExamPdfExportService: 开始调用renderWithChrome', ['tmp_html' => $tmpHtml]);
         $chromePdf = $this->renderWithChrome($tmpHtml);
-        Log::info('ExamPdfExportService: renderWithChrome完成', [
-            'pdf_size' => $chromePdf ? strlen($chromePdf) : 0,
-            'pdf_success' => !empty($chromePdf)
-        ]);
-
         @unlink($tmpHtml);
         return $chromePdf;
     }
 
-    /**
-     * 从URL生成PDF
-     */
-    private function buildPdfFromUrl(string $url): ?string
-    {
-        Log::info('ExamPdfExportService: buildPdfFromUrl开始', ['url' => $url]);
-
-        try {
-            $response = Http::get($url);
-            Log::info('ExamPdfExportService: HTTP请求完成', [
-                'url' => $url,
-                'status' => $response->status(),
-                'successful' => $response->successful()
-            ]);
-
-            if (!$response->successful()) {
-                Log::error('ExamPdfExportService: 获取URL内容失败', [
-                    'url' => $url,
-                    'status_code' => $response->status()
-                ]);
-                return null;
-            }
-
-            $html = $response->body();
-            $htmlSize = strlen($html);
-            Log::info('ExamPdfExportService: 获取HTML内容成功', [
-                'url' => $url,
-                'html_size' => $htmlSize,
-                'html_preview' => substr($html, 0, 100)
-            ]);
-
-            if (empty($html)) {
-                Log::error('ExamPdfExportService: URL返回内容为空', ['url' => $url]);
-                return null;
-            }
-
-            Log::info('ExamPdfExportService: 开始调用buildPdf', ['html_size' => $htmlSize]);
-            $pdfBinary = $this->buildPdf($html);
-            Log::info('ExamPdfExportService: buildPdf完成', [
-                'pdf_size' => $pdfBinary ? strlen($pdfBinary) : 0,
-                'pdf_success' => !empty($pdfBinary)
-            ]);
-
-            return $pdfBinary;
-        } catch (\Exception $e) {
-            Log::error('ExamPdfExportService: buildPdfFromUrl异常', [
-                'url' => $url,
-                'error' => $e->getMessage(),
-                'trace' => $e->getTraceAsString()
-            ]);
-            return null;
-        }
-    }
-
     /**
      * 使用Chrome渲染PDF
      */
@@ -1252,12 +953,6 @@ class ExamPdfExportService
             return null;
         }
 
-        Log::info('ExamPdfExportService: 开始Chrome渲染', [
-            'html_path' => $htmlPath,
-            'tmp_pdf' => $tmpPdf,
-            'chrome_binary' => $chromeBinary
-        ]);
-
         // 设置运行时目录
         $runtimeHome = sys_get_temp_dir() . '/chrome-home';
         $runtimeXdg = sys_get_temp_dir() . '/chrome-xdg';
@@ -1268,14 +963,15 @@ class ExamPdfExportService
             @File::makeDirectory($runtimeXdg, 0755, true);
         }
 
-        // 【性能优化】深度优化Chrome参数,提升渲染速度
         $process = new Process([
             $chromeBinary,
-            '--headless=new',
+            '--headless',
             '--disable-gpu',
             '--no-sandbox',
             '--disable-setuid-sandbox',
             '--disable-dev-shm-usage',
+            '--no-zygote',
+            '--disable-features=VizDisplayCompositor',
             '--disable-software-rasterizer',
             '--disable-extensions',
             '--disable-background-networking',
@@ -1298,139 +994,50 @@ class ExamPdfExportService
             '--disable-backgrounding-occluded-windows',
             '--disable-renderer-backgrounding',
             '--disable-features=AudioServiceOutOfProcess',
-            '--disable-gpu-rasterization',
-            '--disable-web-security',
-            '--disable-features=VizDisplayCompositor',
-            '--font-render-hinting=none',
-            '--disable-logging',
-            '--disable-gpu-sandbox',
-            '--disable-partial-raster',
-            '--disable-skia-runtime',
-            '--no-zygote',
-            // 【新增】启用高级性能优化
-            '--enable-features=NetworkService,NetworkServiceInProcess',
-            '--enable-automation',
-            '--disable-infobars',
-            '--disable-extensions-ui',
-            '--disable-smooth-scrolling',
-            '--disable-ipc-flooding-protection',
             '--user-data-dir=' . $userDataDir,
             '--print-to-pdf=' . $tmpPdf,
             '--print-to-pdf-no-header',
             '--allow-file-access-from-files',
-            // 【优化】增加虚拟时间预算到25秒(原来是15秒)
-            '--virtual-time-budget=25000',
-            // 【优化】移除可能导致卡顿的参数
-            // '--run-all-compositor-stages-before-draw', // 移除此参数
-            // 【优化】增加堆内存限制(原来512MB,现在1024MB)
-            '--max_old_space_size=1024',
-            // 【优化】增加新堆内存限制(原来64MB,现在128MB)
-            '--max_new_space_size=128',
             'file://' . $htmlPath,
         ], null, [
             'HOME' => $runtimeHome,
             'XDG_RUNTIME_DIR' => $runtimeXdg,
         ]);
 
-        // 【性能优化】增加超时时间到200秒(原来是45秒)
-        // 匹配25秒虚拟时间预算,同时给Chrome充足时间处理复杂页面
-        $process->setTimeout(200);
+        $process->setTimeout(60);
         $killSignal = \defined('SIGKILL') ? \SIGKILL : 9;
 
         try {
             $startedAt = microtime(true);
-            Log::info('ExamPdfExportService: 优化Chrome进程启动', [
-                'start_time' => date('Y-m-d H:i:s', (int)$startedAt),
-                'timeout' => 200,
-                'virtual_time_budget' => 25000,
-                'html_size' => file_exists($htmlPath) ? filesize($htmlPath) : 0
-            ]);
-
             $process->start();
             $pdfGenerated = false;
 
-            // 【性能优化】调整轮询时间:195秒轮询(小于Chrome超时200秒)
+            // 轮询检测PDF是否生成
             $pollStart = microtime(true);
-            $maxPollSeconds = 195; // 从43秒增加到195秒
-            $checkInterval = 30_000; // 保持30ms检查间隔
-
-            // 【性能优化】缩短初始等待时间到500ms
-            usleep(500_000);
-
+            $maxPollSeconds = 30;
             while ($process->isRunning() && (microtime(true) - $pollStart) < $maxPollSeconds) {
-                // 【优化】每1秒输出一次进度(原来是2秒)
-                $currentElapsed = microtime(true) - $pollStart;
-                if (fmod($currentElapsed, 1) < 0.1 || $currentElapsed < 0.2) {
-                    Log::debug('ExamPdfExportService: 优化Chrome渲染中', [
-                        'elapsed' => round($currentElapsed, 2) . 's',
-                        'pdf_exists' => file_exists($tmpPdf),
-                        'pdf_size' => file_exists($tmpPdf) ? filesize($tmpPdf) : 0
-                    ]);
-                }
-
-                // 【优化】早期检查PDF生成(每30ms)
                 if (file_exists($tmpPdf) && filesize($tmpPdf) > 0) {
                     $pdfGenerated = true;
-                    $elapsed = microtime(true) - $startedAt;
-                    Log::info('ExamPdfExportService: 优化PDF文件已生成,提前终止Chrome', [
-                        'elapsed' => round($elapsed, 2) . 's',
-                        'pdf_size' => filesize($tmpPdf),
-                        'poll_elapsed' => round($currentElapsed, 2) . 's'
-                    ]);
-                    $process->stop(1, $killSignal);
+                    $process->stop(5, $killSignal);
                     break;
                 }
-                usleep($checkInterval);
+                usleep(200_000);
             }
 
-            $elapsed = microtime(true) - $startedAt;
-            Log::info('ExamPdfExportService: Chrome轮询结束', [
-                'elapsed' => round($elapsed, 2) . 's',
-                'is_running' => $process->isRunning(),
-                'pdf_exists' => file_exists($tmpPdf),
-                'pdf_size' => file_exists($tmpPdf) ? filesize($tmpPdf) : 0,
-                'timeout' => $maxPollSeconds
-            ]);
-
-            // 【优化】强制快速停止Chrome进程
             if ($process->isRunning()) {
-                Log::warning('ExamPdfExportService: Chrome进程仍在运行,强制停止', [
-                    'elapsed' => round($elapsed, 2) . 's',
-                    'reason' => 'timeout'
-                ]);
-                $process->stop(1, $killSignal);
+                $process->stop(5, $killSignal);
             }
 
             $process->wait();
 
-        } catch (ProcessTimedOutException $e) {
-            Log::error('ExamPdfExportService: Chrome进程超时', [
-                'timeout' => $e->getExceededTimeout(),
-                'elapsed' => round(microtime(true) - $startedAt, 2) . 's',
-                'html_size' => file_exists($htmlPath) ? filesize($htmlPath) : 0,
-                'message' => 'Chrome渲染超时,已自动终止'
-            ]);
+        } catch (ProcessTimedOutException|ProcessSignaledException $e) {
             if ($process->isRunning()) {
-                $process->stop(1, $killSignal);
-            }
-            return $this->handleChromeProcessResult($tmpPdf, $userDataDir, $process, $startedAt);
-        } catch (ProcessSignaledException $e) {
-            Log::warning('ExamPdfExportService: Chrome进程被信号终止', [
-                'signal' => $e->getSignal(),
-                'elapsed' => round(microtime(true) - $startedAt, 2) . 's'
-            ]);
-            if ($process->isRunning()) {
-                $process->stop(1, $killSignal);
+                $process->stop(5, $killSignal);
             }
             return $this->handleChromeProcessResult($tmpPdf, $userDataDir, $process, $startedAt);
         } catch (\Throwable $e) {
-            Log::error('ExamPdfExportService: Chrome渲染异常', [
-                'error' => $e->getMessage(),
-                'elapsed' => round(microtime(true) - $startedAt, 2) . 's',
-                'trace' => $e->getTraceAsString()
-            ]);
             if ($process->isRunning()) {
-                $process->stop(1, $killSignal);
+                $process->stop(5, $killSignal);
             }
             return $this->handleChromeProcessResult($tmpPdf, $userDataDir, $process, null);
         }
@@ -1443,76 +1050,29 @@ class ExamPdfExportService
      */
     private function handleChromeProcessResult(string $tmpPdf, string $userDataDir, Process $process, ?float $startedAt): ?string
     {
-        $elapsed = $startedAt ? round(microtime(true) - $startedAt, 2) : 0;
         $pdfExists = file_exists($tmpPdf);
         $pdfSize = $pdfExists ? filesize($tmpPdf) : null;
 
         if (!$process->isSuccessful()) {
             if ($pdfExists && $pdfSize > 0) {
                 Log::warning('ExamPdfExportService: Chrome进程异常但生成了PDF', [
-                    'elapsed' => $elapsed . 's',
                     'exit_code' => $process->getExitCode(),
                     'tmp_pdf_size' => $pdfSize,
                 ]);
             } else {
                 Log::error('ExamPdfExportService: Chrome渲染失败', [
-                    'elapsed' => $elapsed . 's',
                     'exit_code' => $process->getExitCode(),
                     'error' => $process->getErrorOutput(),
-                    'tmp_pdf_exists' => $pdfExists,
-                    'tmp_pdf_size' => $pdfSize
                 ]);
-            }
-        } else {
-            Log::info('ExamPdfExportService: Chrome进程正常结束', [
-                'elapsed' => $elapsed . 's',
-                'exit_code' => $process->getExitCode(),
-                'pdf_exists' => $pdfExists,
-                'pdf_size' => $pdfSize
-            ]);
-        }
-
-        // 读取PDF内容
-        $pdfBinary = null;
-        if ($pdfExists && $pdfSize > 0) {
-            $pdfBinary = file_get_contents($tmpPdf);
-            Log::info('ExamPdfExportService: PDF读取成功', [
-                'size' => strlen($pdfBinary),
-                'elapsed' => $elapsed . 's'
-            ]);
-        } else {
-            Log::error('ExamPdfExportService: PDF文件不存在或为空', [
-                'tmp_pdf' => $tmpPdf,
-                'exists' => $pdfExists,
-                'size' => $pdfSize
-            ]);
-        }
-
-        // 【优化】确保资源正确释放,使用try-catch包装
-        try {
-            if ($pdfExists && file_exists($tmpPdf)) {
                 @unlink($tmpPdf);
-                Log::debug('ExamPdfExportService: 临时PDF文件已删除', ['path' => $tmpPdf]);
-            }
-        } catch (\Exception $e) {
-            Log::warning('ExamPdfExportService: 删除临时PDF文件失败', [
-                'path' => $tmpPdf,
-                'error' => $e->getMessage()
-            ]);
-        }
-
-        try {
-            if ($userDataDir && is_dir($userDataDir)) {
                 File::deleteDirectory($userDataDir);
-                Log::debug('ExamPdfExportService: Chrome用户数据目录已删除', ['path' => $userDataDir]);
+                return null;
             }
-        } catch (\Exception $e) {
-            Log::warning('ExamPdfExportService: 删除Chrome用户数据目录失败', [
-                'path' => $userDataDir,
-                'error' => $e->getMessage()
-            ]);
         }
 
+        $pdfBinary = $pdfExists ? file_get_contents($tmpPdf) : null;
+        @unlink($tmpPdf);
+        File::deleteDirectory($userDataDir);
         return $pdfBinary ?: null;
     }