美文网首页
golang 标准库实现zip压缩,忽略部分文件

golang 标准库实现zip压缩,忽略部分文件

作者: mudssky | 来源:发表于2022-12-12 10:47 被阅读0次

最近要实现一个zip打包的功能,前端下载文件夹的时候,没办法一次性创建多个文件,所以需要打包后下载。压缩不压缩的倒是无所谓的。

因为zip应该就是最通用的压缩格式了,是开源的算法,并且win自带zip的解压。。。

查了一下以后发现,golang无论是标准库还是开源的一些库都好难用啊。

开源的github上这个库star很多 https://github.com/mholt/archiver

但是看起来并不好用,官方给的例子不符合我的需求,我下载的时候希望能忽略一些文件夹,比如node_modules,

官方给的例子用的这个FilesFromDisk API,只能改变映射的名字,不能忽略文件夹。

标准库里面的文档也很糟糕,看了例子也不会用。例子里面没有对文件夹怎么处理的步骤

最后在网上找到现成的代码了,然后我改了一下符合我的需求。

type ZipOpt struct {
    SrcFile           string
    DestZip           string
    IgnorePatternList []string
    DestWriter        io.Writer //使用ZipBytes要传
}
func Zip(opt ZipOpt) error {
    zipfile, err := os.Create(opt.DestZip)
    if err != nil {
        return err
    }
    defer zipfile.Close()
    // 压缩后的路径映射
    baseDir := path.Dir(opt.SrcFile)
    archive := zip.NewWriter(zipfile)
    defer archive.Close()
    return ExploreDir(ExploreDirOption{
        RootPath:          opt.SrcFile,
        IgnorePatternList: opt.IgnorePatternList,
    },
        func(path string, d fs.DirEntry, err error) error {
            if err != nil {
                return err
            }
            info, err := d.Info()
            if err != nil {
                return err
            }
            header, err := zip.FileInfoHeader(info)
            if err != nil {
                return err
            }

            // 移除根路径之前的路径,这样映射后是正常的目录结构
            header.Name = strings.TrimLeft(path, baseDir)
            if info.IsDir() {
                header.Name += "/"
            } else {
                header.Method = zip.Deflate
            }
            writer, err := archive.CreateHeader(header)
            if err != nil {
                return err
            }
            if !info.IsDir() {
                file, err := os.Open(path)
                if err != nil {
                    return err
                }
                defer file.Close()
                _, err = io.Copy(writer, file)
                if err != nil {
                    return err
                }
            }
            return nil
        })
}

写了一个递归遍历路径的方法


type ExploreDirOption struct {
    RootPath          string   `yaml:"rootPath"`      // 目标根目录
    IgnorePatternList []string `yaml:"ignorePattern"` // 忽略路径,采用shell文件名模式匹配,在windows下\\会被当作路径分隔符
}

func ExploreDir(option ExploreDirOption, fn fs.WalkDirFunc) error {
    filelist, err := os.ReadDir(option.RootPath)
    if err != nil {
        return err
    }
    for _, ff := range filelist {
        currentpath := path.Join(option.RootPath, ff.Name())
        if len(option.IgnorePatternList) > 0 {
            matched, err := MatchReList(option.IgnorePatternList, currentpath)
            if err != nil {
                return err
            }
            // 匹配到的项目跳过
            if matched {
                continue
            }
        }
        err = fn(currentpath, ff, nil)
        if err != nil {
            return err
        }
        if ff.IsDir() {
            ExploreDir(ExploreDirOption{
                RootPath:          currentpath,
                IgnorePatternList: option.IgnorePatternList,
            }, func(path string, d fs.DirEntry, err error) error {
                return fn(path, d, err)
            })
        }
    }
    return nil

}

然后因为我是用gin框架,生成临时文件太麻烦了,干脆压缩的内容直接写入响应体,所以改了一下


func ZipBytes(opt ZipOpt) error {
    // 压缩后的路径映射
    baseDir := path.Dir(opt.SrcFile)
    if opt.DestWriter == nil {
        return errors.New("DestWriter should not be nil")
    }
    archive := zip.NewWriter(opt.DestWriter)
    defer archive.Close()
    return ExploreDir(ExploreDirOption{
        RootPath:          opt.SrcFile,
        IgnorePatternList: opt.IgnorePatternList,
    },
        func(path string, d fs.DirEntry, err error) error {
            if err != nil {
                return err
            }
            info, err := d.Info()
            if err != nil {
                return err
            }
            header, err := zip.FileInfoHeader(info)
            if err != nil {
                return err
            }
            header.Name = strings.TrimLeft(path, baseDir)
            if info.IsDir() {
                header.Name += "/"
            } else {
                header.Method = zip.Deflate
            }

            writer, err := archive.CreateHeader(header)
            if err != nil {
                return err
            }
            if !info.IsDir() {
                file, err := os.Open(path)
                if err != nil {
                    return err
                }
                defer file.Close()
                _, err = io.Copy(writer, file)
                if err != nil {
                    return err
                }
            }
            return nil
        })
}

下面是gin里面的代码,匹配用的正则,golang filepath 库里面的match方法功能太简陋了,还不如用正则。

    // 打包时忽略的文件
    ignorePatternList := []string{"[\\/]node_modules[\\/]|[\\/]node_modules$"}
    if req.IsFolder {
        err := f.checkDirBeforeCompress(req.Path, DownloadDirCheckOption{
            maxCount:          1000,
            maxSize:           4 << 30, //4gb
            ignorePatternList: ignorePatternList,
        })
        if err != nil {
            response.FailWithMessage(err.Error(), c)
            return
        }
        c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip", url.QueryEscape(req.Name)))
        err = util.ZipBytes(util.ZipOpt{
            SrcFile:           req.Path,
            IgnorePatternList: ignorePatternList,
            DestWriter:        c.Writer,
        })
        if err != nil {
            response.FailWithMessage(err.Error(), c)
            return
        }
    }

还有设置压缩,官网也是给了一个例子

package main

import (
    "archive/zip"
    "bytes"
    "compress/flate"
    "io"
)

func main() {
    // Override the default Deflate compressor with a higher compression level.

    // Create a buffer to write our archive to.
    buf := new(bytes.Buffer)

    // Create a new zip archive.
    w := zip.NewWriter(buf)

    // Register a custom Deflate compressor.
    w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
        return flate.NewWriter(out, flate.BestCompression)
    })

    // Proceed to add files to w.
}

最后我觉得如果真的有压缩文件的需求,还是直接用7z命令行吧。我这边只是为了凑合用,7z命令行也有问题就是要额外打包,会占用一定体积。

相关文章

  • golang压缩和解压缩zip文件

    golang压缩和解压缩zip文件 一个简单例子说明如何使用golang提供的archive/zip包实现zip文...

  • python zipFile库的简单使用

    由于工作需要,我们经常需要压缩,解压缩zip文件。在python操作zip文件时,我们可以使用zipfile库。 ...

  • Linux基础04

    Linux压缩命令 .zip格式压缩 实例:压缩文件 zip 压缩文件名 原文件 实例:压缩文件夹 zip -r ...

  • Linux文件操作

    Linux命令格式 zip格式的压缩 zip 压缩文件名 源文件 压缩文件zip -r 压缩文件名 源文件压缩文...

  • golang文件的压缩与解压

    判断是否是zip文件 解压缩zip文件 压缩成zip文件

  • rar vs zip优缺点对比

    zip格式的优点 zip的第一优点:普及率。 比如说,大部分在 internet 的压缩文件都是 zip 压缩文件...

  • 文件处理-Linux

    1 .zip 文件 zip压缩文件 .zip文件解压缩 2 .gz 文件 gzip, gunzip, zcat -...

  • linux常用命令

    压缩与解压文件 zip zip -r ./.zip ./*压缩当前文件夹下所有文件 -r代表递归压缩,...

  • day14 -文件压缩

    《 文件压缩 》zip压缩 格式 压缩工具 .zip ...

  • 18-zip压缩

    zipfile zip文件操作 zip文件格式是通用的文档压缩标准,在ziplib模块中,使用ZipFile类来操...

网友评论

      本文标题:golang 标准库实现zip压缩,忽略部分文件

      本文链接:https://www.haomeiwen.com/subject/sinmnktx.html