Go 标准库支持 zip/gzip 但无目录压缩/解压高层封装;压缩需用 filepath.Rel 生成安全相对路径并校验路径穿越;解压须 clean 路径、join 目标目录并用 filepath.Rel 验证落点;gzip 适用于单文件压缩(如 HTTP 响应),zip 用于多文件归档;大文件解压应增大 io.Copy 缓冲区。

Go 标准库原生支持 zip 和 gzip,但不直接提供「压缩整个目录」或「自动解压嵌套子目录」的高层封装——这意味着你得自己遍历文件、处理路径、避免目录穿越漏洞,否则一不小心就会写错或被利用。
用 archive/zip 压缩目录时如何正确处理文件路径
标准库不会自动帮你把 os.FileInfo 转成 zip 内部路径;如果直接用 filepath.Abs() 或未清理的 filepath.Join(),压缩包里可能出现绝对路径(如 /home/user/project/file.txt),解压时可能覆盖系统文件。
- 始终用
filepath.Rel(rootDir, fullPath)获取相对于根目录的相对路径 - 对结果调用
strings.ReplaceAll(relPath, "\\", "/")统一为正斜杠(zip 规范要求) - 在写入
zip.FileHeader前检查strings.HasPrefix(relPath, ".."+string(filepath.Separator)),拒绝路径穿越 -
header.Name必须是相对路径且不含盘符或前导/,否则某些解压工具会报错或跳过
解压 zip 文件时如何防止目录穿越攻击
攻击者可构造恶意 zip 文件,让 header.Name 为 ../../../etc/passwd,若直接 os.Create(header.Name) 就会写到任意位置。
- 对每个
zip.FileHeader.Name,先调用filepath.Clean(),再检查是否仍以".."开头或包含".."+string(filepath.Separator) - 确保所有解压目标路径都基于你指定的
destDir,用filepath.Join(destDir, cleanName)构造最终路径 - 解压前用
os.Stat()检查目标路径是否仍在destDir内:比较filepath.Rel(destDir, absTargetPath)是否无错误且不以".."开头 - 跳过
header.FileInfo().IsDir()为true但名字不以"/"结尾的条目(非法 zip 目录标记)
gzip 和 zip 的适用场景怎么选
gzip 是单文件压缩,zip 是归档+压缩,二者不可互换使用。误用会导致程序静默失败或读取乱码。
立即学习“go语言免费学习笔记(深入)”;
- 只压缩一个文件(如日志、JSON 响应体)→ 用
gzip.Writer,速度快、开销小 - 需要打包多个文件或保留目录结构 → 必须用
archive/zip,不能用gzip替代 - HTTP 传输中设
Content-Encoding: gzip→ 只能用gzip,zip不被浏览器或多数 HTTP 客户端识别 -
gzip不支持密码、不记录文件名;zip支持但标准库不实现加密——要密码保护必须引入第三方库(如github.com/knqyf263/go-zip)
为什么 io.Copy 解压大文件时内存不涨但速度慢
看起来没内存泄漏,但实际可能是缓冲区太小导致系统调用频繁,尤其在 SSD 或网络文件系统上更明显。
-
io.Copy默认用32KB缓冲区,对大文件不够高效;显式传入bufio.NewReaderSize(f, 1(1MB)可显著提升吞吐 - 解压 zip 中的单个大文件时,别用
zip.File.Open()后直接io.Copy,先检查file.FileInfo().Size(),超阈值(如 100MB)考虑加进度回调或限速 - 注意
zip.File的Open()返回的是io.ReadCloser,务必在defer或defer func() { _ = rc.Close() }()中关闭,否则句柄泄漏 - Windows 上解压符号链接或特殊权限文件会丢失元数据——
archive/zip本身不支持保存ModeSymlink等,这是设计限制,不是 bug
路径处理和安全校验是压缩解压中最容易被跳过的环节,而线上服务一旦出问题,往往就卡在这两步——不是压缩失败,而是解压时悄悄覆盖了配置文件或触发了沙箱逃逸。










