目录
普通文件下载
zip压缩文件下载
大本营:https://blog.csdn.net/HYZX_9987
使用场景:前端发送请求传给后端需要下载的资源id等字段,后端整理数据,形成文件并返回文件,浏览器此时会看到字段下载的文件,与平时在浏览器下载资源看到的效果一致。本文的场景正是项目中所遇到的下载资源对应的yaml。
我的项目中所用的框架是马卡龙,其他主流的gin、beego等也都是大同小异,供大家参考,希望带来帮助!
效果图如下:
路由如下:
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)}
}
路由如下:
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