Golang 把文件夹打包成 zip

最近在开发 QQ 机器人和它的管理面板,成员在群里提交的截图按群存在一个文件夹里,管理员要能从面板上把整个文件夹打包下载。

Go 标准库的 archive/zip 只管往压缩包里写单个文件,遍历目录、保持目录结构要自己做。

func Zip(src_dir string, zip_file_name string) error {

	dir, err := ioutil.ReadDir(src_dir)
	if err != nil {
		logger.Log.Error("ioutil.ReadDir err:" + err.Error())
		return err
	}
	if len(dir) == 0 {
		logger.Log.Info(src_dir + " is empty dir!")
		return nil
	}
	// 预防:旧文件无法覆盖
	os.RemoveAll(zip_file_name)

	// 创建:zip文件
	zipfile, _ := os.Create(zip_file_name)
	defer zipfile.Close()

	// 打开:zip文件
	archive := zip.NewWriter(zipfile)
	defer archive.Close()

	// 遍历路径信息
	filepath.Walk(src_dir, func(path string, info os.FileInfo, _ error) error {

		// 如果是源路径,提前进行下一个遍历
		if path == src_dir {
			return nil
		}

		// 获取:文件头信息
		header, _ := zip.FileInfoHeader(info)

		header.Name = strings.TrimPrefix(path, src_dir+`\`)

		// 判断:文件是不是文件夹
		if info.IsDir() {
			header.Name += `/`
		} else {
			// 设置:zip的文件压缩算法
			header.Method = zip.Deflate
		}

		// 创建:压缩包头部信息
		writer, _ := archive.CreateHeader(header)
		if !info.IsDir() {
			file, _ := os.Open(path)
			defer file.Close()
			io.Copy(writer, file)
		}

		return nil
	})
	return nil
}

开头先 ReadDir 读一遍源目录,读不到直接返回错误,读到了但是空的就不打包。然后把上次打的包删掉,os.Create 建出 zip 文件,zip.NewWriter 包一层拿到写入器。两个都 defer Close()archive.Close() 会写 zip 的中央目录,漏了这一步压缩包是坏的。

主要的工作在 filepath.Walk 的回调里,每个文件和子目录都会进来一次。根目录本身跳过。zip.FileInfoHeader(info) 从文件信息生成 zip 的文件头,但它的 Name 默认只是文件名本身,不带路径,子目录里的文件会全平铺在压缩包根上,所以要用 TrimPrefix 把它改成相对源目录的路径。是目录的话文件名末尾加 /,zip 用这个区分目录项;是文件的话 header.Method = zip.Deflate 指定压缩算法,不设的话默认是 Store,只存不压。最后 archive.CreateHeader(header) 在压缩包里创建这一项,文件就打开原文件 io.Copy 进去。

需要注意的是:传入的路径最好用 filepath.Join 拼出来,直接拼字符串可能会造成打包错误。TrimPrefix 去的前缀是 src_dir 加一个反斜杠,路径分隔符不一致时前缀去不干净,压缩包里就会多出一层奇怪的目录。zip_file_name 需要带上扩展名,函数里不会自动加 .zip

头图/Cover

Photo by Rhys Abel on Pexels

评论

评论加载中…

输入关键词开始搜索。

选择Enter 打开Esc 关闭CtrlK 唤起