go语言打包tar存档

发布时间 2023-07-21 16:47:30作者: 厚礼蝎

功能介绍

  • 可以多文件多文件夹混合打包
  • 可以设置文件和文件夹黑白名单
  • 可以打包成文件也可以打包成io.Reader类型数据

缺陷:

还是tar的特色,无法支持中文,所以中文打包会出现乱码,如果路径有中文,也无法打包文件,所以打包的文件或者文件夹尽量不要有中文
(有解决方法还请留言不吝指教,感谢感谢)

代码部分

// 打包目录下的所有文件和子文件,但不包括当前目录
func tarFolderToReader(
	tarWriter *tar.Writer,
	folderPath string,
	rootDir bool,
	fileWhitelist,
	dirWhitelist,
	fileBlacklist,
	dirBlacklist []string,
) error {
	err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if path == folderPath {
			// 跳过根目录
			return nil
		}
		for _, f1 := range fileWhitelist {
			for _, f2 := range fileBlacklist {
				if filepath.Clean(f1) == filepath.Clean(f2) {
					return errors.New("文件的黑白名单冲突异常")
				}
			}
		}
		for _, d1 := range dirWhitelist {
			for _, d2 := range dirBlacklist {
				if filepath.Clean(d1) == filepath.Clean(d2) {
					return errors.New("文件夹的黑白名单冲突异常")
				}
			}
		}
		// 如果文件白名单不为空,且是个文件
		if len(fileWhitelist) != 0 && info.Mode().IsRegular() {
			flagfile := false
			//遍历白名单
			for _, filew := range fileWhitelist {
				// 如果匹配到白名单,就直接打包
				if filepath.Clean(filew) == filepath.Clean(path) {
					flagfile = true
					goto end
				} else if len(dirWhitelist) != 0 {
					//如果没匹配到白名单,就看下所在的文件夹是不是被允许的
					for _, dirw := range dirWhitelist {
						if strings.HasPrefix(filepath.Clean(path), filepath.Clean(dirw)) {
							//如果是文件夹白名单中的文件,就需要判断在不在文件黑名单中
							if len(fileBlacklist) != 0 {
								for _, filew := range fileBlacklist {
									if filepath.Clean(filew) == filepath.Clean(path) {
										goto end
									}
								}
							}
							if len(dirBlacklist) != 0 {
								for _, dirw := range dirBlacklist {
									if strings.HasPrefix(filepath.Clean(path), filepath.Clean(dirw)) {
										goto end
									}
								}
							}
							flagfile = true
							goto end
						}
					}

				} else if len(fileBlacklist) != 0 {
					return errors.New("文件的黑白名单冲突异常")
				}
			}
		end:
			if !flagfile {
				return nil
			}
		} else if len(fileBlacklist) != 0 && info.Mode().IsRegular() {
			flagfile := false
			for _, filew := range fileBlacklist {
				if filepath.Clean(filew) == filepath.Clean(path) {
					flagfile = true
					break
				}
			}
			if len(dirBlacklist) != 0 {
				for _, dirw := range dirBlacklist {
					if strings.HasPrefix(filepath.Clean(path), filepath.Clean(dirw)) {
						flagfile = true
						break
					}
				}
			}
			if flagfile {
				return nil
			}
		}
		// 如果文件夹白名单存在,且是个文件夹
		if len(dirWhitelist) != 0 && info.Mode().IsDir() {
			flagdir := false
			for _, dirw := range dirWhitelist {
				// 判断是不是在白名单中
				if filepath.Clean(dirw) == filepath.Clean(path) {
					//是在白名单,则直接跳出通过
					flagdir = true
					goto end1
				} else if strings.HasPrefix(filepath.Clean(path), filepath.Clean(dirw)) {
					if len(dirBlacklist) != 0 {
						for _, dirw := range dirBlacklist {
							if filepath.Clean(dirw) == filepath.Clean(path) {
								goto end1
							}
						}
					}
					flagdir = true
					goto end1
				}
			}
		end1:
			if !flagdir {
				return nil
			}
		} else if len(dirBlacklist) != 0 && info.Mode().IsDir() {
			flagdir := false
			for _, dirw := range dirBlacklist {
				if filepath.Clean(dirw) == filepath.Clean(path) {
					flagdir = true
					break
				}
			}
			if flagdir {
				return nil
			}
		}
		header, err := tar.FileInfoHeader(info, "")
		if err != nil {
			return err
		}
		if rootDir {
			header.Name = filepath.FromSlash(path)
		} else {
			// 使用相对路径而不是绝对路径
			relPath, err := filepath.Rel(folderPath, path)
			if err != nil {
				return err
			}
			header.Name = filepath.FromSlash(relPath)
		}

		// 对于Windows,在标头中将反斜杠转换为正斜杠。名称字段
		if os.PathSeparator == '\\' {
			header.Name = filepath.ToSlash(header.Name)
		}

		if info.IsDir() {
			header.Name += "/"
		}
		header.Format = tar.FormatGNU
		if err := tarWriter.WriteHeader(header); err != nil {
			return err
		}

		if !info.Mode().IsRegular() {
			return nil
		}
		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		_, err = io.Copy(tarWriter, file)
		return err
	})

	if err != nil {
		return err
	}

	return nil
}

上面的代码是功能部分,所依赖的库都是go的标准库

下面是使用方法

打包成文件的方式

func TestTarFiles(t *testing.T) {
	// 定义输出的tar包的文件名
	tarFileName := "a.tar"

	// 创建输出文件
	outputFile, err := os.Create(tarFileName)
	if err != nil {
		t.Fatal("无法创建输出文件:", err)
	}
	defer outputFile.Close()
	tarWriter := tar.NewWriter(outputFile)
	// 文件白名单
	fileWhitelist := []string{
		"Dockerfile",
		"a.html",
		"test/a.html",
	}

	// 文件夹白名单
	dirWhitelist := []string{
		"Dockerfile",
		"a.html",
		"test/a.html",
	}
	err = tarFolderToReader(
        tarWriter,
		".",
		false,
		fileWhitelist,
		dirWhitelist,
		nil,
		nil,
	)
	if err != nil {
		t.Fatal("打包文件失败:", err)
	}
	defer tarWriter.Close()
}

打包成io.Reader形式

func TestTarFiles(t *testing.T) {
	buffer := new(bytes.Buffer)
	tarWriter := tar.NewWriter(buffer)
	// 文件白名单
	fileWhitelist := []string{
		"Dockerfile",
		"a.html",
		"test/a.html",
	}

	// 文件夹白名单
	dirWhitelist := []string{
		"Dockerfile",
		"a.html",
		"test/a.html",
	}
	err := tarFolderToReader(tarWriter,
		".",
		false,
		fileWhitelist,
		dirWhitelist,
		nil,
		nil,
	)
	if err != nil {
		t.Fatal("打包文件失败:", err)
	}
	defer tarWriter.Close()

	// 然后这里的buffer就是io.Reader形式
}