使用 archive/zip 递归压缩目录前,须先用 os.Stat 校验路径合法性并转为绝对路径,遍历时用 filepath.WalkDir 区分符号链接与真实目录,Header.Name 统一转为正斜杠分隔的相对路径且目录末尾加 /,显式写入目录项并设 Method=Store,确保调用 zip.Writer.Close() 并检查其错误。

用 archive/zip 递归压缩目录前,先确认路径和文件权限是否合法
Go 的 archive/zip 不支持直接压缩目录,必须手动遍历文件树。如果传入的路径不存在、是符号链接未解引用、或某层子目录无读取权限,filepath.WalkDir 会静默跳过或提前返回错误——但你可能没检查 error,结果 zip 包空空如也。
- 务必用
os.Stat先校验源路径是否为目录且可读 - 避免直接传
./或相对路径给生产代码;统一用filepath.Abs转成绝对路径,防止 zip 包内路径混乱(比如出现../../etc/passwd) - 遍历时用
filepath.WalkDir(Go 1.16+),它比Walk更可靠,能区分 symlink 和真实目录
写入 zip 文件时,每个 zip.FileHeader 的 Name 必须是正斜杠分隔的相对路径
Windows 下路径是 分隔,但 zip 格式只认 /。如果直接把 filepath.Join("dir", "file.txt") 的结果塞进 Header.Name,在 Linux/macOS 上可能正常,在 Windows 上解压会出错(比如创建嵌套空文件夹)。
- 对每个文件路径,用
strings.ReplaceAll(filepath.ToSlash(relPath), "\", "/")强制转为正斜杠 - 目录项也要显式写入:当
fi.IsDir()为true时,Header.Name末尾必须加/,且Header.Method = zip.Store(不能用 Deflate 压缩空目录) -
Header.ModTime建议设为fi.ModTime(),否则所有文件时间戳会变成打包时刻,影响增量比对
别忽略 zip.Writer.Close() —— 它不只刷新缓冲区,还写入中央目录
很多示例代码在 defer w.Close() 后就结束,但若中途 panic 或忘记 Close(),生成的 zip 文件能打开、能看到文件名,却打不开任何内容——因为中央目录没写入,解压工具无法定位数据偏移。
- 必须确保
w.Close()被调用,且检查其返回的error;它可能因磁盘满、权限不足等失败 - 不要在循环中反复
w.CreateHeader()后不写内容,这会产生损坏的条目;哪怕空文件也要调用io.Copy(w, f)或至少w.Write(nil) - 如果要压缩大目录,考虑用
bufio.NewWriter包裹文件句柄再传给zip.NewWriter,减少系统调用次数
递归压缩时,软链接和特殊文件(设备、socket)默认被跳过,但需主动处理硬链接
filepath.WalkDir 默认不会展开 symlink,也不会报错;硬链接则会被当作两个独立文件重复写入,导致 zip 包体积膨胀甚至解压失败(同一 inode 多次写入)。
立即学习“go语言免费学习笔记(深入)”;
- 用
fi.Sys().(*syscall.Stat_t).Nlink(Unix)或fi.Sys().(*syscall.Win32FileAttributeData)(Windows)判断硬链接数,>1 时跳过后续出现的同 inode 文件 - 若需保留 symlink,得自己读取目标路径并写入
Header.Flags |= 0x1(ZIP 的“系统”标志位),但多数解压工具不识别,实用性低 - 遇到
os.ModeDevice | os.ModeSocket | os.ModeNamedPipe,直接跳过,archive/zip本身不支持这些类型
最易被忽略的是:zip 包里没有“根目录”概念,Header.Name 是什么,解压出来就是什么路径。如果你从 /home/user/project 开始压缩,又没做路径裁剪,用户解压后会得到完整绝对路径结构——这通常不是你想要的。










