|
|
@@ -39,14 +39,38 @@ class ExamPdfExportService
|
|
|
public function generateExamPdf(string $paperId): ?string
|
|
|
{
|
|
|
Log::info('generateExamPdf 开始:', ['paper_id' => $paperId]);
|
|
|
- $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);
|
|
|
+
|
|
|
+ // 返回页面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");
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
- return $url;
|
|
|
+ // 【关键修复】验证文件是否真的写入成功
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -55,25 +79,65 @@ class ExamPdfExportService
|
|
|
public function generateGradingPdf(string $paperId): ?string
|
|
|
{
|
|
|
Log::info('generateGradingPdf 开始:', ['paper_id' => $paperId]);
|
|
|
- $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);
|
|
|
+
|
|
|
+ // 返回页面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");
|
|
|
+ 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;
|
|
|
}
|
|
|
|
|
|
- return $url;
|
|
|
+ Log::info('ExamPdfExportService: 判卷PDF文件写入成功', ['path' => $pdfPath, 'size' => $result]);
|
|
|
+
|
|
|
+ // 返回页面URL(不是PDF URL)
|
|
|
+ return $pageUrl;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 生成合并PDF(试卷 + 判卷)
|
|
|
* 先分别生成两个PDF,然后合并
|
|
|
* 【优化】添加进度回调支持和快速合并模式
|
|
|
+ * 【修复】优化临时文件清理逻辑,确保合并成功后才删除源文件
|
|
|
*/
|
|
|
public function generateMergedPdf(string $paperId, ?callable $progressCallback = null): ?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...');
|
|
|
}
|
|
|
@@ -86,135 +150,142 @@ class ExamPdfExportService
|
|
|
$examPdfPath = null;
|
|
|
$gradingPdfPath = null;
|
|
|
$mergedPdfPath = null;
|
|
|
+ $mergeSuccess = false;
|
|
|
+ $uploadSuccess = false;
|
|
|
|
|
|
try {
|
|
|
- // 先生成试卷PDF
|
|
|
- if ($progressCallback) {
|
|
|
- $progressCallback(10, '生成试卷PDF...');
|
|
|
- }
|
|
|
- $examPdfUrl = $this->generateExamPdf($paperId);
|
|
|
- if (!$examPdfUrl) {
|
|
|
- Log::error('ExamPdfExportService: 生成试卷PDF失败', ['paper_id' => $paperId]);
|
|
|
- return null;
|
|
|
- }
|
|
|
-
|
|
|
- // 再生成判卷PDF
|
|
|
- if ($progressCallback) {
|
|
|
- $progressCallback(30, '生成判卷PDF...');
|
|
|
- }
|
|
|
- $gradingPdfUrl = $this->generateGradingPdf($paperId);
|
|
|
- if (!$gradingPdfUrl) {
|
|
|
- Log::error('ExamPdfExportService: 生成判卷PDF失败', ['paper_id' => $paperId]);
|
|
|
- return null;
|
|
|
+ // 【修复】不重复生成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文件到本地临时目录
|
|
|
- Log::info('开始下载PDF文件到本地', [
|
|
|
+ Log::info('使用本地PDF文件进行合并', [
|
|
|
'exam_url' => $examPdfUrl,
|
|
|
'grading_url' => $gradingPdfUrl
|
|
|
]);
|
|
|
|
|
|
if ($progressCallback) {
|
|
|
- $progressCallback(40, '下载试卷PDF...');
|
|
|
+ $progressCallback(10, '验证PDF文件...');
|
|
|
}
|
|
|
|
|
|
- $examPdfPath = $tempDir . "/{$paperId}_exam.pdf";
|
|
|
- $gradingPdfPath = $tempDir . "/{$paperId}_grading.pdf";
|
|
|
+ // 直接使用本地PDF文件
|
|
|
+ $examPdfPath = storage_path("app/public/exams/{$paperId}_exam.pdf");
|
|
|
+ $gradingPdfPath = storage_path("app/public/exams/{$paperId}_grading.pdf");
|
|
|
+
|
|
|
+ // 验证文件是否存在
|
|
|
+ Log::info('ExamPdfExportService: 检查PDF文件是否存在', [
|
|
|
+ 'exam_pdf' => $examPdfPath,
|
|
|
+ 'exam_exists' => file_exists($examPdfPath),
|
|
|
+ 'grading_pdf' => $gradingPdfPath,
|
|
|
+ 'grading_exists' => file_exists($gradingPdfPath)
|
|
|
+ ]);
|
|
|
|
|
|
- // 【修复】下载试卷PDF - 添加HTTP状态码检查
|
|
|
- $examResponse = Http::get($examPdfUrl);
|
|
|
- if (!$examResponse->successful()) {
|
|
|
- Log::error('ExamPdfExportService: 下载试卷PDF失败', [
|
|
|
- 'url' => $examPdfUrl,
|
|
|
- 'status_code' => $examResponse->status()
|
|
|
+ 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
|
|
|
]);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- $examContent = $examResponse->body();
|
|
|
- if (empty($examContent)) {
|
|
|
- Log::error('ExamPdfExportService: 下载试卷PDF内容为空', ['url' => $examPdfUrl]);
|
|
|
+ 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
|
|
|
+ ]);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 写入文件并验证
|
|
|
- if (file_put_contents($examPdfPath, $examContent) === false) {
|
|
|
- Log::error('ExamPdfExportService: 写入试卷PDF文件失败', ['path' => $examPdfPath]);
|
|
|
- return null;
|
|
|
- }
|
|
|
+ $examSize = filesize($examPdfPath);
|
|
|
+ $gradingSize = filesize($gradingPdfPath);
|
|
|
|
|
|
- if (!file_exists($examPdfPath) || filesize($examPdfPath) === 0) {
|
|
|
- Log::error('ExamPdfExportService: 试卷PDF文件无效', [
|
|
|
- 'path' => $examPdfPath,
|
|
|
- 'exists' => file_exists($examPdfPath),
|
|
|
- 'size' => file_exists($examPdfPath) ? filesize($examPdfPath) : 'N/A'
|
|
|
+ Log::info('PDF文件验证成功', [
|
|
|
+ 'exam_pdf' => $examPdfPath,
|
|
|
+ 'grading_pdf' => $gradingPdfPath,
|
|
|
+ 'exam_size' => $examSize,
|
|
|
+ 'grading_size' => $gradingSize,
|
|
|
+ 'total_size' => $examSize + $gradingSize
|
|
|
+ ]);
|
|
|
+
|
|
|
+ if ($examSize < 1000 || $gradingSize < 1000) {
|
|
|
+ Log::warning('ExamPdfExportService: PDF文件过小,可能生成不完整', [
|
|
|
+ 'exam_size' => $examSize,
|
|
|
+ 'grading_size' => $gradingSize
|
|
|
]);
|
|
|
- return null;
|
|
|
}
|
|
|
|
|
|
if ($progressCallback) {
|
|
|
- $progressCallback(50, '下载判卷PDF...');
|
|
|
+ $progressCallback(20, '开始合并PDF文件...');
|
|
|
}
|
|
|
|
|
|
- // 【修复】下载判卷PDF - 添加HTTP状态码检查
|
|
|
- $gradingResponse = Http::get($gradingPdfUrl);
|
|
|
- if (!$gradingResponse->successful()) {
|
|
|
- Log::error('ExamPdfExportService: 下载判卷PDF失败', [
|
|
|
- 'url' => $gradingPdfUrl,
|
|
|
- 'status_code' => $gradingResponse->status()
|
|
|
+ // 【优化】合并PDF文件 - 使用快速合并模式
|
|
|
+ $mergedPdfPath = $tempDir . "/{$paperId}_merged.pdf";
|
|
|
+ $merged = $this->pdfMerger->mergeWithProgress([$examPdfPath, $gradingPdfPath], $mergedPdfPath, $progressCallback);
|
|
|
+
|
|
|
+ if (!$merged) {
|
|
|
+ Log::error('ExamPdfExportService: PDF文件合并失败', [
|
|
|
+ 'tool' => $this->pdfMerger->getMergeTool(),
|
|
|
+ 'exam_pdf' => $examPdfPath,
|
|
|
+ 'grading_pdf' => $gradingPdfPath,
|
|
|
+ 'output_pdf' => $mergedPdfPath
|
|
|
]);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- $gradingContent = $gradingResponse->body();
|
|
|
- if (empty($gradingContent)) {
|
|
|
- Log::error('ExamPdfExportService: 下载判卷PDF内容为空', ['url' => $gradingPdfUrl]);
|
|
|
+ // 【新增】验证合并后的PDF内容
|
|
|
+ if (!file_exists($mergedPdfPath)) {
|
|
|
+ Log::error('ExamPdfExportService: 合并后PDF文件不存在', ['path' => $mergedPdfPath]);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- // 写入文件并验证
|
|
|
- if (file_put_contents($gradingPdfPath, $gradingContent) === false) {
|
|
|
- Log::error('ExamPdfExportService: 写入判卷PDF文件失败', ['path' => $gradingPdfPath]);
|
|
|
- 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)
|
|
|
+ ]);
|
|
|
|
|
|
- if (!file_exists($gradingPdfPath) || filesize($gradingPdfPath) === 0) {
|
|
|
- Log::error('ExamPdfExportService: 判卷PDF文件无效', [
|
|
|
- 'path' => $gradingPdfPath,
|
|
|
- 'exists' => file_exists($gradingPdfPath),
|
|
|
- 'size' => file_exists($gradingPdfPath) ? filesize($gradingPdfPath) : 'N/A'
|
|
|
+ // 验证合并后的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)
|
|
|
]);
|
|
|
- return null;
|
|
|
}
|
|
|
|
|
|
- Log::info('PDF文件下载完成', [
|
|
|
- 'exam_path' => $examPdfPath,
|
|
|
- 'exam_size' => filesize($examPdfPath),
|
|
|
- 'grading_path' => $gradingPdfPath,
|
|
|
- 'grading_size' => filesize($gradingPdfPath)
|
|
|
- ]);
|
|
|
+ $mergeSuccess = true;
|
|
|
|
|
|
if ($progressCallback) {
|
|
|
- $progressCallback(60, '开始合并PDF文件...');
|
|
|
+ $progressCallback(80, '上传合并PDF...');
|
|
|
}
|
|
|
|
|
|
- // 【优化】合并PDF文件 - 使用快速合并模式
|
|
|
- $mergedPdfPath = $tempDir . "/{$paperId}_merged.pdf";
|
|
|
- $merged = $this->pdfMerger->mergeWithProgress([$examPdfPath, $gradingPdfPath], $mergedPdfPath, $progressCallback);
|
|
|
-
|
|
|
- if (!$merged) {
|
|
|
- Log::error('ExamPdfExportService: PDF文件合并失败', [
|
|
|
- 'tool' => $this->pdfMerger->getMergeTool()
|
|
|
+ // 读取合并后的PDF内容并上传到云存储
|
|
|
+ $mergedPdfContent = file_get_contents($mergedPdfPath);
|
|
|
+ if (strlen($mergedPdfContent) < 1000) {
|
|
|
+ Log::error('ExamPdfExportService: 合并PDF内容过小,上传失败', [
|
|
|
+ 'content_size' => strlen($mergedPdfContent),
|
|
|
+ 'file_size' => $mergedSize
|
|
|
]);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
- if ($progressCallback) {
|
|
|
- $progressCallback(90, '上传合并PDF...');
|
|
|
- }
|
|
|
-
|
|
|
- // 读取合并后的PDF内容并上传到云存储
|
|
|
- $mergedPdfContent = file_get_contents($mergedPdfPath);
|
|
|
$path = "exams/{$paperId}_all.pdf";
|
|
|
$mergedUrl = $this->pdfStorageService->put($path, $mergedPdfContent);
|
|
|
|
|
|
@@ -223,13 +294,24 @@ 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()
|
|
|
+ 'tool' => $this->pdfMerger->getMergeTool(),
|
|
|
+ 'exam_size' => $examSize,
|
|
|
+ 'grading_size' => $gradingSize,
|
|
|
+ 'merged_size' => $mergedSize
|
|
|
]);
|
|
|
return $mergedUrl;
|
|
|
|
|
|
@@ -246,14 +328,48 @@ class ExamPdfExportService
|
|
|
|
|
|
return null;
|
|
|
} finally {
|
|
|
- // 【修复】清理临时文件
|
|
|
- $tempFiles = [$examPdfPath, $gradingPdfPath, $mergedPdfPath];
|
|
|
- foreach ($tempFiles as $file) {
|
|
|
- if ($file && file_exists($file)) {
|
|
|
- @unlink($file);
|
|
|
+ // 【修复】优化临时文件清理逻辑:
|
|
|
+ // 1. 合并失败时不删除源文件,便于重试
|
|
|
+ // 2. 合并成功后才删除源文件
|
|
|
+ // 3. 保留合并后的文件一段时间,便于调试
|
|
|
+
|
|
|
+ if ($mergeSuccess && $uploadSuccess) {
|
|
|
+ // 合并成功且上传成功,删除源文件
|
|
|
+ $sourceFiles = [$examPdfPath, $gradingPdfPath];
|
|
|
+ foreach ($sourceFiles as $file) {
|
|
|
+ if ($file && file_exists($file)) {
|
|
|
+ @unlink($file);
|
|
|
+ Log::debug('删除源PDF文件', ['path' => $file]);
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
+ // 保留合并文件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)
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ } 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('清理临时文件完成');
|
|
|
+
|
|
|
+ Log::debug('PDF合并流程完成', [
|
|
|
+ 'merge_success' => $mergeSuccess,
|
|
|
+ 'upload_success' => $uploadSuccess
|
|
|
+ ]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1009,16 +1125,82 @@ 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
|
|
|
*/
|