go标准库足够覆盖95%压缩归档需求:archive/zip用于多文件打包压缩,compress/gzip仅单文件流式压缩;需防范zip slip路径穿越、务必调用gzip.writer.close()、大文件须流式处理避免oom。

Go 标准库就足够用了,别急着引入第三方——archive/zip 和 compress/gzip 覆盖 95% 场景,关键在用法是否踩坑。
archive/zip vs compress/gzip:归档和压缩不是一回事
很多人混淆 ZIP 和 GZIP 的职责:archive/zip 是打包+压缩一体化(支持多文件、目录结构、Deflate 压缩);compress/gzip 只做单文件流式压缩,不打包、不处理路径。选错就等于写一半逻辑还得补。
- 要打包
config.yaml+images/icon.png→ 必须用archive/zip - 只压缩一个 2GB 日志文件为
app.log.gz→ 用compress/gzip更轻量、更可控 - 想发 tar.gz 包?得先用
archive/tar打包成流,再套gzip.Writer——顺序不能反,否则解压失败
路径安全是 zip 压缩/解压里最常漏掉的一环
不校验路径,zip.FileHeader.Name 里出现 ../../../etc/passwd 就是 Zip Slip 漏洞,解压时直接覆盖系统文件。
- 压缩前:用
filepath.Rel(rootDir, fullPath)得到相对路径,再strings.ReplaceAll(relPath, "\", "/")统一斜杠 - 解压时:对每个
f.Header.Name先filepath.Clean(),再检查strings.HasPrefix(cleanName, "..") || strings.Contains(cleanName, "../") - 最终目标路径必须用
filepath.Join(destDir, cleanName)构造,且用filepath.Rel(destDir, absDst)验证是否仍在目标目录内
gzip.Writer.Close() 不调用 = 生成损坏文件
gzip.Writer 内部有缓冲,CRC 和 ISIZE 尾部数据只在 Close() 时写入。漏掉这一步,gunzip -t 会报 unexpected end of file。
- 别只写
defer gzWriter.Close()就完事——如果函数中途return或 panic,defer还没执行,文件就废了 - 稳妥做法:在
io.Copy后立刻if err := gzWriter.Close(); err != nil { return err } - 压缩级别可选:
gzip.NoCompression(快)、gzip.BestSpeed(日志友好)、gzip.DefaultCompression(通用)
大文件处理必须流式,否则 OOM
archive/zip 默认把整个 ZIP 加载进内存,几百 MB 文件就可能触发 OOM;compress/gzip 虽是流式,但默认缓冲区小(bufio 默认 4KB),大文件解压慢。
- 压缩 ZIP:用
io.Copy直接从os.File到zip.Writer,禁用bytes.Buffer中转 - 解压 ZIP:遍历
zip.File时,对每个f.Open()返回的io.ReadCloser,用io.Copy流式写入目标文件 - gzip 解压可显式加大缓冲:
buf := make([]byte, 1(1MB),传给 <code>io.CopyBuffer
真正容易被忽略的是:ZIP 规范强制要求路径用 / 分隔,Windows 上的 会导致 macOS 归档工具打不开;而 compress/gzip 对文件名、时间戳等元数据支持弱,需要额外封装才能保留这些信息。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











