在Go语言的标准库中,archive/zip包提供了对zip格式文件的完整读写支持,开发者可以直接基于该包实现文件压缩和解压功能,无需引入第三方依赖,既降低了项目依赖复杂度,也能保证功能的稳定性。无论是日常的文件批量处理工具,还是服务端的上传文件解压需求,都可以通过该包快速实现。
环境准备
本文的示例基于Go 1.18及以上版本,无需额外安装第三方包,直接使用标准库即可。需要引入的包如下:
import (
"archive/zip"
"io"
"os"
"path/filepath"
"strings"
)
实现文件压缩功能
压缩单个文件
压缩单个文件的核心逻辑是创建zip文件写入器,将目标文件的内容读取后写入zip条目中。具体实现代码如下:
// CompressSingleFile 压缩单个文件到目标zip路径
func CompressSingleFile(srcFile string, destZip string) error {
// 创建目标zip文件
zipFile, err := os.Create(destZip)
if err != nil {
return err
}
defer zipFile.Close()
// 创建zip写入器
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// 打开源文件
src, err := os.Open(srcFile)
if err != nil {
return err
}
defer src.Close()
// 获取文件信息,用于设置zip条目的属性
fileInfo, err := src.Stat()
if err != nil {
return err
}
// 创建zip条目
header, err := zip.FileInfoHeader(fileInfo)
if err != nil {
return err
}
// 设置压缩方法为默认压缩
header.Method = zip.Deflate
// 条目名称使用文件名,避免路径问题
header.Name = filepath.Base(srcFile)
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
// 将源文件内容拷贝到zip条目中
_, err = io.Copy(writer, src)
return err
}
压缩整个目录
压缩目录需要遍历目录下的所有文件和子目录,逐个将文件添加到zip中,同时需要保持原有的目录结构。实现代码如下:
// CompressDir 压缩整个目录到目标zip路径
func CompressDir(srcDir string, destZip string) error {
zipFile, err := os.Create(destZip)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := zip.NewWriter(zipFile)
defer zipWriter.Close()
// 获取源目录的绝对路径,用于处理相对路径
absSrcDir, err := filepath.Abs(srcDir)
if err != nil {
return err
}
// 遍历目录
err = filepath.Walk(absSrcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// 跳过目录本身,只处理文件
if info.IsDir() {
return nil
}
// 计算zip条目中的相对路径
relPath, err := filepath.Rel(absSrcDir, path)
if err != nil {
return err
}
// 替换路径分隔符为zip标准的正斜杠
relPath = strings.ReplaceAll(relPath, string(os.PathSeparator), "/")
// 创建zip条目
header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}
header.Name = relPath
header.Method = zip.Deflate
writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}
// 打开当前文件并拷贝内容
src, err := os.Open(path)
if err != nil {
return err
}
defer src.Close()
_, err = io.Copy(writer, src)
return err
})
return err
}
实现文件解压功能
解压zip文件的核心是读取zip文件中的条目,逐个将条目内容写入到目标目录中,同时需要创建对应的目录结构。实现代码如下:
// DecompressZip 解压zip文件到目标目录
func DecompressZip(zipPath string, destDir string) error {
// 打开zip文件
zipReader, err := zip.OpenReader(zipPath)
if err != nil {
return err
}
defer zipReader.Close()
// 确保目标目录存在
err = os.MkdirAll(destDir, 0755)
if err != nil {
return err
}
// 遍历zip中的所有条目
for _, file := range zipReader.File {
// 构建目标路径
destPath := filepath.Join(destDir, file.Name)
// 防止路径穿越攻击,检查目标路径是否在destDir下
if !strings.HasPrefix(destPath, filepath.Clean(destDir)+string(os.PathSeparator)) {
return errors.New("非法的zip条目路径")
}
// 如果是目录,创建目录
if file.FileInfo().IsDir() {
err = os.MkdirAll(destPath, file.Mode())
if err != nil {
return err
}
continue
}
// 创建文件所在的目录
err = os.MkdirAll(filepath.Dir(destPath), 0755)
if err != nil {
return err
}
// 打开zip条目
srcFile, err := file.Open()
if err != nil {
return err
}
// 创建目标文件
destFile, err := os.OpenFile(destPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
srcFile.Close()
return err
}
// 拷贝内容
_, err = io.Copy(destFile, srcFile)
destFile.Close()
srcFile.Close()
if err != nil {
return err
}
}
return nil
}
功能测试
可以编写简单的测试代码验证上述功能是否正确:
func main() {
// 测试压缩单个文件
err := CompressSingleFile("./test.txt", "./single.zip")
if err != nil {
panic(err)
}
// 测试压缩目录
err = CompressDir("./test_dir", "./dir.zip")
if err != nil {
panic(err)
}
// 测试解压文件
err = DecompressZip("./dir.zip", "./unzip_dir")
if err != nil {
panic(err)
}
}
注意事项
- 处理中文文件名时,zip包默认使用UTF-8编码,若遇到其他编码的zip文件可能需要额外处理编码转换。
- 解压时需要校验条目路径,避免路径穿越导致文件被写入到预期之外的目录,引发安全问题。
- 压缩大文件时建议添加进度处理逻辑,避免用户等待时无反馈。
- 操作完成后及时关闭文件句柄,避免资源泄露。