go实现文件下载(普通文件和压缩文件):基于macaron框架

目录

普通文件下载

zip压缩文件下载


大本营:https://blog.csdn.net/HYZX_9987

使用场景:前端发送请求传给后端需要下载的资源id等字段,后端整理数据,形成文件并返回文件,浏览器此时会看到字段下载的文件,与平时在浏览器下载资源看到的效果一致。本文的场景正是项目中所遇到的下载资源对应的yaml。

我的项目中所用的框架是马卡龙,其他主流的gin、beego等也都是大同小异,供大家参考,希望带来帮助!

效果图如下:

go实现文件下载(普通文件和压缩文件):基于macaron框架_第1张图片

普通文件下载

路由如下:

m.Get("/yaml/download", binding.Bind(model.YamlDetail{}), DownloadYaml)

处理器及相关逻辑:

func DownloadYaml(yaml model.YamlDetail, s session.Store, c *macaron.Context) macaron.ReturnStruct {
	var err error
    //省略部分业务逻辑,bytes为资源的内容,也即文件的内容
	bytes, err := GetYamlbytes(yaml, projectID, token)
	if err != nil {
		errMsg := fmt.Sprint("failed to Get Yaml:", err.Error())
		logError(errMsg)
		c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
		return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
	}

	var fileName string
    //自定义文件名,此处为xx.yaml
	fileName = yaml.Name + ".yaml"
    //生成文件,参数为文件名,默认在根目录下
	f, err := os.Create(fileName)
	if err != nil {
		errMsg := fmt.Sprint("failed to create file:", err.Error())
		logError(errMsg)
		c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
		return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
	}
    //写入内容
	_, err = f.Write(bytes)
	if err != nil {
		errMsg := fmt.Sprint("failed to write yaml-bytes to file:", err.Error())
		logError(errMsg)
		c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
		return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
	}
//获取改文件的绝对路径
	path, err := filepath.Abs(fileName)
	if err != nil {
		errMsg := fmt.Sprint("failed to get file-path:", err.Error())
		logError(errMsg)
		c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
		return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
	}
//发送文件
	c.ServeFile(path, fileName)
//关闭流(至关重要)
	f.Close()
//在目录下删除该文件
	err = os.RemoveAll(fileName)
	if err != nil {
		errMsg := fmt.Sprint("failed to del file:", err.Error())
		logError(errMsg)
		c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
		return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
	}

	return macaron.ReturnStruct{Code: http.StatusOK, Data: string(bytes)}
}

zip压缩文件下载

路由如下:

m.Get("/yaml/downloads/:ArrStr", DownloadYamls)

处理器逻辑:

func DownloadYamls(s session.Store, c *macaron.Context) macaron.ReturnStruct {
	var err error
	var files []string
	var fileList []os.File
	var zipFileType string
    //此处省略大量业务逻辑,yamlGroups为前端需要下载的压缩文件中各个文件的内容集合
	for _, yaml := range yamlGroups {
		bytes, err := GetYamlbytes(yaml, projectID, token)
		if err != nil {
			errMsg := fmt.Sprint("failed to Get Yaml:", err.Error())
			logError(errMsg)
			c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
			return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
		}

		var fileName string
		fileName = yaml.Name + ".yaml"
		f, err := os.Create(fileName)
		if err != nil {
			errMsg := fmt.Sprint("failed to create file:", err.Error())
			logError(errMsg)
			c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
			return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
		}

		_, err = f.Write(bytes)
		if err != nil {
			errMsg := fmt.Sprint("failed to write yaml-byte to file:", err.Error())
			logError(errMsg)
			c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
			return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
		}
           //组装文件集合,分别包括文件名称集合、file类型集合,根据需要定
		files = append(files, fileName)
		fileList = append(fileList, *f)
		zipFileType = yaml.Type
	}
        //此处根据业务需要定义压缩文件的名称zipFileName 
	zipFileName := ""
	switch zipFileType {
	case "workloads":
		zipFileName = "deployment.zip"
	case "ingresses":
		zipFileName = "ingress.zip"
	case "dnsRecords":
		zipFileName = "service.zip"
	case "persistentVolumeClaims":
		zipFileName = "persistentVolumeClaim.zip"
	case "pipelines":
		zipFileName = "pipeline.zip"
	case "configMaps":
		zipFileName = "configMap.zip"
	case "namespacedSecrets":
		zipFileName = "secret.zip"
	}

	path, err := filepath.Abs(zipFileName)
	if err != nil {
		errMsg := fmt.Sprint("failed to Get Abs-Path:", err.Error())
		logError(errMsg)
		c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
		return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
	}
        //发送完文件后删除对应文件
	defer func() {
		var s []string
        //该方法用来获取根目录下的文件
		s, _ = GetAllFile(".", s)
		for _, f := range s {
        //删除前先检查,增加代码的健壮性
			if strings.Contains(f, ".zip") {
				os.Remove(f)
			}
		}
	}()
        //执行压缩
	ZipFiles(zipFileName, files, ".", ".")
	for _, del := range fileList {
		del.Close()
	}

	for _, f := range files {
        //上面组装的文件集合,此处用于删除对应文件,也可defer中执行
		err := os.RemoveAll(f)
		if err != nil {
			errMsg := fmt.Sprint("failed to del file:", err.Error())
			logError(errMsg)
			c.Resp.Header().Set(consts.KeyErrorInfo, errMsg)
			return macaron.ReturnStruct{Code: http.StatusBadRequest, Msg: errMsg}
		}
	}
        //发送文件
	c.ServeFile(path, zipFileName)

	return macaron.ReturnStruct{Code: http.StatusOK}
}

上面用到的相关工具方法如下:

//参数1:压缩文件的名称, 参数2:文件名称的集合
func ZipFiles(filename string, files []string, oldform, newform string) error {

	newZipFile, err := os.Create(filename)
	if err != nil {
		return err
	}

	defer newZipFile.Close()
	zipWriter := zip.NewWriter(newZipFile)
	defer zipWriter.Close()

	// 把files添加到zip中
	for _, file := range files {
		zipfile, err := os.Open(file)
		if err != nil {
			return err
		}
		defer zipfile.Close()
		info, err := zipfile.Stat()
		if err != nil {
			return err
		}
		header, err := zip.FileInfoHeader(info)
		if err != nil {
			return err
		}
		header.Name = strings.Replace(file, oldform, newform, -1)
		header.Method = zip.Deflate
		writer, err := zipWriter.CreateHeader(header)
		if err != nil {
			return err
		}

		if _, err = io.Copy(writer, zipfile); err != nil {
			return err
		}
	}
	return nil
}

//遍历根目录文件的方法
func GetAllFile(pathname string, s []string) ([]string, error) {
	rd, err := ioutil.ReadDir(pathname)
	if err != nil {
		fmt.Println("read dir fail:", err)
		return s, err
	}

	for _, fi := range rd {
		if !fi.IsDir() {
			fullName := pathname + "/" + fi.Name()
			s = append(s, fullName)
		}
	}
	return s, nil
}

需要注意的地方:

1、生成的文件在发送完毕后必须保证删除掉,因为它对于项目来说是多余的

2、文件操作需要注意close

 

 

你可能感兴趣的:(golang)