|
@@ -33,69 +33,20 @@ class ExamPdfExportService
|
|
|
private readonly PdfMerger $pdfMerger
|
|
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(不含答案)
|
|
* 生成试卷 PDF(不含答案)
|
|
|
*/
|
|
*/
|
|
|
public function generateExamPdf(string $paperId): ?string
|
|
public function generateExamPdf(string $paperId): ?string
|
|
|
{
|
|
{
|
|
|
Log::info('generateExamPdf 开始:', ['paper_id' => $paperId]);
|
|
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
|
|
public function generateGradingPdf(string $paperId): ?string
|
|
|
{
|
|
{
|
|
|
Log::info('generateGradingPdf 开始:', ['paper_id' => $paperId]);
|
|
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(试卷 + 判卷)
|
|
|
* 先分别生成两个PDF,然后合并
|
|
* 先分别生成两个PDF,然后合并
|
|
|
- * 【优化】添加进度回调支持和快速合并模式
|
|
|
|
|
- * 【修复】优化临时文件清理逻辑,确保合并成功后才删除源文件
|
|
|
|
|
*/
|
|
*/
|
|
|
- public function generateMergedPdf(string $paperId, ?callable $progressCallback = null): ?string
|
|
|
|
|
|
|
+ public function generateMergedPdf(string $paperId): ?string
|
|
|
{
|
|
{
|
|
|
Log::info('generateMergedPdf 开始:', ['paper_id' => $paperId]);
|
|
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");
|
|
$tempDir = storage_path("app/temp");
|
|
|
if (!is_dir($tempDir)) {
|
|
if (!is_dir($tempDir)) {
|
|
|
mkdir($tempDir, 0755, true);
|
|
mkdir($tempDir, 0755, true);
|
|
@@ -181,142 +81,65 @@ class ExamPdfExportService
|
|
|
$examPdfPath = null;
|
|
$examPdfPath = null;
|
|
|
$gradingPdfPath = null;
|
|
$gradingPdfPath = null;
|
|
|
$mergedPdfPath = null;
|
|
$mergedPdfPath = null;
|
|
|
- $mergeSuccess = false;
|
|
|
|
|
- $uploadSuccess = false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
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,
|
|
'exam_url' => $examPdfUrl,
|
|
|
'grading_url' => $gradingPdfUrl
|
|
'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;
|
|
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;
|
|
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";
|
|
$mergedPdfPath = $tempDir . "/{$paperId}_merged.pdf";
|
|
|
- $merged = $this->pdfMerger->mergeWithProgress([$examPdfPath, $gradingPdfPath], $mergedPdfPath, $progressCallback);
|
|
|
|
|
|
|
+ $merged = $this->pdfMerger->merge([$examPdfPath, $gradingPdfPath], $mergedPdfPath);
|
|
|
|
|
|
|
|
if (!$merged) {
|
|
if (!$merged) {
|
|
|
Log::error('ExamPdfExportService: PDF文件合并失败', [
|
|
Log::error('ExamPdfExportService: PDF文件合并失败', [
|
|
|
- 'tool' => $this->pdfMerger->getMergeTool(),
|
|
|
|
|
- 'exam_pdf' => $examPdfPath,
|
|
|
|
|
- 'grading_pdf' => $gradingPdfPath,
|
|
|
|
|
- 'output_pdf' => $mergedPdfPath
|
|
|
|
|
|
|
+ 'tool' => $this->pdfMerger->getMergeTool()
|
|
|
]);
|
|
]);
|
|
|
return null;
|
|
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内容并上传到云存储
|
|
// 读取合并后的PDF内容并上传到云存储
|
|
|
$mergedPdfContent = file_get_contents($mergedPdfPath);
|
|
$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";
|
|
$path = "exams/{$paperId}_all.pdf";
|
|
|
$mergedUrl = $this->pdfStorageService->put($path, $mergedPdfContent);
|
|
$mergedUrl = $this->pdfStorageService->put($path, $mergedPdfContent);
|
|
|
|
|
|
|
@@ -325,24 +148,13 @@ class ExamPdfExportService
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Log::info('ExamPdfExportService: 合并PDF上传成功', [
|
|
|
|
|
- 'url' => $mergedUrl,
|
|
|
|
|
- 'content_size' => strlen($mergedPdfContent),
|
|
|
|
|
- 'file_size' => $mergedSize
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
- $uploadSuccess = true;
|
|
|
|
|
-
|
|
|
|
|
// 保存到数据库的all_pdf_url字段
|
|
// 保存到数据库的all_pdf_url字段
|
|
|
$this->saveAllPdfUrlToDatabase($paperId, $mergedUrl);
|
|
$this->saveAllPdfUrlToDatabase($paperId, $mergedUrl);
|
|
|
|
|
|
|
|
Log::info('generateMergedPdf 完成:', [
|
|
Log::info('generateMergedPdf 完成:', [
|
|
|
'paper_id' => $paperId,
|
|
'paper_id' => $paperId,
|
|
|
'url' => $mergedUrl,
|
|
'url' => $mergedUrl,
|
|
|
- 'tool' => $this->pdfMerger->getMergeTool(),
|
|
|
|
|
- 'exam_size' => $examSize,
|
|
|
|
|
- 'grading_size' => $gradingSize,
|
|
|
|
|
- 'merged_size' => $mergedSize
|
|
|
|
|
|
|
+ 'tool' => $this->pdfMerger->getMergeTool()
|
|
|
]);
|
|
]);
|
|
|
return $mergedUrl;
|
|
return $mergedUrl;
|
|
|
|
|
|
|
@@ -352,61 +164,16 @@ class ExamPdfExportService
|
|
|
'error' => $e->getMessage(),
|
|
'error' => $e->getMessage(),
|
|
|
'trace' => $e->getTraceAsString(),
|
|
'trace' => $e->getTraceAsString(),
|
|
|
]);
|
|
]);
|
|
|
-
|
|
|
|
|
- if ($progressCallback) {
|
|
|
|
|
- $progressCallback(-1, '合并PDF失败: ' . $e->getMessage());
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
return null;
|
|
return null;
|
|
|
} finally {
|
|
} 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
|
|
private function buildPdf(string $html): ?string
|
|
|
{
|
|
{
|
|
|
- Log::info('ExamPdfExportService: buildPdf开始', ['html_size' => strlen($html)]);
|
|
|
|
|
-
|
|
|
|
|
$tmpHtml = tempnam(sys_get_temp_dir(), 'exam_html_') . '.html';
|
|
$tmpHtml = tempnam(sys_get_temp_dir(), 'exam_html_') . '.html';
|
|
|
- Log::info('ExamPdfExportService: 创建临时HTML文件', ['tmp_html' => $tmpHtml]);
|
|
|
|
|
-
|
|
|
|
|
$utf8Html = $this->ensureUtf8Html($html);
|
|
$utf8Html = $this->ensureUtf8Html($html);
|
|
|
file_put_contents($tmpHtml, $utf8Html);
|
|
file_put_contents($tmpHtml, $utf8Html);
|
|
|
|
|
|
|
|
- Log::info('ExamPdfExportService: HTML文件已写入', ['tmp_html' => $tmpHtml, 'size' => filesize($tmpHtml)]);
|
|
|
|
|
-
|
|
|
|
|
// 仅使用Chrome渲染
|
|
// 仅使用Chrome渲染
|
|
|
- Log::info('ExamPdfExportService: 开始调用renderWithChrome', ['tmp_html' => $tmpHtml]);
|
|
|
|
|
$chromePdf = $this->renderWithChrome($tmpHtml);
|
|
$chromePdf = $this->renderWithChrome($tmpHtml);
|
|
|
- Log::info('ExamPdfExportService: renderWithChrome完成', [
|
|
|
|
|
- 'pdf_size' => $chromePdf ? strlen($chromePdf) : 0,
|
|
|
|
|
- 'pdf_success' => !empty($chromePdf)
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
@unlink($tmpHtml);
|
|
@unlink($tmpHtml);
|
|
|
return $chromePdf;
|
|
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
|
|
* 使用Chrome渲染PDF
|
|
|
*/
|
|
*/
|
|
@@ -1252,12 +953,6 @@ class ExamPdfExportService
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Log::info('ExamPdfExportService: 开始Chrome渲染', [
|
|
|
|
|
- 'html_path' => $htmlPath,
|
|
|
|
|
- 'tmp_pdf' => $tmpPdf,
|
|
|
|
|
- 'chrome_binary' => $chromeBinary
|
|
|
|
|
- ]);
|
|
|
|
|
-
|
|
|
|
|
// 设置运行时目录
|
|
// 设置运行时目录
|
|
|
$runtimeHome = sys_get_temp_dir() . '/chrome-home';
|
|
$runtimeHome = sys_get_temp_dir() . '/chrome-home';
|
|
|
$runtimeXdg = sys_get_temp_dir() . '/chrome-xdg';
|
|
$runtimeXdg = sys_get_temp_dir() . '/chrome-xdg';
|
|
@@ -1268,14 +963,15 @@ class ExamPdfExportService
|
|
|
@File::makeDirectory($runtimeXdg, 0755, true);
|
|
@File::makeDirectory($runtimeXdg, 0755, true);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 【性能优化】深度优化Chrome参数,提升渲染速度
|
|
|
|
|
$process = new Process([
|
|
$process = new Process([
|
|
|
$chromeBinary,
|
|
$chromeBinary,
|
|
|
- '--headless=new',
|
|
|
|
|
|
|
+ '--headless',
|
|
|
'--disable-gpu',
|
|
'--disable-gpu',
|
|
|
'--no-sandbox',
|
|
'--no-sandbox',
|
|
|
'--disable-setuid-sandbox',
|
|
'--disable-setuid-sandbox',
|
|
|
'--disable-dev-shm-usage',
|
|
'--disable-dev-shm-usage',
|
|
|
|
|
+ '--no-zygote',
|
|
|
|
|
+ '--disable-features=VizDisplayCompositor',
|
|
|
'--disable-software-rasterizer',
|
|
'--disable-software-rasterizer',
|
|
|
'--disable-extensions',
|
|
'--disable-extensions',
|
|
|
'--disable-background-networking',
|
|
'--disable-background-networking',
|
|
@@ -1298,139 +994,50 @@ class ExamPdfExportService
|
|
|
'--disable-backgrounding-occluded-windows',
|
|
'--disable-backgrounding-occluded-windows',
|
|
|
'--disable-renderer-backgrounding',
|
|
'--disable-renderer-backgrounding',
|
|
|
'--disable-features=AudioServiceOutOfProcess',
|
|
'--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,
|
|
'--user-data-dir=' . $userDataDir,
|
|
|
'--print-to-pdf=' . $tmpPdf,
|
|
'--print-to-pdf=' . $tmpPdf,
|
|
|
'--print-to-pdf-no-header',
|
|
'--print-to-pdf-no-header',
|
|
|
'--allow-file-access-from-files',
|
|
'--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,
|
|
'file://' . $htmlPath,
|
|
|
], null, [
|
|
], null, [
|
|
|
'HOME' => $runtimeHome,
|
|
'HOME' => $runtimeHome,
|
|
|
'XDG_RUNTIME_DIR' => $runtimeXdg,
|
|
'XDG_RUNTIME_DIR' => $runtimeXdg,
|
|
|
]);
|
|
]);
|
|
|
|
|
|
|
|
- // 【性能优化】增加超时时间到200秒(原来是45秒)
|
|
|
|
|
- // 匹配25秒虚拟时间预算,同时给Chrome充足时间处理复杂页面
|
|
|
|
|
- $process->setTimeout(200);
|
|
|
|
|
|
|
+ $process->setTimeout(60);
|
|
|
$killSignal = \defined('SIGKILL') ? \SIGKILL : 9;
|
|
$killSignal = \defined('SIGKILL') ? \SIGKILL : 9;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
$startedAt = microtime(true);
|
|
$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();
|
|
$process->start();
|
|
|
$pdfGenerated = false;
|
|
$pdfGenerated = false;
|
|
|
|
|
|
|
|
- // 【性能优化】调整轮询时间:195秒轮询(小于Chrome超时200秒)
|
|
|
|
|
|
|
+ // 轮询检测PDF是否生成
|
|
|
$pollStart = microtime(true);
|
|
$pollStart = microtime(true);
|
|
|
- $maxPollSeconds = 195; // 从43秒增加到195秒
|
|
|
|
|
- $checkInterval = 30_000; // 保持30ms检查间隔
|
|
|
|
|
-
|
|
|
|
|
- // 【性能优化】缩短初始等待时间到500ms
|
|
|
|
|
- usleep(500_000);
|
|
|
|
|
-
|
|
|
|
|
|
|
+ $maxPollSeconds = 30;
|
|
|
while ($process->isRunning() && (microtime(true) - $pollStart) < $maxPollSeconds) {
|
|
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) {
|
|
if (file_exists($tmpPdf) && filesize($tmpPdf) > 0) {
|
|
|
$pdfGenerated = true;
|
|
$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;
|
|
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()) {
|
|
if ($process->isRunning()) {
|
|
|
- Log::warning('ExamPdfExportService: Chrome进程仍在运行,强制停止', [
|
|
|
|
|
- 'elapsed' => round($elapsed, 2) . 's',
|
|
|
|
|
- 'reason' => 'timeout'
|
|
|
|
|
- ]);
|
|
|
|
|
- $process->stop(1, $killSignal);
|
|
|
|
|
|
|
+ $process->stop(5, $killSignal);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$process->wait();
|
|
$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()) {
|
|
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);
|
|
return $this->handleChromeProcessResult($tmpPdf, $userDataDir, $process, $startedAt);
|
|
|
} catch (\Throwable $e) {
|
|
} catch (\Throwable $e) {
|
|
|
- Log::error('ExamPdfExportService: Chrome渲染异常', [
|
|
|
|
|
- 'error' => $e->getMessage(),
|
|
|
|
|
- 'elapsed' => round(microtime(true) - $startedAt, 2) . 's',
|
|
|
|
|
- 'trace' => $e->getTraceAsString()
|
|
|
|
|
- ]);
|
|
|
|
|
if ($process->isRunning()) {
|
|
if ($process->isRunning()) {
|
|
|
- $process->stop(1, $killSignal);
|
|
|
|
|
|
|
+ $process->stop(5, $killSignal);
|
|
|
}
|
|
}
|
|
|
return $this->handleChromeProcessResult($tmpPdf, $userDataDir, $process, null);
|
|
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
|
|
private function handleChromeProcessResult(string $tmpPdf, string $userDataDir, Process $process, ?float $startedAt): ?string
|
|
|
{
|
|
{
|
|
|
- $elapsed = $startedAt ? round(microtime(true) - $startedAt, 2) : 0;
|
|
|
|
|
$pdfExists = file_exists($tmpPdf);
|
|
$pdfExists = file_exists($tmpPdf);
|
|
|
$pdfSize = $pdfExists ? filesize($tmpPdf) : null;
|
|
$pdfSize = $pdfExists ? filesize($tmpPdf) : null;
|
|
|
|
|
|
|
|
if (!$process->isSuccessful()) {
|
|
if (!$process->isSuccessful()) {
|
|
|
if ($pdfExists && $pdfSize > 0) {
|
|
if ($pdfExists && $pdfSize > 0) {
|
|
|
Log::warning('ExamPdfExportService: Chrome进程异常但生成了PDF', [
|
|
Log::warning('ExamPdfExportService: Chrome进程异常但生成了PDF', [
|
|
|
- 'elapsed' => $elapsed . 's',
|
|
|
|
|
'exit_code' => $process->getExitCode(),
|
|
'exit_code' => $process->getExitCode(),
|
|
|
'tmp_pdf_size' => $pdfSize,
|
|
'tmp_pdf_size' => $pdfSize,
|
|
|
]);
|
|
]);
|
|
|
} else {
|
|
} else {
|
|
|
Log::error('ExamPdfExportService: Chrome渲染失败', [
|
|
Log::error('ExamPdfExportService: Chrome渲染失败', [
|
|
|
- 'elapsed' => $elapsed . 's',
|
|
|
|
|
'exit_code' => $process->getExitCode(),
|
|
'exit_code' => $process->getExitCode(),
|
|
|
'error' => $process->getErrorOutput(),
|
|
'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);
|
|
@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);
|
|
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;
|
|
return $pdfBinary ?: null;
|
|
|
}
|
|
}
|
|
|
|
|
|