archive/zip 是压缩多文件 zip 包的首选,需用 createheader 设置相对路径 header.name 并启用 zip.deflate;解压时须 filepath.clean 校验路径防 zip slip;大文件必须流式 io.copy 处理,禁用内存缓存。

用 archive/zip 压缩多个文件到 ZIP 包
Go 标准库的 archive/zip 是最常用、最稳妥的选择,无需第三方依赖。关键在于正确构造 zip.FileHeader 并处理文件路径,否则解压后目录结构错乱或文件名带冗余前缀。
常见错误是直接用 os.Open 读取文件后调用 zipWriter.Create,结果只生成空文件;必须用 zipWriter.CreateHeader 配合手动写入内容。
- 压缩时,
header.Name应为相对路径(如"images/logo.png"),避免绝对路径(如"/home/user/logo.png")导致解压失败或安全风险 - 记得设置
header.Method = zip.Deflate启用压缩(默认是 Store,即无压缩) - 对每个待压缩文件,先
os.Stat获取信息,再创建zip.FileHeader,最后用io.Copy写入内容
解压 ZIP 文件并还原目录结构
解压看似简单,但容易因路径校验缺失引发“Zip Slip”漏洞——攻击者构造恶意路径(如 "../../../etc/passwd")覆盖系统文件。
标准做法是:对每个 zip.File 的 FileHeader.Name 调用 filepath.Clean,再检查是否仍以 "." 开头或包含 "..",否则跳过该文件。
立即学习“go语言免费学习笔记(深入)”;
- 用
zip.OpenReader打开 ZIP,遍历Reader.File列表 - 对每个
f,执行dstPath := filepath.Join(outputDir, filepath.Clean(f.Name)) - 若
dstPath不是以outputDir为前缀(用strings.HasPrefix(filepath.ToSlash(dstPath), filepath.ToSlash(outputDir))判断),则拒绝解压 - 创建父目录用
os.MkdirAll(filepath.Dir(dstPath), 0755),再写入文件内容
处理大文件时避免内存溢出
archive/zip 默认把整个 ZIP 加载进内存,压缩或解压几百 MB 文件时极易 OOM。必须流式处理。
压缩大文件:用 os.Create 创建目标文件,再传给 zip.NewWriter,逐个 CreateHeader + io.Copy,不缓存原始文件内容。
解压大文件:同样用 io.Copy 直接从 zip.File.Open() 流向磁盘文件句柄,不读到 []byte。
- 禁用
bytes.Buffer或io.ReadAll处理 ZIP 内容 - 压缩时,用
f, _ := os.Open(srcPath); defer f.Close(),然后io.Copy(writer, f) - 解压时,
rc, _ := f.Open(); defer rc.Close(),再io.Copy(dstFile, rc) - 必要时加进度回调:在
io.Copy外层包装自定义io.Reader统计已读字节数
gzip 单文件压缩与解压更轻量
如果只需压缩单个文件(比如日志归档),compress/gzip 比 ZIP 更快、更省内存,且 Go 原生支持流式处理。
注意:gzip 不打包目录,只压缩一个数据流;文件名、时间戳等元信息需额外保存(如用 gzip.Header 设置 Name 字段)。
- 压缩:打开源文件 →
gzip.NewWriter包裹输出文件 →io.Copy→ 调用w.Close()触发 footer 写入 - 解压:用
gzip.NewReader包裹输入文件 →io.Copy到目标文件 → 关闭 reader(可选) -
gzip.Header.Name可设为原始文件名,但解压端不一定读取;不要依赖它还原文件名 - 若需保留修改时间,需手动读取
os.Stat的ModTime(),赋值给gzip.Header.ModTime
archive/zip 并严格校验路径;仅单文件高频压缩 → compress/gzip 更直接。两者都务必流式处理,否则一碰大文件就崩。










