导出:zip 用 CreateRaw 写长度/CRC,别再带 data descriptor(真机判整包损坏)

真正的病根(09-16 起每一份站点导出都被拒都是它):Go 的 zip.Writer 默认走流式写入——本地头里
的长度与 CRC 留 0、真值挪到条目尾部的数据描述符里,并在 flag 里置 bit3。**真机的读取器只认
「本地头里就写着长度」的条目**,遇到这种包直接判成损坏,报「备份恢复失败,你的当前数据未被更改」。

铁证(直接读文件头):
  站点 Go 写的每一份导出        本地头 flags=0x0008  csize/usize=0/0   → 全部被拒
  20260920-fixed-A-数据修复    本地头 flags=0x0000  有长度             → 真机接受(Python 重打包)
  真机自己的包                 本地头 flags=0x0800  有长度             → 正常(只有 UTF-8 位)

这也解释了之前为什么全都修错了方向:09-20 那次「修悬空引用后就能导入」其实是 Python 重打包顺手把
zip 写法修对了;而 09-29 一整天修的内容问题(活动颜色为 NULL、交通方式重名)都不是拦路虎。

修复:zipBackup 改用 zw.CreateRaw,先把三个文件读进内存算出 CRC32 与长度写进本地头,
不再用会置 bit3 的 zw.CreateHeader。

用例:TestZipBackupWritesNoDataDescriptor —— 直接读第 0 个条目的**本地头**,断言 bit3 为 0、
压缩/原始长度非 0、CRC 正确(变异验证:改回 CreateHeader → 立刻变红,且精确复现 0x0008 + 长度 0)。
真实数据验证:修复后新导出的本地头 flags=0x0000、长度正确。

全量验收 296 PASS / 0 FAIL / 0 SKIP。
This commit is contained in:
tamakyi
2026-09-29 15:04:07 +08:00
parent 9c1413bb39
commit f98d3864b8
2 changed files with 84 additions and 13 deletions
+18 -13
View File
@@ -8,6 +8,7 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"hash/crc32"
"io"
"math"
"os"
@@ -1476,19 +1477,23 @@ func zipBackup(sqlitePath string, dst io.Writer) error {
}
}
name := "LifeEasy.sqlite" + suffix
if err := func() error {
f, err := os.Open(p)
if err != nil {
return err
}
defer f.Close()
w, err := zw.CreateHeader(&zip.FileHeader{Name: name, Method: zip.Store})
if err != nil {
return err
}
_, err = io.Copy(w, f)
return err
}(); err != nil {
// 必须用 CreateRaw 把长度与 CRC 先写进本地头:zip.Writer 默认走流式写入,会把这三项留成 0
// 并在 flag 里置 bit3(data descriptor)、把真值挪到条目尾部——真机的读取器只认本地头里就
// 写着长度的条目,遇到这种包会直接判成损坏(实测 2026-09-16 起每一份 Go 写的导出都被拒,
// 而唯一被接受过的那份恰好是 Python 重新打包的,它的 flags 是 0x0000)。
data, err := os.ReadFile(p)
if err != nil {
return fmt.Errorf("读 %s 失败: %w", name, err)
}
fh := &zip.FileHeader{Name: name, Method: zip.Store}
fh.CRC32 = crc32.ChecksumIEEE(data)
fh.CompressedSize64 = uint64(len(data))
fh.UncompressedSize64 = uint64(len(data))
w, err := zw.CreateRaw(fh)
if err != nil {
return fmt.Errorf("打包 %s 失败: %w", name, err)
}
if _, err := w.Write(data); err != nil {
return fmt.Errorf("打包 %s 失败: %w", name, err)
}
}