压缩ZIP

通过该方法你可以将目录或文件压缩到可写IO中,例如文件、内存......

// Zip 压缩文件或目录
// @params dst io.Writer 压缩文件可写流
// @params src string    待压缩源文件/目录路径
func Zip(dst io.Writer, src string) error {
	// 强转一下路径
	src = filepath.Clean(src)
	// 提取最后一个文件或目录的名称
	baseFile := filepath.Base(src)
	// 判断src是否存在
	_, err := os.Stat(src)
	if err != nil {
		return err
	}

	// 通文件流句柄创建一个ZIP压缩包
	zw := zip.NewWriter(dst)
	// 延迟关闭这个压缩包
	defer zw.Close()

	// 通过filepath封装的Walk来递归处理源路径到压缩文件中
	return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {
		// 是否存在异常
		if err != nil {
			return err
		}

		// 通过原始文件头信息,创建zip文件头信息
		zfh, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}

        // 赋值默认的压缩方法,否则不压缩
        zfh.Method = zip.Deflate

		// 移除绝对路径
		tmpPath := path
		index := strings.Index(tmpPath, baseFile)
		if index > -1 {
			tmpPath = tmpPath[index:]
		}
		// 替换文件名,并且去除前后 "\" 或 "/"
		tmpPath = strings.Trim(tmpPath, string(filepath.Separator))
		// 替换一下分隔符,zip不支持 "\\"
		zfh.Name = strings.ReplaceAll(tmpPath, "\\", "/")
		// 目录需要拼上一个 "/" ,否则会出现一个和目录一样的文件在压缩包中
		if info.IsDir() {
			zfh.Name += "/"
		}

		// 写入文件头信息,并返回一个ZIP文件写入句柄
		zfw, err := zw.CreateHeader(zfh)
		if err != nil {
			return err
		}

		// 仅在他是标准文件时进行文件内容写入
		if zfh.Mode().IsRegular() {
			// 打开要压缩的文件
			sfr, err := os.Open(path)
			if err != nil {
				return err
			}
			defer sfr.Close()

			// 将srcFileReader拷贝到zipFilWrite中
			_, err = io.Copy(zfw, sfr)
			if err != nil {
				return err
			}
		}

		// 搞定
		return nil
	})
}

解压ZIP

通过该方法你可以将ZIP压缩流解压到指定目录

// Unzip 解压压缩文件
// @params dst string      解压后的目标路径
// @params src *zip.Reader 压缩文件可读流
func Unzip(dst string, src *zip.Reader) error {
	// 强制转换一遍目录
	dst = filepath.Clean(dst)

	// 遍历压缩文件
	for _, file := range src.File {
		// 在闭包中完成以下操作可以及时释放文件句柄
		err := func() error {
			// 跳过文件夹
			if file.Mode().IsDir() {
				return nil
			}

			// 配置输出目标路径
			filename := filepath.Join(dst, file.Name)
			// 创建目标路径所在文件夹
			e := os.MkdirAll(filepath.Dir(filename), os.ModeDir)
			if e != nil {
				return e
			}

			// 打开这个压缩文件
			zfr, e := file.Open()
			if e != nil {
				return e
			}
			defer zfr.Close()

			// 创建目标文件
			fw, e := os.Create(filename)
			if e != nil {
				return e
			}
			defer fw.Close()

			// 执行拷贝
			_, e = io.Copy(fw, zfr)
			if e != nil {
				return e
			}

			// 拷贝成功
			return nil
		}()

		// 是否发生异常
		if err != nil {
			return err
		}
	}

	// 解压完成
	return nil
}

对压缩二次封装

// ZipToFile 压缩至文件
// @params dst string 压缩文件目标路径
// @params src string 待压缩源文件/目录路径
// @return     error  错误信息
func ZipToFile(dst, src string) error {
	// 创建一个ZIP文件
	fw, err := os.Create(filepath.Clean(dst))
	if err != nil {
		return err
	}
	defer fw.Close()

	// 执行压缩
	return Zip(fw, src)
}

// ZipToBytes 压缩至字节流
// @params src string 待压缩源文件/目录路径
// @return []byte 压缩字节流
// @return error  错误信息
func ZipToBytes(src string) ([]byte, error) {
	// 创建一个缓冲区
	buf := bytes.NewBuffer(nil)

	// 执行压缩
	err := Zip(buf, src)
	if err != nil {
		return nil, err
	}

	// 返回缓冲区的二进制流
	return buf.Bytes(), nil
}

对解压二次封装

// UnzipFromFile 解压压缩文件
// @params dst string 解压后目标路径
// @params src string 压缩文件目标路径
func UnzipFromFile(dst, src string) error {
	// 打开压缩文件
	zr, err := zip.OpenReader(filepath.Clean(src))
	if err != nil {
		return err
	}
	defer zr.Close()

	// 解压
	return Unzip(dst, &zr.Reader)
}

// UnzipFromBytes 解压压缩字节流
// @params dst string 解压后目标路径
// @params src []byte 压缩字节流
func UnzipFromBytes(dst string, src []byte) error {
	// 通过字节流创建zip的Reader对象
	zr, err := zip.NewReader(bytes.NewReader(src), int64(len(src)))
	if err != nil {
		return err
	}

	// 解压
	return Unzip(dst, zr)
}

测试一下

func main() {
	// 压缩目录到指定路径
	err := ZipToFile("../build.zip", "../build")
	if err != nil {
		log.Fatalln(err.Error())
	}

	// 解压文件
	err = UnzipFromFile("../build_dst_1", "../build.zip")
	if err != nil {
		log.Fatalln(err.Error())
	}

	// 压缩目录到字节流
	buf, err := ZipToBytes("../build")
	if err != nil {
		log.Fatalln(err.Error())
	}

	// 解压字节流
	err = UnzipFromBytes("../build_dst_2", buf)
	if err != nil {
		log.Fatalln(err.Error())
	}
}