| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- <?php
- /**
- * 同一 questions.id:输出两份本地 PDF 供对比
- * A) 题目质检(pdf.question-check → generateQuestionCheckPdf)
- * B) 常规学生卷页面(pdf.exam-paper,含装订线与卷头,与组卷导出同源样式)
- *
- * 用法:
- * php scripts/compare_question_pdf_two_paths.php --id=34728
- * [--out-dir storage/app/audit_placeholder/pdf_compare]
- */
- declare(strict_types=1);
- require __DIR__.'/../vendor/autoload.php';
- $app = require __DIR__.'/../bootstrap/app.php';
- $kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
- $kernel->bootstrap();
- use App\Services\ExamPdfExportService;
- use App\Services\KatexRenderer;
- use Illuminate\Support\Facades\DB;
- $options = getopt('', ['id::', 'out-dir::', 'connection::', 'table::']);
- $questionId = isset($options['id']) ? max(1, (int) $options['id']) : 34728;
- $connection = isset($options['connection']) ? trim((string) $options['connection']) : config('database.default');
- $table = isset($options['table']) ? trim((string) $options['table']) : 'questions';
- $outRoot = isset($options['out-dir']) ? rtrim((string) $options['out-dir'], '/') : dirname(__DIR__).'/storage/app/audit_placeholder/pdf_compare';
- if ($outRoot === '' || ($outRoot[0] !== '/' && ! preg_match('#^[A-Za-z]:[\\\\/]#', $outRoot))) {
- $outRoot = dirname(__DIR__).'/'.ltrim($outRoot, '/');
- }
- $stamp = date('Ymd_His');
- $runDir = $outRoot.'/'.$stamp.'_q'.$questionId;
- if (! @mkdir($runDir, 0775, true) && ! is_dir($runDir)) {
- fwrite(STDERR, "Cannot mkdir {$runDir}\n");
- exit(1);
- }
- $row = DB::connection($connection)->table($table)->where('id', $questionId)->first();
- if ($row === null) {
- fwrite(STDERR, "Question id {$questionId} not found.\n");
- exit(1);
- }
- $questionMap = [(int) $row->id => $row];
- $grouped = groupQuestionsByType($questionMap, [$questionId]);
- $paper = buildVirtualPaper('PDF对比_'.$questionId, 'pdf_compare_'.$stamp, $grouped);
- /** @var ExamPdfExportService $pdfService */
- $pdfService = $app->make(ExamPdfExportService::class);
- // --- A 题目质检 ---
- $pdfMetaCheck = [
- 'student_name' => '对比',
- 'exam_code' => 'QC_'.$questionId,
- 'assemble_type_label' => '题目质检',
- 'header_title' => '对比|QC_'.$questionId.'|题目质检',
- 'exam_pdf_title' => '对比_QC_'.$questionId,
- 'grading_pdf_title' => '对比_QC_'.$questionId,
- 'knowledge_pdf_title' => '对比_QC_'.$questionId,
- ];
- $htmlCheck = view('pdf.question-check', [
- 'paper' => $paper,
- 'questions' => $grouped,
- 'student' => ['name' => '对比', 'grade' => '________'],
- 'teacher' => ['name' => '________'],
- 'pdfMeta' => $pdfMetaCheck,
- ])->render();
- $resultA = $pdfService->generateQuestionCheckPdf(
- $paper,
- $grouped,
- ['name' => '对比', 'grade' => '________'],
- ['name' => '________'],
- $runDir.'/A_question_check.pdf'
- );
- if (($resultA['local_path'] ?? '') === '') {
- fwrite(STDERR, "Path A failed: ".json_encode($resultA, JSON_UNESCAPED_UNICODE)."\n");
- exit(1);
- }
- // --- B 常规试卷(学生卷,无答案)---
- $pdfMetaExam = [
- 'exam_pdf_title' => '对比_常规卷_'.$questionId,
- 'exam_code' => 'EX_'.$questionId,
- 'student_name' => '对比',
- 'header_title' => '对比|EX_'.$questionId.'|常规卷',
- 'assemble_type_label' => '样式对比',
- ];
- $htmlExam = view('pdf.exam-paper', [
- 'paper' => $paper,
- 'questions' => $grouped,
- 'student' => ['name' => '对比', 'grade' => '________'],
- 'teacher' => ['name' => '________'],
- 'includeAnswer' => false,
- 'pdfMeta' => $pdfMetaExam,
- ])->render();
- $katex = new KatexRenderer();
- $htmlExam = $katex->renderHtml($htmlExam);
- $ref = new ReflectionClass($pdfService);
- $mEnsure = $ref->getMethod('ensureUtf8Html');
- $mEnsure->setAccessible(true);
- $mBuild = $ref->getMethod('buildPdf');
- $mBuild->setAccessible(true);
- $htmlExamUtf8 = $mEnsure->invoke($pdfService, $htmlExam);
- $binaryExam = $mBuild->invoke($pdfService, $htmlExamUtf8, true);
- if ($binaryExam === null || $binaryExam === '') {
- fwrite(STDERR, "Path B buildPdf returned empty.\n");
- exit(1);
- }
- $pathB = $runDir.'/B_exam_paper_student.pdf';
- file_put_contents($pathB, $binaryExam);
- // 可选:保存 HTML 便于 diff
- file_put_contents($runDir.'/A_question_check.html', $htmlCheck);
- file_put_contents($runDir.'/B_exam_paper.html', $htmlExam);
- echo json_encode([
- 'question_id' => $questionId,
- 'output_directory' => $runDir,
- 'A_question_check_pdf' => $resultA['local_path'],
- 'B_exam_paper_pdf' => $pathB,
- 'notes' => 'A=题目质检模板;B=常规组卷学生卷模板(exam-paper)。题干区均用 components.exam.paper-body。',
- ], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)."\n";
- /**
- * @param array<int, object> $questionMap
- */
- function groupQuestionsByType(array $questionMap, array $originalOrder): array
- {
- $grouped = ['choice' => [], 'fill' => [], 'answer' => []];
- $n = 1;
- foreach ($originalOrder as $id) {
- if (! isset($questionMap[$id])) {
- continue;
- }
- $q = $questionMap[$id];
- $type = normalizeQuestionType($q->question_type ?? null);
- $grouped[$type][] = (object) [
- 'id' => $q->id,
- 'question_number' => $n++,
- 'content' => $q->stem,
- 'options' => is_string($q->options) ? json_decode($q->options, true) : ($q->options ?? []),
- 'answer' => $q->answer,
- 'solution' => $q->solution,
- 'score' => match ($type) {
- 'choice', 'fill' => 5,
- default => 10,
- },
- 'difficulty' => $q->difficulty,
- 'kp_code' => $q->kp_code,
- ];
- }
- return $grouped;
- }
- function normalizeQuestionType(?string $type): string
- {
- if (! $type) {
- return 'answer';
- }
- $type = strtolower(trim($type));
- $map = [
- 'choice' => 'choice', '选择题' => 'choice', 'single_choice' => 'choice', 'multiple_choice' => 'choice',
- 'fill' => 'fill', '填空题' => 'fill', 'blank' => 'fill',
- 'answer' => 'answer', '解答题' => 'answer', 'subjective' => 'answer',
- ];
- return $map[$type] ?? 'answer';
- }
- function buildVirtualPaper(string $paperName, string $studentId, array $groupedQuestions): object
- {
- $totalScore = 0;
- $totalQuestions = 0;
- foreach ($groupedQuestions as $questions) {
- foreach ($questions as $q) {
- $totalScore += $q->score;
- $totalQuestions++;
- }
- }
- // PaperNaming::extractExamCode 要求可解析为合法 15 位考试编码
- $paperId = 'paper_100000000000001';
- return (object) [
- 'paper_id' => $paperId,
- 'paper_name' => $paperName,
- 'total_score' => $totalScore,
- 'total_questions' => $totalQuestions,
- 'created_at' => now()->toDateTimeString(),
- ];
- }
|