Go语言处理zip压缩还是比较方便的,可以直接使用Go标准库archive/zip。下面记录下具体使用方法,以及注意事项。
Go 处理zip解压
一般zip文件是来自于磁盘或者网络,不管处理磁盘还是网络中的zip文件首先都是读取文件数据
// 读取磁盘文件
func getFromDisk(filePath string) ([]byte, error) {
return os.ReadFile(filePath)
}
// 读取网络文件
func getZipFromNet(zipURL string) ([]byte, error) {
rsp, err := http.Get(zipURL)
if err != nil {
return nil, err
}
rspBody, err := io.ReadAll(rsp.Body)
if err != nil {
return nil, err
}
defer rsp.Body.Close()
return rspBody, nil
}
拿到数据后在使用标准库解压就好,需要注意的是要记得校验目录是否存在,如果不存在要先创建不然下面Open打开文件的时候会报错。
// @zipData 压缩数据
// @destDir 要解压的文件夹
func unzip(zipData []byte, destDir string) error {
zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
if err != nil {
return err
}
for _, f := range zipReader.File {
err := writeUnzipFile(f, destDir)
if err != nil {
fmt.Println(err)
return err
}
}
return nil
}
// isFileExist 文件或目录是否存在
func isFileExist(filePath string) bool {
_, err := os.Stat(filePath)
if err != nil {
if os.IsExist(err) {
return true
}
return false
}
return true
}
func writeUnzipFile(f *zip.File, destDir string) error {
fName := f.Name
destPath := filepath.Join(destDir, fName)
// 判断文件夹是否存在,主要是处理zip包含多层文件目录的情况
if f.FileInfo().IsDir() && !isFileExist(destPath) {
err := os.MkdirAll(destPath, os.ModePerm)
return err
}
// 创建要写入的文件
fw, err := os.Open(destPath)
if err != nil {
return err
}
defer fw.Close()
fr, err := f.Open()
if err != nil {
return err
}
defer fr.Close()
_, err = io.Copy(fw, fr)
return err
}
压缩文件
压缩文件就要用到zip writer了,这里用到filepath.Walk遍历目录下所有文件读取并写入到zip writer里,需要注意的是filepath.Walk方法会遍历目录自身,处理的时候要跳过。
// @toZipFilePath 要压缩的文件所在目录 绝对路径
// @destDir 生成压缩文件的目录
// @fileName 生成的压缩文件名称 如xxx.zip
func zipData(toZipFilePath string, destDir string, fileName string) error {
if !isFileExist(destDir) {
err := os.MkdirAll(destDir, os.ModePerm)
return err
}
// 创建新的压缩文件
archive, err := os.Create(destDir + "/" + fileName)
if err != nil {
return err
}
zipWriter := zip.NewWriter(archive)
defer zipWriter.Close()
err = filepath.Walk(toZipFilePath, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
fmt.Println("walk", path)
// 跳过目录自身
if path == toZipFilePath {
return nil
}
// 获取zip包中的相对路径 比如要压缩的目录是/tmp/tozip
// 要压缩的文件是/tmp/tozip/tozip.file
// 则得到的zipPath = tozip.file
// 保证压缩后文件目录结构和之前是一样的
// 如果需要使用新的目录,可以根据需要自定义
zipPath := path[len(toZipFilePath)+1:]
if info.IsDir() {
zipPath += "/"
}
w, err := zipWriter.Create(zipPath)
if err != nil {
return err
}
if info.IsDir() {
return nil
}
fr, err := os.Open(path)
defer fr.Close()
if err != nil {
return err
}
_, err = io.Copy(w, fr)
if err != nil {
return err
}
return nil
})
// 在这里读取新的zip文件可能会出问题
// 除非吧上面的defer zipWriter.Close()去掉,然后在这里先执行zipWriter.Close()在读取
return err
}
处理压缩的时候在zipWriter close之前读取文件读到的数据可能会有问题,因为缓冲区的数据不一定写入完成。在这里被坑过一次,想要读取压缩后的文件重新上传到文件服务结果缓冲区没有flush导致读取的数据有问题。
在内存中将zip文件解压修改后重新压缩
直接将zip reader中解压后的文件重新写入zip writer也是可以的,如果需要修改zip包中的某些数据并重新压缩可以直接在内存中完成,不用先解压至磁盘,再从磁盘读取数据压缩。需要注意的是获取压缩数据前zipWriter要先close保证缓冲区数据都已写入。
func reZip(zipData []byte) ([]byte, error) {
zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
if err != nil {
fmt.Println(err)
return nil, err
}
buf := []byte{}
writer := bytes.NewBuffer(buf)
zipWriter := zip.NewWriter(writer)
for _, f := range zipReader.File {
w, err := zipWriter.Create(f.Name)
if err != nil {
fmt.Println(err)
return nil, err
}
fr, err := f.Open()
if err != nil {
fmt.Println(err)
return nil, err
}
_, err = io.Copy(w, fr)
if err != nil {
fmt.Println(err)
return nil, err
}
}
zipWriter.Close()
return writer.Bytes(), nil
}