OSS 文件&文件夹 直接打包下载
前言
OSS 存放了很多项目(项目是 TMagic 低代码平台编辑生成,自动上传 OSS),现在需要在管理后台将项目打包ZIP下载,并不在本地生成文件。
OSS 要下载项目文件:
一、思路实现
- 创建 OSSClient 实例
- 获取 Bucket 实例
- 获取所有需要的文件信息,循环下载每个文件流,创建并写入 ZIP 存档中
- 下载 ZIP 文件
二、代码实现(Go)
1. OSSClient 创建
代码如下:
const ( EndPoint = "OSS账号EndPoint" AccessKeyId = "OSS账号AccessKeyId" AccessKeySecret = "OSS账号AccessKeySecret" BucketName = "OSS账号BucketName" Prefix = "cuisines/" // 文件前缀 // 创建OSSClient实例。 // yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。 // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 client, err := oss.New(EndPoint, AccessKeyId, AccessKeySecret) if err != nil { log.Fatalf("creates the new client instance failed, err: %v", err.Error()) // 获取Bucket实例 bucket, err := client.Bucket(BucketName) if err != nil { log.Fatalf("gets the bucket instance failed, err: %v", err.Error())
2.获取 OSS 的 Bucket 实例
代码如下:
// 获取Bucket实例 bucket, err := client.Bucket(BucketName) if err != nil { log.Fatalf("gets the bucket instance failed, err: %v", err.Error()) // 列举所有文件。 // oss.Prefix(Prefix) 通过Prefix参数设置列举的文件前缀为"cuisines/" // oss.MaxKeys(1000) 限制数量1000,默认100,最大值1000 lsRes, err := bucket.ListObjectsV2(oss.Prefix(Prefix), oss.MaxKeys(1000)) if err != nil { log.Fatalf("list the objects under the current bucket failed, err: %v", err.Error())
3.获取文件信息,遍历写入ZIP文件中
代码如下:
// 创建文件(cuisines.zip) f, err := os.Create(fmt.Sprintf("%s.zip", strings.TrimSuffix(Prefix, "/"))) if err != nil { log.Fatalf("create file failed, err: %v", err.Error()) // 关闭文件,释放资源。 defer f.Close() // 创建一个向 zip 文件中写入的 writer zipWriter := zip.NewWriter(f) // 关闭压缩文件 defer zipWriter.Close() // 打印列举结果。默认情况下,一次返回100条记录。 for _, object := range lsRes.Objects { // log.Printf("%+v", object.Key) // cuisines/ // cuisines/css/animate.css // cuisines/js/easing.js // cuisines/images/asia.jpg // cuisines/index.html // 将其分成目录和文件名部分。 path = dir + file,例:cuisines/css/animate.css,dir:cuisines/css/,file:animate.css dir, file := filepath.Split(object.Key) dir = strings.TrimPrefix(dir, Prefix) // 路径+文件为空,则跳过,否则创建无名文件 if dir+file == "" { continue // 下载文件到流 body, err := bucket.GetObject(object.Key) if err != nil { log.Fatalf("downloads the object failed, err: %v", err.Error()) // 创建一个文件到ZIP中 fileWriter, err := zipWriter.Create(dir + file) if err != nil { log.Fatalf("adds a file to the zip file failed, err: %v", err.Error()) // 写入文件内容 if _, err := io.Copy(fileWriter, body); err != nil { log.Fatalf("file copy failed, err: %+v", err.Error())
此时,在"main,go"下就会生成"cuisines.zip"文件。但现实中我们可能会存在一个需求,就是在下载打包的时候,需要修改某个文件中信息,那该如何实现呢?
这个实现其实也很简单,只需要我们读取读取下载文件,匹配替换即可。
代码如下:
// 下载文件到流 body, err := bucket.GetObject(object.Key) if err != nil { log.Fatalf("downloads the object failed, err: %v", err.Error()) // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。 defer body.Close() // 创建一个文件到ZIP中 fileWriter, err := zipWriter.Create(dir + file) if err != nil { log.Fatalf("adds a file to the zip file failed, err: %v", err.Error()) // 读取文件数据 data, err := ioutil.ReadAll(body) if err != nil { log.Fatalf("read file failed, err: %+v", err.Error()) // 替换 cuisines/index.html 文件中指定内容 if dir+file == "index.html" { var newData = strings.ReplaceAll(string(data), "<title>Home</title>", "<title>主页</title>") // 写入文件内容 if _, err := io.Copy(fileWriter, strings.NewReader(newData)); err != nil { log.Fatalf("file copy failed, err: %+v", err.Error()) } else { // 写入文件内容 if _, err := io.Copy(fileWriter, body); err != nil { log.Fatalf("file copy failed, err: %+v", err.Error())
前后对比:
4.ZIP 文件下载
如果生成文件到本地,只需要我们项目路由可访问即可下载,但我不想在本地生成 ZIP 文件,只需要下载的时候直接下载,那该如何做呢?
下面我们对代码调整,使用 Gin 框架启动服务。
代码如下:
package main import ( "archive/zip" "bytes" "context" "fmt" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/gin-gonic/gin" "io/ioutil" "log" "net/http" "net/url" "path/filepath" "strings" "time" const ( EndPoint = "OSS账号EndPoint" AccessKeyId = "OSS账号AccessKeyId" AccessKeySecret = "OSS账号AccessKeySecret" BucketName = "OSS账号BucketName" Prefix = "cuisines/" // 文件前缀 // download func main() { router := gin.Default() router.GET("/download", zipDownload) err := http.ListenAndServe(":8888", router) if err != nil { log.Printf("%+v", err.Error()) // 生成ZIP文件,直接下载 func zipDownload(c *gin.Context) { // 创建OSSClient实例。 // yourEndpoint填写Bucket对应的Endpoint,以华东1(杭州)为例,填写为https://oss-cn-hangzhou.aliyuncs.com。其它Region请按实际情况填写。 // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 client, err := oss.New(EndPoint, AccessKeyId, AccessKeySecret) if err != nil { log.Fatalf("creates the new client instance failed, err: %v", err.Error()) // 获取Bucket实例 bucket, err := client.Bucket(BucketName) if err != nil { log.Fatalf("gets the bucket instance failed, err: %v", err.Error()) // 列举所有文件。 // oss.Prefix(Prefix) 通过Prefix参数设置列举的文件前缀为"cuisines/" // oss.MaxKeys(1000) 限制数量1000,默认100,最大值1000 lsRes, err := bucket.ListObjectsV2(oss.Prefix(Prefix), oss.MaxKeys(1000)) if err != nil { log.Fatalf("list the objects under the current bucket failed, err: %v", err.Error()) // 创建一个缓冲区来写入我们的存档。 buf := new(bytes.Buffer) // 创建一个向 zip 文件中写入的 writer zipWriter := zip.NewWriter(buf) // 打印列举结果。默认情况下,一次返回100条记录。 for _, object := range lsRes.Objects { // log.Printf("%+v", object.Key) // cuisines/ // cuisines/css/animate.css // cuisines/js/easing.js // cuisines/images/asia.jpg // cuisines/index.html // 将其分成目录和文件名部分。 path = dir + file,例:cuisines/css/animate.css,dir:cuisines/css/,file:animate.css dir, file := filepath.Split(object.Key) dir = strings.TrimPrefix(dir, Prefix) // 路径+文件为空,则跳过,否则创建无名文件 if dir+file == "" { continue // 下载文件到流 body, err := bucket.GetObject(object.Key) if err != nil { log.Fatalf("downloads the object failed, err: %v", err.Error()) // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。 defer body.Close() // 创建一个文件到ZIP中 fileWriter, err := zipWriter.Create(dir + file) if err != nil { log.Fatalf("adds a file to the zip file failed, err: %v", err.Error()) // 读取文件数据 data, err := ioutil.ReadAll(body) if err != nil { log.Fatalf("read file failed, err: %+v", err.Error()) // 替换 cuisines/index.html 文件中指定内容 if dir+file == "index.html" { var newData = strings.ReplaceAll(string(data), "<title>Home</title>", "<title>主页</title>") // 写入文件内容 if _, err := io.Copy(fileWriter, strings.NewReader(newData)); err != nil { log.Fatalf("file copy failed, err: %+v", err.Error()) } else { // 写入文件内容 if _, err := io.Copy(fileWriter, body); err != nil { log.Fatalf("file copy failed, err: %+v", err.Error()) // 关闭压缩文件 if err = zipWriter.Close(); err != nil { log.Fatalf("zip writer close failed, err: %+v", err.Error()) c.Writer.Header().Set("Content-Type", "application/octet-stream") disposition := fmt.Sprintf("attachment; filename=\"%s.zip\"", strings.TrimSuffix(Prefix, "/")) c.Writer.Header().Set("Content-Disposition", disposition) c.Writer.Write(buf.Bytes())
浏览器上输入"http://127.0.0.1:8888/download",即可下载打包文件
这里项目只有几MB,如果下载的文件过大,不建议直接下载,还是建议下载到本地。