背景
在网站开发过程中,随着项目的发展,对网站访问的高可用性要求,会使用云存储的架构,这样就会碰到大文件上传的需求。本文介绍如何对接七牛的分片上传。
调用流程
引用官方发布的流程图
其中的关键点如下:
- 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)
}