', '123456789012345678'], ]; $bin = XlsxWriter::render($rows, ['widths' => [16, 24], 'title' => '白名单']); file_put_contents($out, $bin); echo "生成字节数: " . strlen($bin) . PHP_EOL; // 1) ZIP 头必须是 PK $pass = 0; $fail = 0; function chk(string $name, bool $ok, string $extra = ''): void { global $pass, $fail; if ($ok) { $pass++; echo " PASS $name" . ($extra ? " — $extra" : '') . PHP_EOL; } else { $fail++; echo " FAIL $name" . ($extra ? " — $extra" : '') . PHP_EOL; } } chk('ZIP 魔数 PK', substr($bin, 0, 2) === 'PK'); // 用 ZipArchive 解开校验包内结构(ZIP 内容已压缩,不能直接在二进制里搜字符串) $parts = []; if (class_exists('ZipArchive')) { $z = new ZipArchive(); if ($z->open($out) === true) { for ($i = 0; $i < $z->numFiles; $i++) { $n = $z->getNameIndex($i); $parts[$n] = $z->getFromIndex($i); } $z->close(); } } chk('包内含 xl/workbook.xml', isset($parts['xl/workbook.xml'])); chk('包内含 xl/worksheets/sheet1.xml', isset($parts['xl/worksheets/sheet1.xml'])); chk('包内含 xl/sharedStrings.xml', isset($parts['xl/sharedStrings.xml'])); chk('Content_Types 声明 workbook 主部件', strpos((string)($parts['[Content_Types].xml'] ?? ''), 'spreadsheetml.sheet.main+xml') !== false); chk('文本格式 numFmtId=49 已写入', strpos((string)($parts['xl/styles.xml'] ?? ''), 'numFmtId="49"') !== false); chk('学号以共享字符串写入(非纯数字单元格)', strpos((string)($parts['xl/sharedStrings.xml'] ?? ''), '2024010102001234') !== false); chk('列名(A/B)正确', strpos((string)($parts['xl/worksheets/sheet1.xml'] ?? ''), 'r="A1"') !== false && strpos((string)($parts['xl/worksheets/sheet1.xml'] ?? ''), 'r="B1"') !== false); // 2) 用自带的 XlsxReader 读回 $back = XlsxReader::read($out); chk('XlsxReader 成功解析', is_array($back) && count($back) > 0, '行数=' . count($back)); chk('行数一致', count($back) === count($rows), '期望 ' . count($rows) . ',实际 ' . count($back)); $first = $back[0] ?? []; chk('表头 = 姓名/登陆账号', ($first[0] ?? '') === '姓名' && ($first[1] ?? '') === '登陆账号', json_encode($first, JSON_UNESCAPED_UNICODE)); chk('普通行正确', ($back[1][0] ?? '') === '张三' && ($back[1][1] ?? '') === '2024010101', json_encode($back[1] ?? [], JSON_UNESCAPED_UNICODE)); chk('超长学号不被转科学计数法', ($back[2][1] ?? '') === '2024010102001234', '实际=' . ($back[2][1] ?? 'NULL')); chk('18 位纯数字学号原样保留', ($back[4][1] ?? '') === '123456789012345678', '实际=' . ($back[4][1] ?? 'NULL')); chk('特殊字符转义正确', ($back[4][0] ?? '') === 'Bob "引号" & <标签>', '实际=' . ($back[4][0] ?? 'NULL')); // 3) 无 ZipArchive 时的手写打包路径(stored):单独生成再用 XlsxReader 读回 $manualOut = __DIR__ . '/_xlsxwriter_manual.xlsx'; $manual = XlsxWriter::packManual($parts !== [] ? $parts : []); if ($parts !== []) { file_put_contents($manualOut, $manual); chk('手写 ZIP 魔数 PK', substr($manual, 0, 2) === 'PK'); $mBack = []; try { $mBack = XlsxReader::read($manualOut); } catch (Throwable $e) { $mBack = []; } chk('手写打包产物可被解析', count($mBack) === count($rows), '行数=' . count($mBack)); chk('手写打包内容一致', ($mBack[1][0] ?? '') === '张三' && ($mBack[4][1] ?? '') === '123456789012345678', json_encode($mBack[1] ?? [], JSON_UNESCAPED_UNICODE)); @unlink($manualOut); } @unlink($out); echo PHP_EOL . "结果:$pass 通过,$fail 失败" . PHP_EOL; exit($fail === 0 ? 0 : 1);