zip.Writer.WriteHeader 会静默失败,必须检查 err;Header.Name 需标准化并校验;写入顺序影响解压体验;大文件需流式处理防 OOM;Windows 中文名需设 Flags=0x800 启用 UTF-8。

zip.Writer.WriteHeader 会静默失败,必须检查 err
写入 ZIP 文件时,zip.Writer.WriteHeader 看似只是“准备一个文件头”,但实际它会校验 Header.Name 是否合法(比如含非法字符、路径穿越如 ../etc/passwd),不合法就直接返回 error,而后续 io.Copy 仍会继续执行——结果是 ZIP 看似生成成功,解压时却报 “invalid zip file” 或只解出部分文件。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每次调用
zip.Writer.WriteHeader后必须显式检查err != nil,不能忽略 -
Header.Name应使用filepath.ToSlash(filepath.Clean(relPath))标准化,避免 Windows 路径分隔符或冗余./ - 禁止直接传入用户输入的原始文件名;若需保留原始名,先白名单校验(如只允许字母、数字、下划线、短横线、斜杠)
多个文件写入顺序影响 ZIP 内部结构,但不影响解压兼容性
ZIP 是顺序写入格式,zip.Writer 不支持随机写入或追加。你按什么顺序调用 WriteHeader + io.Copy,最终 ZIP 中的目录树和文件顺序就固定了。这点在跨平台解压(尤其是 macOS Finder 或旧版 Windows 资源管理器)时可能影响体验——比如想让 README.md 排第一,就得最先写。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 提前对文件路径切片排序:
sort.Strings(filePaths),或按业务逻辑自定义排序(如目录优先、主文件靠前) - 不要依赖操作系统
filepath.Walk的遍历顺序,它不保证跨平台一致 - 若需生成带层级的 ZIP(如
dist/v1.2.0/app.js),确保Header.Name包含完整相对路径,且父目录无需单独写 Header
内存中压缩大文件易 OOM,要用 io.Pipe 配合 goroutine 流式处理
把多个 GB 的文件全读进 []byte 再写入 zip.Writer,Go 程序很快触发 OOM。正确做法是让文件读取和 ZIP 写入并行:一边从磁盘读,一边往 ZIP 流写,中间不缓存整块内容。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
io.Pipe()创建管道,启动 goroutine 在写端调用zip.Writer,主 goroutine 在读端逐个打开文件并io.Copy - 每个文件的
os.Open和io.Copy必须成对,及时Close(),否则句柄泄漏 - 注意
zip.FileInfoHeader返回的Header中UncompressedSize64和Modified字段需手动设置(stat.Size()和stat.ModTime()),否则默认为 0,解压工具可能显示错误大小或时间
Windows 下中文文件名乱码,本质是 ZIP 传统编码缺陷
标准 ZIP 规范(APPNOTE 6.3.4)未强制指定文件名编码,老工具默认用系统本地编码(Windows 是 GBK),而 Go archive/zip 默认用 UTF-8 写入——导致 WinRAR、7-Zip(未开 UTF-8 选项)解压时显示乱码。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 若目标用户主要在 Windows 且用旧版解压工具,改用
github.com/mholt/archiver/v4等库,它支持ZipEncoding: archiver.Compatibility自动降级为 CP437 + 位标记 - 纯 Go 原生方案:手动设置
Header.Flags = 0x800(表示 UTF-8),并确保所有Header.Name是合法 UTF-8 字节序列(Go string 天然满足) - 测试环节必须在目标环境(如 Win10 + 默认资源管理器)解压验证,不能只看 Linux
unzip -l
最麻烦的不是怎么写 ZIP,而是怎么让 ZIP 被所有人正常打开——编码、路径、权限、时间戳,每个字段都可能在某个角落悄悄失效。










