golang对接七牛文件分块上传

726 阅读2分钟

背景

在网站开发过程中,随着项目的发展,对网站访问的高可用性要求,会使用云存储的架构,这样就会碰到大文件上传的需求。本文介绍如何对接七牛的分片上传。

调用流程

引用官方发布的流程图
image.png
其中的关键点如下:

  • Part 大小要求,除最后一个 Part 外,每个 Part 大小在 1MB - 1GB 之间。
  • 每个 Multipart Upload 任务,最多 10000 个 Part, 编号 PartNumber 在 1 - 10000 (含)之间。
  • 要上传的文件切分成 Part 之后,实现并发上传。具体的并发个数并不是越多速度越快,要结合自身情况考虑。网络情况较好时,建议增大 Part 大小。反之,减小 Part 大小。
  • 在所有 Part 上传完成后,通过调用 completeMultipartUpload 将这些上传完成的 Part 信息严格的按编号 PartNumber 顺序(编号可以不连续,但必须是升序)组装出一个逻辑资源的元信息,从而完成整个资源的分片上传过程。

文档

https://developer.qiniu.com/kodo/6364/multipartupload-interface

代码实现

使用七牛的sdk

https://developer.qiniu.com/kodo/6364/multipartupload-interface
import (
   "bytes"
   "context"
   "crypto/md5"
   "encoding/hex"
   "fmt"
   "io/ioutil"
   "os"
   "path/filepath"
   "time"

   "github.com/qiniu/go-sdk/v7/auth"
   "github.com/qiniu/go-sdk/v7/storage"
)
type FileUploadReq struct {
   Name, AccessKey, SecretKey, Bucket string
   FileContent                        []byte
}

type FileUploadResp struct {
   UrlKey string `json:"url_key"`
   Hash   string `json:"hash"`
}


func upload(fileUploadReq FileUploadReq) (fileResp FileUploadResp, err error) {
   putPolicy := storage.PutPolicy{Scope: fileUploadReq.Bucket}
   mac := auth.New(fileUploadReq.AccessKey, fileUploadReq.SecretKey)
   upToken := putPolicy.UploadToken(mac)

   cfg := storage.Config{}
   // 空间对应的机房
   cfg.Zone, err = storage.GetZone(fileUploadReq.AccessKey, fileUploadReq.Bucket)
   if err != nil {
      return fileResp, err
   }
   // 是否使用https域名
   cfg.UseHTTPS = false
   // 上传是否使用CDN上传加速
   cfg.UseCdnDomains = false

   resumeUploaderV2 := storage.NewResumeUploaderV2(&cfg)
   upHost, err := resumeUploaderV2.UpHost(fileUploadReq.AccessKey, fileUploadReq.Bucket)
   if err != nil {
      return fileResp, err
   }

   // *** 1 初始化分块上传 ***
   initPartsRet := storage.InitPartsRet{}
   err = resumeUploaderV2.InitParts(context.TODO(), upToken, upHost, fileUploadReq.Bucket, fileUploadReq.Name,
      true, &initPartsRet)
   if err != nil {
      return fileResp, err
   }

   
   fileLen := len(fileUploadReq.FileContent)

   chunkSize := 1024 * 1024 // 每一块的大小,1M
   num := fileLen / chunkSize
   if fileLen%chunkSize > 0 {
      num++
   }

   // *** 2 分块上传 ***
   var uploadPartInfos []storage.UploadPartInfo
   for i := 1; i <= num; i++ {

      partNumber := int64(i)
      fmt.Printf("开始上传第%v片数据\n", partNumber)

      var partContentBytes []byte
      endSize := i * chunkSize
      if endSize > fileLen {
         endSize = fileLen
      }
      partContentBytes = fileUploadReq.FileContent[(i-1)*chunkSize : endSize]

      partContentMd5 := Md5(string(partContentBytes))
      uploadPartsRet := storage.UploadPartsRet{}
      err = resumeUploaderV2.UploadParts(context.TODO(), upToken, upHost, fileUploadReq.Bucket, fileUploadReq.Name,
         true, initPartsRet.UploadID, partNumber, partContentMd5, &uploadPartsRet, bytes.NewReader(partContentBytes), len(partContentBytes))
      if err != nil {
         return fileResp, err
      }

      uploadPartInfos = append(uploadPartInfos, storage.UploadPartInfo{
         Etag:       uploadPartsRet.Etag,
         PartNumber: partNumber,
      })

      fmt.Printf("结束上传第%v片数据\n", partNumber)
   }

   // *** 3 完成分块上传 ***
   rPutExtra := storage.RputV2Extra{Progresses: uploadPartInfos}
   completePartRet := storage.PutRet{}
   err = resumeUploaderV2.CompleteParts(context.TODO(), upToken, upHost, &completePartRet, fileUploadReq.Bucket, fileUploadReq.Name,
      true, initPartsRet.UploadID, &rPutExtra)
   if err != nil {
      return fileResp, err
   }

   fileResp.UrlKey = completePartRet.Key
   fileResp.Hash = completePartRet.Hash

   return fileResp, nil
}

调用

Bucket:创建七牛的 bucket AccessKey:在七牛的【个人中心】-> 【密钥管理】查看
SecretKey:在七牛的【个人中心】-> 【密钥管理】查看

func main() {
   file := "文件完整路径"
   fileInfo, err := os.Open(file)
   if err != nil {
      fmt.Println(err.Error())
      return
   }

   defer fileInfo.Close()

   fileContentBytes, err := ioutil.ReadAll(fileInfo)
   if err != nil {
      fmt.Println(err.Error())
      return
   }

   var fileUploadReq = FileUploadReq{
      Bucket:      "七牛 bucket",
      AccessKey:   "七牛 access key",
      SecretKey:   "七牛 secret key",
      Name:        "test/" + time.Now().Format(DateLayout) + "/" + time.Now().Format(TimeLayout) + filepath.Ext(file),
      FileContent: fileContentBytes,
   }

   fileResp, err := upload(fileUploadReq)
   if err != nil {
      fmt.Println(err.Error())
      return
   }

   fmt.Printf("%+v", fileResp)
}