|
@@ -6,6 +6,7 @@ use App\DTO\ExamAnalysisDataDto;
|
|
|
use App\DTO\ReportPayloadDto;
|
|
use App\DTO\ReportPayloadDto;
|
|
|
use App\Models\Paper;
|
|
use App\Models\Paper;
|
|
|
use App\Models\Student;
|
|
use App\Models\Student;
|
|
|
|
|
+use App\Support\PaperNaming;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\File;
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Http;
|
|
@@ -13,6 +14,7 @@ use Illuminate\Support\Facades\Log;
|
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
use Illuminate\Support\Facades\URL;
|
|
use Illuminate\Support\Facades\URL;
|
|
|
|
|
+use Illuminate\Support\Str;
|
|
|
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
|
use Symfony\Component\Process\Exception\ProcessSignaledException;
|
|
|
use Symfony\Component\Process\Exception\ProcessTimedOutException;
|
|
use Symfony\Component\Process\Exception\ProcessTimedOutException;
|
|
|
use Symfony\Component\Process\Process;
|
|
use Symfony\Component\Process\Process;
|
|
@@ -151,7 +153,14 @@ class ExamPdfExportService
|
|
|
Log::info('generateUnifiedPdf: PDF生成完成', ['paper_id' => $paperId, 'pdf_size' => strlen($pdfBinary)]);
|
|
Log::info('generateUnifiedPdf: PDF生成完成', ['paper_id' => $paperId, 'pdf_size' => strlen($pdfBinary)]);
|
|
|
|
|
|
|
|
// 步骤4:保存PDF
|
|
// 步骤4:保存PDF
|
|
|
- $path = "exams/{$paperId}_all.pdf";
|
|
|
|
|
|
|
+ $paper = Paper::where('paper_id', $paperId)->first();
|
|
|
|
|
+ if (! $paper) {
|
|
|
|
|
+ Log::error('ExamPdfExportService: 生成统一PDF失败,未找到试卷', ['paper_id' => $paperId]);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $allPdfName = $this->buildPdfFileName($paper);
|
|
|
|
|
+ $path = "exams/{$allPdfName}";
|
|
|
Log::info('generateUnifiedPdf: 开始保存PDF到云存储', ['paper_id' => $paperId, 'path' => $path]);
|
|
Log::info('generateUnifiedPdf: 开始保存PDF到云存储', ['paper_id' => $paperId, 'path' => $path]);
|
|
|
$url = $this->pdfStorageService->put($path, $pdfBinary);
|
|
$url = $this->pdfStorageService->put($path, $pdfBinary);
|
|
|
if (! $url) {
|
|
if (! $url) {
|
|
@@ -373,7 +382,14 @@ class ExamPdfExportService
|
|
|
|
|
|
|
|
// 读取合并后的PDF内容并上传到云存储
|
|
// 读取合并后的PDF内容并上传到云存储
|
|
|
$mergedPdfContent = file_get_contents($mergedPdfPath);
|
|
$mergedPdfContent = file_get_contents($mergedPdfPath);
|
|
|
- $path = "exams/{$paperId}_all.pdf";
|
|
|
|
|
|
|
+ $paper = Paper::where('paper_id', $paperId)->first();
|
|
|
|
|
+ if (! $paper) {
|
|
|
|
|
+ Log::error('ExamPdfExportService: 合并PDF失败,未找到试卷', ['paper_id' => $paperId]);
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $allPdfName = $this->buildPdfFileName($paper);
|
|
|
|
|
+ $path = "exams/{$allPdfName}";
|
|
|
$mergedUrl = $this->pdfStorageService->put($path, $mergedPdfContent);
|
|
$mergedUrl = $this->pdfStorageService->put($path, $mergedPdfContent);
|
|
|
|
|
|
|
|
if (! $mergedUrl) {
|
|
if (! $mergedUrl) {
|
|
@@ -571,6 +587,21 @@ class ExamPdfExportService
|
|
|
$teacherModel = \App\Models\Teacher::find($paper->teacher_id);
|
|
$teacherModel = \App\Models\Teacher::find($paper->teacher_id);
|
|
|
$student = ['name' => $studentModel->name ?? ($paper->student_id ?? '________'), 'grade' => $studentModel->grade ?? '________'];
|
|
$student = ['name' => $studentModel->name ?? ($paper->student_id ?? '________'), 'grade' => $studentModel->grade ?? '________'];
|
|
|
$teacher = ['name' => $teacherModel->name ?? ($paper->teacher_id ?? '________')];
|
|
$teacher = ['name' => $teacherModel->name ?? ($paper->teacher_id ?? '________')];
|
|
|
|
|
+ $examCode = PaperNaming::extractExamCode((string) $paper->paper_id);
|
|
|
|
|
+ try {
|
|
|
|
|
+ $assembleTypeLabel = PaperNaming::assembleTypeLabel((int) $paper->paper_type);
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ $assembleTypeLabel = '未知类型';
|
|
|
|
|
+ }
|
|
|
|
|
+ $pdfMeta = [
|
|
|
|
|
+ 'student_name' => $student['name'],
|
|
|
|
|
+ 'exam_code' => $examCode,
|
|
|
|
|
+ 'assemble_type_label' => $assembleTypeLabel,
|
|
|
|
|
+ 'header_title' => $examCode,
|
|
|
|
|
+ 'exam_pdf_title' => '试卷_'.$examCode,
|
|
|
|
|
+ 'grading_pdf_title' => '判卷_'.$examCode,
|
|
|
|
|
+ 'knowledge_pdf_title' => '知识点讲解_'.$examCode,
|
|
|
|
|
+ ];
|
|
|
|
|
|
|
|
$html = view($viewName, [
|
|
$html = view($viewName, [
|
|
|
'paper' => $paper,
|
|
'paper' => $paper,
|
|
@@ -578,6 +609,7 @@ class ExamPdfExportService
|
|
|
'includeAnswer' => $includeAnswer,
|
|
'includeAnswer' => $includeAnswer,
|
|
|
'student' => $student,
|
|
'student' => $student,
|
|
|
'teacher' => $teacher,
|
|
'teacher' => $teacher,
|
|
|
|
|
+ 'pdfMeta' => $pdfMeta,
|
|
|
])->render();
|
|
])->render();
|
|
|
|
|
|
|
|
if (empty(trim($html))) {
|
|
if (empty(trim($html))) {
|
|
@@ -1713,6 +1745,13 @@ class ExamPdfExportService
|
|
|
|
|
|
|
|
// 构建HTML内容
|
|
// 构建HTML内容
|
|
|
$bodyContent = '';
|
|
$bodyContent = '';
|
|
|
|
|
+ $mergedTitle = '试卷与判卷合并';
|
|
|
|
|
+ if (preg_match('/<title>(.*?)<\/title>/is', $examHead, $titleMatches)) {
|
|
|
|
|
+ $candidateTitle = trim(strip_tags($titleMatches[1]));
|
|
|
|
|
+ if ($candidateTitle !== '') {
|
|
|
|
|
+ $mergedTitle = $candidateTitle;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 如果有知识点讲解,添加到最前面
|
|
// 如果有知识点讲解,添加到最前面
|
|
|
if ($kpExplainBody) {
|
|
if ($kpExplainBody) {
|
|
@@ -1745,7 +1784,7 @@ class ExamPdfExportService
|
|
|
<head>
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
- <title>试卷与判卷合并</title>
|
|
|
|
|
|
|
+ <title>'.$mergedTitle.'</title>
|
|
|
'.$headContent.'
|
|
'.$headContent.'
|
|
|
</head>
|
|
</head>
|
|
|
<body>
|
|
<body>
|
|
@@ -1949,6 +1988,56 @@ class ExamPdfExportService
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ private function buildPaperNamePrefix(Paper $paper): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $assembleType = ($paper->paper_type === null || $paper->paper_type === '')
|
|
|
|
|
+ ? null
|
|
|
|
|
+ : (int) $paper->paper_type;
|
|
|
|
|
+ $studentName = Student::query()
|
|
|
|
|
+ ->where('student_id', $paper->student_id)
|
|
|
|
|
+ ->value('name') ?? ($paper->student_id ?: '________');
|
|
|
|
|
+ try {
|
|
|
|
|
+ $assembleTypeLabel = $assembleType !== null ? PaperNaming::assembleTypeLabel($assembleType) : '未知类型';
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ $assembleTypeLabel = '未知类型';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return "{$studentName}_".PaperNaming::extractExamCode((string) $paper->paper_id)."_{$assembleTypeLabel}";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function buildPaperDisplayTitle(Paper $paper): string
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->buildPaperNamePrefix($paper).'_'.now()->format('Ymd');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function buildPdfFileName(Paper $paper, ?string $stamp = null): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $basePrefix = $this->buildPaperNamePrefix($paper);
|
|
|
|
|
+ $stamp = $stamp ?: now()->format('YmdHis').strtoupper(Str::random(4));
|
|
|
|
|
+ $base = "{$basePrefix}_{$stamp}";
|
|
|
|
|
+ $safe = PaperNaming::toSafeFilename($base);
|
|
|
|
|
+
|
|
|
|
|
+ return "{$safe}.pdf";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function extractUploadStamp(string $url): ?string
|
|
|
|
|
+ {
|
|
|
|
|
+ $path = parse_url($url, PHP_URL_PATH);
|
|
|
|
|
+ if (! is_string($path) || $path === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ $stem = pathinfo($path, PATHINFO_FILENAME);
|
|
|
|
|
+ if (! is_string($stem) || $stem === '') {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (preg_match('/(\d{14}[A-Za-z0-9]{4})/', $stem, $matches)) {
|
|
|
|
|
+ return $matches[1];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 保存PDF URL到数据库
|
|
* 保存PDF URL到数据库
|
|
|
*/
|
|
*/
|
|
@@ -1957,11 +2046,22 @@ class ExamPdfExportService
|
|
|
try {
|
|
try {
|
|
|
$paper = Paper::where('paper_id', $paperId)->first();
|
|
$paper = Paper::where('paper_id', $paperId)->first();
|
|
|
if ($paper) {
|
|
if ($paper) {
|
|
|
- $paper->update([$field => $url]);
|
|
|
|
|
|
|
+ $paperDisplayTitle = $this->buildPaperDisplayTitle($paper);
|
|
|
|
|
+ $stamp = $this->extractUploadStamp($url);
|
|
|
|
|
+ if ($stamp) {
|
|
|
|
|
+ $paperDisplayTitle = $this->buildPaperNamePrefix($paper).'_'.$stamp;
|
|
|
|
|
+ }
|
|
|
|
|
+ $updatePayload = [
|
|
|
|
|
+ $field => $url,
|
|
|
|
|
+ 'paper_name' => $paperDisplayTitle,
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ $paper->update($updatePayload);
|
|
|
Log::info('ExamPdfExportService: PDF URL已写入数据库', [
|
|
Log::info('ExamPdfExportService: PDF URL已写入数据库', [
|
|
|
'paper_id' => $paperId,
|
|
'paper_id' => $paperId,
|
|
|
'field' => $field,
|
|
'field' => $field,
|
|
|
'url' => $url,
|
|
'url' => $url,
|
|
|
|
|
+ 'paper_name' => $paperDisplayTitle,
|
|
|
]);
|
|
]);
|
|
|
}
|
|
}
|
|
|
} catch (\Throwable $e) {
|
|
} catch (\Throwable $e) {
|