对象存储

  • Go SDK

    最近更新时间:2017-10-22 11:27:38

    简介

    此 SDK 适用于 Go 1.7.0 及以上版本。使用此 SDK 构建您的网络应用程序,能让您以非常便捷的方式将数据安全地存储到七牛云上。无论您的网络应用是一个网站程序,还是包括从云端(服务端程序)到终端(手持设备应用)的架构服务和应用,通过七牛云及其 SDK,都能让您应用程序的终端用户高速上传和下载,同时也让您的服务端更加轻盈。

    Go SDK 属于七牛服务端SDK之一,主要有如下功能:

    1. 提供生成客户端上传所需的上传凭证的功能
    2. 提供文件从服务端直接上传七牛的功能
    3. 提供对七牛空间中文件进行管理的功能
    4. 提供对七牛空间中文件进行处理的功能
    5. 提供七牛CDN相关的刷新,预取,日志功能

    开源

    安装

    按照Go语言的推荐方式,使用如下的命令安装:

    $ go get -u github.com/qiniu/api.v7
    

    鉴权

    七牛 Go SDK 的所有的功能,都需要合法的授权。授权凭证的签算需要七牛账号下的一对有效的Access KeySecret Key,这对密钥可以通过如下步骤获得:

    1. 点击注册🔗开通七牛开发者帐号
    2. 如果已有账号,直接登录七牛开发者后台,点击这里🔗查看 Access Key 和 Secret Key

    文件上传

    上传流程

    七牛文件上传分为客户端上传(主要是指网页端和移动端等面向终端用户的场景)和服务端上传两种场景,具体可以参考文档七牛业务流程

    服务端SDK在上传方面主要提供两种功能,一种是生成客户端上传所需要的上传凭证,另外一种是直接上传文件到云端。

    客户端上传凭证

    客户端(移动端或者Web端)上传文件的时候,需要从客户自己的业务服务器获取上传凭证,而这些上传凭证是通过服务端的SDK来生成的,然后通过客户自己的业务API分发给客户端使用。根据上传的业务需求不同,七牛云 Go SDK支持丰富的上传凭证生成方式。

    // 存储相关功能的引入包只有这两个,后面不再赘述
    import (
        "github.com/qiniu/api.v7/auth/qbox"
        "github.com/qiniu/api.v7/storage"
    )
    
    accessKey := "your access key"
    secretKey := "your secret key"
    mac := qbox.NewMac(accessKey, secretKey)
    

    简单上传的凭证

    最简单的上传凭证只需要AccessKeySecretKeyBucket就可以。

    bucket:="your bucket name"
    putPolicy := storage.PutPolicy{
            Scope: bucket,
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    默认情况下,在不指定上传凭证的有效时间情况下,默认有效期为1个小时。也可以自行指定上传凭证的有效期,例如:

    //自定义凭证有效期(示例2小时,Expires 单位为秒,为上传凭证的有效时间)
    bucket := "your bucket name"
    
    putPolicy := storage.PutPolicy{
        Scope: bucket,
    }
    putPolicy.Expires = 7200 //示例2小时有效期
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    覆盖上传的凭证

    覆盖上传除了需要简单上传所需要的信息之外,还需要想进行覆盖的文件名称,这个文件名称同时可是客户端上传代码中指定的文件名,两者必须一致。

    bucket := "your bucket name"
    
    // 需要覆盖的文件名
    keyToOverwrite := "qiniu.mp4"
    putPolicy := storage.PutPolicy{
        Scope: fmt.Sprintf("%s:%s", bucket, keyToOverwrite),
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    自定义上传回复的凭证

    默认情况下,文件上传到七牛之后,在没有设置returnBody或者回调相关的参数情况下,七牛返回给上传端的回复格式为hashkey,例如:

    {"hash":"Ftgm-CkWePC9fzMBTRNmPMhGBcSV","key":"qiniu.jpg"}
    

    有时候我们希望能自定义这个返回的JSON格式的内容,可以通过设置returnBody参数来实现,在returnBody中,我们可以使用七牛支持的魔法变量自定义变量

    bucket := "your bucket name"
    
    putPolicy := storage.PutPolicy{
        Scope:      bucket,
        ReturnBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    则文件上传到七牛之后,收到的回复内容格式如下:

    {"key":"github-x.png","hash":"FqKXVdTvIx_mPjOYdjDyUSy_H1jr","fsize":6091,"bucket":"if-pbl","name":"github logo"}
    

    对于上面的自定义返回值,我们需要自定义结构体来解析这个回复,例如下面提供了一个解析结果的方法:

    // 自定义返回值结构体
    type MyPutRet struct {
        Key    string
        Hash   string
        Fsize  int
        Bucket string
        Name   string
    }
    
    localFile := "your local file path"
    bucket := "your bucket name"
    key := "your file save key"
    
    // 使用 returnBody 自定义回复格式
    putPolicy := storage.PutPolicy{
        Scope:      bucket,
        ReturnBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    
    cfg := storage.Config{}
    formUploader := storage.NewFormUploader(&cfg)
    ret := MyPutRet{}
    putExtra := storage.PutExtra{
        Params: map[string]string{
            "x:name": "github logo",
        },
    }
    err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Bucket, ret.Key, ret.Fsize, ret.Hash, ret.Name)
    

    带回调业务服务器的凭证

    上面生成的自定义上传回复的上传凭证适用于上传端(无论是客户端还是服务端)和七牛服务器之间进行直接交互的情况下。在客户端上传的场景之下,有时候客户端需要在文件上传到七牛之后,从业务服务器获取相关的信息,这个时候就要用到七牛的上传回调及相关回调参数的设置。

    putPolicy := storage.PutPolicy{
        Scope:            bucket,
        CallbackURL:      "http://api.example.com/qiniu/upload/callback",
        CallbackBody:     `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
        CallbackBodyType: "application/json",
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    在使用了上传回调的情况下,客户端收到的回复就是业务服务器响应七牛的JSON格式内容,客户端收到回调之后必须响应JSON格式的回复給七牛,这个回复会被七牛传递给客户端。 例如上面的 CallbackBody 的设置会在文件上传到七牛之后,触发七牛回调如下内容給业务服务器:

    {"key":"github-x.png","hash":"FqKXVdTvIx_mPjOYdjDyUSy_H1jr","fsize":6091,"bucket":"if-pbl","name":"github logo"}
    

    通常情况下,我们建议使用application/json格式来设置callbackBody,保持数据格式的统一性。实际情况下,callbackBody也支持application/x-www-form-urlencoded格式来组织内容,这个主要看业务服务器在接收到callbackBody的内容时如何解析。例如:

    putPolicy := storage.PutPolicy{
        Scope:        bucket,
        CallbackURL:  "http://api.example.com/qiniu/upload/callback",
        CallbackBody: "key=$(key)&hash=$(etag)&bucket=$(bucket)&fsize=$(fsize)&name=$(x:name)",
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    带数据处理的凭证

    七牛支持在文件上传到七牛之后,立即对其进行多种指令的数据处理,这个只需要在生成的上传凭证中指定相关的处理参数即可。

    saveMp4Entry := base64.URLEncoding.EncodeToString([]byte(bucket + ":avthumb_test_target.mp4"))
    saveJpgEntry := base64.URLEncoding.EncodeToString([]byte(bucket + ":vframe_test_target.jpg"))
    //数据处理指令,支持多个指令
    avthumbMp4Fop := "avthumb/mp4|saveas/" + saveMp4Entry
    vframeJpgFop := "vframe/jpg/offset/1|saveas/" + saveJpgEntry
    
    //连接多个操作指令
    persistentOps := strings.Join([]string{avthumbMp4Fop, vframeJpgFop}, ";")
    pipeline := "test"
    
    putPolicy := storage.PutPolicy{
        Scope:               bucket,
        PersistentOps:       persistentOps,
        PersistentPipeline:  pipeline,
        PersistentNotifyURL: "http://api.example.com/qiniu/pfop/notify",
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

    队列 pipeline 请参阅创建私有队列;转码操作具体参数请参阅音视频转码;saveas 请参阅处理结果另存

    带自定义参数的凭证

    七牛支持客户端上传文件的时候定义一些自定义参数,这些参数可以在returnBodycallbackBody里面和七牛内置支持的魔法变量(即系统变量)通过相同的方式来引用。这些自定义的参数名称必须以x:开头。例如客户端上传的时候指定了自定义的参数x:namex:age分别是stringint类型。那么可以通过下面的方式引用:

    putPolicy := storage.PutPolicy{
        //其他上传策略参数...
        ReturnBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}`,
    }
    

    或者

    putPolicy := storage.PutPolicy{
        //其他上传策略参数...
        CallbackBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)","age":$(x:age)}`,
    }
    

    综合上传凭证

    上面的生成上传凭证的方法,都是通过设置上传策略🔗相关的参数来支持的,这些参数可以通过不同的组合方式来满足不同的业务需求,可以灵活地组织你所需要的上传凭证。

    服务端直传

    服务端直传是指客户利用七牛服务端SDK从服务端直接上传文件到七牛云,交互的双方一般都在机房里面,所以服务端可以自己生成上传凭证,然后利用SDK中的上传逻辑进行上传,最后从七牛云获取上传的结果,这个过程中由于双方都是业务服务器,所以很少利用到上传回调的功能,而是直接自定义returnBody来获取自定义的回复内容。

    构建配置类

    七牛存储支持空间创建在不同的机房,在使用七牛的 Go SDK 中的FormUploaderResumeUploader上传文件之前,必须要构建一个上传用的Config对象,在该对象中,可以指定空间对应的zone以及其他的一些影响上传的参数。

    cfg := storage.Config{}
    
    // 空间对应的机房
    cfg.Zone = &storage.ZoneHuadong
    // 是否使用https域名
    cfg.UseHTTPS = false
    // 上传是否使用CDN上传加速
    cfg.UseCdnDomains = false
    

    其中关于Zone对象和机房的关系如下:

    机房 Zone对象
    华东 storage.ZoneHuadong
    华北 storage.ZoneHuabei
    华南 storage.ZoneHuanan
    北美 storage.ZoneBeimei

    文件上传(表单方式)

    最简单的就是上传本地文件,直接指定文件的完整路径即可上传。

    localFile = "/Users/jemy/Documents/github.png"
    bucket = "if-pbl"
    key = "github-x.png"
    
    putPolicy := storage.PutPolicy{
        Scope:               bucket,
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    
    cfg := storage.Config{}
    // 空间对应的机房
    cfg.Zone = &storage.ZoneHuadong
    // 是否使用https域名
    cfg.UseHTTPS = false
    // 上传是否使用CDN上传加速
    cfg.UseCdnDomains = false
    
    // 构建表单上传的对象
    formUploader := storage.NewFormUploader(&cfg)
    ret := storage.PutRet{}
    
    // 可选配置
    putExtra := storage.PutExtra{
        Params: map[string]string{
            "x:name": "github logo",
        },
    }
    err := formUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Key,ret.Hash)
    

    字节数组上传(表单方式)

    可以支持将内存中的字节数组上传到空间中。

    putPolicy := storage.PutPolicy{
            Scope: bucket,
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    
    cfg := storage.Config{}
    // 空间对应的机房
    cfg.Zone = &storage.ZoneHuadong
    // 是否使用https域名
    cfg.UseHTTPS = false
    // 上传是否使用CDN上传加速
    cfg.UseCdnDomains = false
    
    formUploader := storage.NewFormUploader(&cfg)
    ret := storage.PutRet{}
    putExtra := storage.PutExtra{
        Params: map[string]string{
            "x:name": "github logo",
        },
    }
    
    data := []byte("hello, this is qiniu cloud")
    dataLen := int64(len(data))
    err := formUploader.Put(context.Background(), &ret, upToken, key, bytes.NewReader(data), dataLen, &putExtra)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Key, ret.Hash)
    

    数据流上传(表单方式)

    io.Reader对象的上传也是采用Put方法或者PutWithoutKey方法,使用方式和上述的 字节数组上传 方式相同。

    文件分片上传

    对于大的文件,我们一般推荐使用分片上传的方式来上传文件,分片上传通过将一个文件切割为标准的块(固定大小4MB),然后再将每个块切割为数据片,然后通过上传片的方式来进行文件的上传。一个块中的片和另外一个块中的片是可以并发的,但是同一个块中的片是顺序上传的。片大小必须可以整除块大小4MB,服务端SDK中默认片大小为4MB,以提高上传效率。分片上传不等于断点续传,但是分片上传可以支持断点续传。

    断点续传是将每个块上传完毕的返回的context保存到本地的文件中持久化,如果本次上传被中断,下次可以从这个进度文件中读取每个块上传的状态,然后继续上传完毕没有完成的块,最后完成文件的拼接。这里需要注意,只有在块上传完毕之后,才向本地的进度文件写入context内容。另外需要注意,每个context的有效期最长是7天,过期的context会触发701的错误,我们可以检查context中的expire参数来确认上传的进度是否过期。

    putPolicy := storage.PutPolicy{
            Scope: bucket,
    }
    mac := qbox.NewMac(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    
    cfg := storage.Config{}
    // 空间对应的机房
    cfg.Zone = &storage.ZoneHuadong
    // 是否使用https域名
    cfg.UseHTTPS = false
    // 上传是否使用CDN上传加速
    cfg.UseCdnDomains = false
    
    resumeUploader := storage.NewResumeUploader(&cfg)
    ret := storage.PutRet{}
    putExtra := storage.RputExtra{
    
    }
    err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Key, ret.Hash)
    

    文件断点续传

    断点续传是基于分片上传来实现的,基本原理就是用一个文本文件记录下上传的进度,如果上传中断,下次再从这个文件读取进度,继续上传未完成的分块。

    package main
    
    import (
        "crypto/md5"
        "encoding/hex"
        "encoding/json"
        "fmt"
        "github.com/qiniu/api.v7/auth/qbox"
        "github.com/qiniu/api.v7/storage"
        "golang.org/x/net/context"
        "io/ioutil"
        "os"
        "path/filepath"
    )
    
    var (
        accessKey = "your access key"
        secretKey = "your secret key"
    )
    
    func md5Hex(str string) string {
        h := md5.New()
        h.Write([]byte(str))
        return hex.EncodeToString(h.Sum(nil))
    }
    
    type ProgressRecord struct {
        Progresses []storage.BlkputRet `json:"progresses"`
    }
    
    func main() {
        localFile := "your local file path"
        bucket := "your bucket name"
        key := "your file save key"
    
        putPolicy := storage.PutPolicy{
            Scope: bucket,
        }
        mac := qbox.NewMac(accessKey, secretKey)
        upToken := putPolicy.UploadToken(mac)
    
        cfg := storage.Config{}
        // 空间对应的机房
        cfg.Zone = &storage.ZoneHuadong
        // 是否使用https域名
        cfg.UseHTTPS = false
        // 上传是否使用CDN上传加速
        cfg.UseCdnDomains = false
    
        // 必须仔细选择一个能标志上传唯一性的 recordKey 用来记录上传进度
        // 我们这里采用 md5(bucket+key+local_path+local_file_last_modified)+".progress" 作为记录上传进度的文件名
        fileInfo, statErr := os.Stat(localFile)
        if statErr != nil {
            fmt.Println(statErr)
            return
        }
    
        fileSize := fileInfo.Size()
        fileLmd := fileInfo.ModTime().UnixNano()
        recordKey := md5Hex(fmt.Sprintf("%s:%s:%s:%s", bucket, key, localFile, fileLmd)) + ".progress"
    
        // 指定的进度文件保存目录,实际情况下,请确保该目录存在,而且只用于记录进度文件
        recordDir := "/Users/jemy/Temp/progress"
        mErr := os.MkdirAll(recordDir, 0755)
        if mErr != nil {
            fmt.Println("mkdir for record dir error,", mErr)
            return
        }
    
        recordPath := filepath.Join(recordDir, recordKey)
    
        progressRecord := ProgressRecord{}
        // 尝试从旧的进度文件中读取进度
        recordFp, openErr := os.Open(recordPath)
        if openErr == nil {
            progressBytes, readErr := ioutil.ReadAll(recordFp)
            if readErr == nil {
                mErr := json.Unmarshal(progressBytes, &progressRecord)
                if mErr == nil {
                    // 检查context 是否过期,避免701错误
                    for _, item := range progressRecord.Progresses {
                        if storage.IsContextExpired(item) {
                            fmt.Println(item.ExpiredAt)
                            progressRecord.Progresses = make([]storage.BlkputRet, storage.BlockCount(fileSize))
                            break
                        }
                    }
                }
            }
            recordFp.Close()
        }
    
        if len(progressRecord.Progresses) == 0 {
            progressRecord.Progresses = make([]storage.BlkputRet, storage.BlockCount(fileSize))
        }
    
        resumeUploader := storage.NewResumeUploader(&cfg)
        ret := storage.PutRet{}
        progressLock := sync.RWMutex{}
    
        putExtra := storage.RputExtra{
            Progresses: progressRecord.Progresses,
            Notify: func(blkIdx int, blkSize int, ret *storage.BlkputRet) {
                progressLock.Lock()
                progressLock.Unlock()
    
                //将进度序列化,然后写入文件
                progressRecord.Progresses[blkIdx] = *ret
                progressBytes, _ := json.Marshal(progressRecord)
                fmt.Println("write progress file", blkIdx, recordPath)
                wErr := ioutil.WriteFile(recordPath, progressBytes, 0644)
                if wErr != nil {
                    fmt.Println("write progress file error,", wErr)
                }
            },
        }
        err := resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
        if err != nil {
            fmt.Println(err)
            return
        }
        //上传成功之后,一定记得删除这个进度文件
        os.Remove(recordPath)
        fmt.Println(ret.Key, ret.Hash)
    }
    

    解析自定义回复内容

    有些情况下,七牛返回给上传端的内容不是默认的hashkey形式,这种情况下,可能出现在自定义returnBody或者自定义了callbackBody的情况下,前者一般是服务端直传的场景,而后者则是接受上传回调的场景,这两种场景之下,都涉及到需要将自定义的回复进行内容解析,一般建议在交互过程中,都采用JSON的方式,这样处理起来方法比较一致,而且JSON的方法最通用。默认情况下SDK提供了storage.PutRet来作为标准回复的解析结构体,如果需要用到自定义回复,可以类似上面讲解returnBody的时候给的例子,自定义一个结构体用于回复的解析,例如:

    // 自定义返回值结构体
    type MyPutRet struct {
        Key    string
        Hash   string
        Fsize  int
        Bucket string
        Name   string
    }
    

    业务服务器验证七牛回调

    在上传策略里面设置了上传回调相关参数的时候,七牛在文件上传到服务器之后,会主动地向callbackUrl发送POST请求的回调,回调的内容为callbackBody模版所定义的内容,如果这个模版里面引用了魔法变量或者自定义变量,那么这些变量会被自动填充对应的值,然后在发送给业务服务器。

    业务服务器在收到来自七牛的回调请求的时候,可以根据请求头部的Authorization字段来进行验证,查看该请求是否是来自七牛的未经篡改的请求。

    Go SDK 提供了一个方法 qbox.VerifyCallback 用于验证回调的请求:

    // VerifyCallback 验证上传回调请求是否来自七牛
    func VerifyCallback(mac *Mac, req *http.Request) (bool, error) {
        return mac.VerifyCallback(req)
    }
    

    下载文件

    文件下载分为公开空间的文件下载和私有空间的文件下载。

    公开空间

    对于公开空间,其访问的链接主要是将空间绑定的域名(可以是七牛空间的默认域名或者是绑定的自定义域名)拼接上空间里面的文件名即可访问,标准情况下需要在拼接链接之前,将文件名进行urlencode以兼容不同的字符。 当然也可以直接使用SDK里面的方法,传入域名和原始文件名即可得到公开访问外链。

    domain := "https://image.example.com"
    key := "这是一个测试文件.jpg"
    publicAccessURL := storage.MakePublicURL(domain, key)
    fmt.Println(publicAccessURL)
    

    私有空间

    对于私有空间,首先需要按照公开空间的文件访问方式构建对应的公开空间访问链接,然后再对这个链接进行私有授权签名。

    mac := qbox.NewMac(accessKey, secretKey)
    domain := "https://image.example.com"
    key := "这是一个测试文件.jpg"
    
    deadline := time.Now().Add(time.Second * 3600).Unix() //1小时有效期
    privateAccessURL := storage.MakePrivateURL(mac, domain, key, deadline)
    

    资源管理

    资源管理包括的主要功能有:

    资源管理相关的操作首先要构建BucketManager对象:

    mac := qbox.NewMac(accessKey, secretKey)
    cfg := storage.Config{
        // 是否使用https域名进行资源管理
        UseHTTPS: false,
    }
    
    // 指定空间所在的区域,如果不指定将自动探测
    // 如果没有特殊需求,默认不需要指定
    //cfg.Zone=&storage.ZoneHuabei
    
    bucketManager := storage.NewBucketManager(mac, &cfg)
    

    获取文件信息

    bucket := "if-pbl"
    key := "github.png"
    
    fileInfo, sErr := bucketManager.Stat(bucket, key)
    if sErr != nil {
        fmt.Println(sErr)
        return
    }
    fmt.Println(fileInfo.String())
    //可以解析文件的PutTime
    fmt.Println(storage.ParsePutTime(fileInfo.PutTime))
    

    修改文件MimeType

    bucket := "if-pbl"
    key := "github.png"
    newMime := "image/x-png"
    
    err := bucketManager.ChangeMime(bucket, key, newMime)
    if err != nil {
        fmt.Println(err)
        return
    }
    

    修改文件存储类型

    bucket := "if-pbl"
    key := "github.png"
    fileType := 1
    
    err := bucketManager.ChangeType(bucket, key, fileType)
    if err != nil {
        fmt.Println(err)
        return
    }
    

    移动或重命名文件

    移动操作本身支持移动文件到相同,不同空间中,在移动的同时也可以支持文件重命名。唯一的限制条件是,移动的源空间和目标空间必须在同一个机房。

    源空间 目标空间 源文件名 目标文件名 描述
    BucketA BucketA KeyA KeyB 相当于同空间文件重命名
    BucketA BucketB KeyA KeyA 移动文件到BucketB,文件名一致
    BucketA BucketB KeyA KeyB 移动文件到BucketB,文件名变成KeyB

    move操作支持强制覆盖选项,即如果目标文件已存在,可以设置强制覆盖选项force来覆盖那个文件的内容。

    srcBucket := "if-pbl"
    srcKey := "github.png"
    
    //目标空间可以和源空间相同,但是不能为跨机房的空间
    destBucket := srcBucket
    //目标文件名可以和源文件名相同,也可以不同
    destKey := "github-new.png"
    
    //如果目标文件存在,是否强制覆盖,如果不覆盖,默认返回614 file exists
    force := false
    err := bucketManager.Move(srcBucket, srcKey, destBucket, destKey, force)
    if err != nil {
        fmt.Println(err)
        return
    }
    

    复制文件副本

    文件的复制和文件移动其实操作一样,主要的区别是移动后源文件不存在了,而复制的结果是源文件还存在,只是多了一个新的文件副本。

    srcBucket := "if-pbl"
    srcKey := "github.png"
    
    //目标空间可以和源空间相同,但是不能为跨机房的空间
    destBucket := srcBucket
    //目标文件名可以和源文件名相同,也可以不同
    destKey := "github-new.png"
    
    //如果目标文件存在,是否强制覆盖,如果不覆盖,默认返回614 file exists
    force := false
    err := bucketManager.Copy(srcBucket, srcKey, destBucket, destKey, force)
    if err != nil {
        fmt.Println(err)
        return
    }
    

    删除空间中的文件

    bucket := "if-pbl"
    key := "github.png"
    
    err := bucketManager.Delete(bucket, key)
    if err != nil {
        fmt.Println(err)
        return
    }
    

    设置或更新文件的生存时间

    可以给已经存在于空间中的文件设置文件生存时间,或者更新已设置了生存时间但尚未被删除的文件的新的生存时间。

    bucket := "if-pbl"
    key := "github.png"
    days := 7
    
    err := bucketManager.DeleteAfterDays(bucket, key, days)
    if err != nil {
        fmt.Println(err)
        return
    }
    

    获取指定前缀的文件列表

    获取指定文件前缀的列表用来从空间列举文件,每次最大的列举数量为1000,如果需要列举超过1000的文件数量,可以根据每次函数调用返回的nextMarker进行循环列举。

    bucket := "if-bc"
    limit := 1000
    prefix := "qshell/"
    delimiter := ""
    //初始列举marker为空
    marker := ""
    
    for {
        entries, _, nextMarker, hashNext, err := bucketManager.ListFiles(bucket, prefix, delimiter, marker, limit)
        if err != nil {
            fmt.Println("list error,", err)
            break
        }
    
        //print entries
        for _, entry := range entries {
            fmt.Println(entry.Key)
        }
    
        if hashNext {
            marker = nextMarker
        } else {
            //list end
            break
        }
    
    }
    

    抓取网络资源到空间

    bucket := "if-bc"
    resURL := "http://devtools.qiniu.com/qiniu.png"
    
    // 指定保存的key
    fetchRet, err := bucketManager.Fetch(resURL, bucket, "qiniu.png")
    if err != nil {
        fmt.Println("fetch error,", err)
    } else {
        fmt.Println(fetchRet.String())
    }
    
    // 不指定保存的key,默认用文件hash作为文件名
    fetchRet, err = bucketManager.FetchWithoutKey(resURL, bucket)
    if err != nil {
        fmt.Println("fetch error,", err)
    } else {
        fmt.Println(fetchRet.String())
    }
    

    更新镜像空间中存储的文件内容

    bucket := "if-bc"
    key := "qiniu.png"
    
    err := bucketManager.Prefetch(bucket, key)
    if err != nil {
        fmt.Println("fetch error,", err)
    }
    

    资源管理批量操作

    批量获取文件信息

    //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
    bucket := "if-pbl"
    keys := []string{
        "github1.png",
        "github2.png",
        "github3.png",
        "github4.png",
        "github5.png",
    }
    statOps := make([]string, 0, len(keys))
    for _, key := range keys {
        statOps = append(statOps, storage.URIStat(bucket, key))
    }
    
    rets, err := bucketManager.Batch(statOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                } else {
                    fmt.Printf("%v\n", ret.Data)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
            fmt.Printf("%v\n", ret.Data)
        }
    }
    

    批量修改文件类型

    //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
    bucket := "if-pbl"
    chgmKeys := map[string]string{
        "github1.png": "image/x-png",
        "github2.png": "image/x-png",
        "github3.png": "image/x-png",
        "github4.png": "image/x-png",
        "github5.png": "image/x-png",
    }
    chgmOps := make([]string, 0, len(chgmKeys))
    for key, newMime := range chgmKeys {
        chgmOps = append(chgmOps, storage.URIChangeMime(bucket, key, newMime))
    }
    
    rets, err := bucketManager.Batch(chgmOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
            if ret.Code != 200 {
                fmt.Printf("%s\n", ret.Data.Error)
            }
        }
    }
    

    批量删除文件

    //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
    bucket := "if-pbl"
    keys := []string{
        "github1.png",
        "github2.png",
        "github3.png",
        "github4.png",
        "github5.png",
    }
    deleteOps := make([]string, 0, len(keys))
    for _, key := range keys {
        deleteOps = append(deleteOps, storage.URIDelete(bucket, key))
    }
    
    rets, err := bucketManager.Batch(deleteOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
        }
    }
    

    批量复制文件

    srcBucket := "if-pbl"
    destBucket := "if-pbl"
    force := true
    copyKeys := map[string]string{
        "github1.png": "github1-copy.png",
        "github2.png": "github2-copy.png",
        "github3.png": "github3-copy.png",
        "github4.png": "github4-copy.png",
        "github5.png": "github5-copy.png",
    }
    copyOps := make([]string, 0, len(copyKeys))
    for srcKey, destKey := range copyKeys {
        copyOps = append(copyOps, storage.URICopy(srcBucket, srcKey, destBucket, destKey, force))
    }
    
    rets, err := bucketManager.Batch(copyOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
            fmt.Printf("%v\n", ret.Data)
        }
    }
    

    批量移动或重命名文件

    srcBucket := "if-pbl"
    destBucket := "if-pbl"
    force := true
    moveKeys := map[string]string{
        "github1.png": "github1-move.png",
        "github2.png": "github2-move.png",
        "github3.png": "github3-move.png",
        "github4.png": "github4-move.png",
        "github5.png": "github5-move.png",
    }
    moveOps := make([]string, 0, len(moveKeys))
    for srcKey, destKey := range moveKeys {
        moveOps = append(moveOps, storage.URIMove(srcBucket, srcKey, destBucket, destKey, force))
    }
    
    rets, err := bucketManager.Batch(moveOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
            fmt.Printf("%v\n", ret.Data)
        }
    }
    

    批量更新文件的有效期

    //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
    bucket := "if-bc"
    expireKeys := map[string]int{
        "github1.png": 7,
        "github2.png": 8,
        "github3.png": 9,
        "github4.png": 10,
        "github5.png": 11,
    }
    expireOps := make([]string, 0, len(expireKeys))
    for key, expire := range expireKeys {
        expireOps = append(expireOps, storage.URIDeleteAfterDays(bucket, key, expire))
    }
    
    rets, err := bucketManager.Batch(expireOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
            if ret.Code != 200 {
                fmt.Printf("%s\n", ret.Data.Error)
            }
        }
    }
    

    批量更新文件存储类型

    //每个batch的操作数量不可以超过1000个,如果总数量超过1000,需要分批发送
    bucket := "if-pbl"
    chtypeKeys := map[string]int{
        "github1.png": 1,
        "github2.png": 1,
        "github3.png": 1,
        "github4.png": 1,
        "github5.png": 1,
    }
    chtypeOps := make([]string, 0, len(chtypeKeys))
    for key, fileType := range chtypeKeys {
        chtypeOps = append(chtypeOps, storage.URIChangeType(bucket, key, fileType))
    }
    
    rets, err := bucketManager.Batch(chtypeOps)
    if err != nil {
        // 遇到错误
        if _, ok := err.(*rpc.ErrorInfo); ok {
            for _, ret := range rets {
                // 200 为成功
                fmt.Printf("%d\n", ret.Code)
                if ret.Code != 200 {
                    fmt.Printf("%s\n", ret.Data.Error)
                }
            }
        } else {
            fmt.Printf("batch error, %s", err)
        }
    } else {
        // 完全成功
        for _, ret := range rets {
            // 200 为成功
            fmt.Printf("%d\n", ret.Code)
            if ret.Code != 200 {
                fmt.Printf("%s\n", ret.Data.Error)
            }
        }
    }
    

    持久化数据处理

    发送数据处理请求

    对于已经保存到七牛空间的文件,可以通过发送持久化的数据处理指令来进行处理,这些指令支持七牛官方提供的指令,也包括客户自己开发的自定义数据处理的指令。数据处理的结果还可以通过七牛主动通知的方式告知业务服务器。

    使用数据处理,首先需要构建一个OperationManager对象:

    mac := qbox.NewMac(accessKey, secretKey)
    cfg := storage.Config{
        UseHTTPS: false,
    }
    
    // 指定空间所在的区域,如果不指定将自动探测
    // 如果没有特殊需求,默认不需要指定
    //cfg.Zone=&storage.ZoneHuabei
    operationManager := storage.NewOperationManager(mac, &cfg)
    

    进行视频的处理示例:

    bucket := "if-pbl"
    key := "qiniu.mp4"
    mac := qbox.NewMac(accessKey, secretKey)
    
    saveBucket := bucket
    
    // 处理指令集合
    fopAvthumb := fmt.Sprintf("avthumb/mp4/s/480x320/vb/500k|saveas/%s",
        storage.EncodedEntry(saveBucket, "pfop_test_qiniu.mp4"))
    fopVframe := fmt.Sprintf("vframe/jpg/offset/10|saveas/%s",
        storage.EncodedEntry(saveBucket, "pfop_test_qiniu.jpg"))
    fopVsample := fmt.Sprintf("vsample/jpg/interval/20/pattern/%s",
        base64.URLEncoding.EncodeToString([]byte("pfop_test_$(count).jpg")))
    
    fopBatch := []string{fopAvthumb, fopVframe, fopVsample}
    fops := strings.Join(fopBatch, ";")
    
    // 强制重新执行数据处理任务
    force := true
    // 数据处理指令全部完成之后,通知该地址
    notifyURL := "http://api.example.com/pfop/callback"
    // 数据处理的私有队列,必须指定以保障处理速度
    pipeline := "jemy"
    persistentId, err := operationManager.Pfop(bucket, key, fops, pipeline, notifyURL, force)
    if err != nil {
        fmt.Println(err)
        return
    }
    
    fmt.Println(persistentId)
    

    查询数据处理请求状态

    由于数据处理是异步处理,可以根据发送处理请求时返回的 persistentId 去查询任务的处理进度,如果在上传策略里面设置了persistentNotifyUrl或者发送处理请求时指定notifyURL的情况下,直接业务服务器等待处理结果通知即可,如果需要主动查询,可以采用如下代码中的:

    persistentId := "z0.597f28b445a2650c994bb208"
    ret, err := operationManager.Prefop(persistentId)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.String())
    

    CDN相关功能

    在使用CDN相关功能之前,需要构建CdnManager对象:

    mac := qbox.NewMac(accessKey, secretKey)
    cdnManager := cdn.NewCdnManager(mac)
    

    文件刷新

    //刷新链接,单次请求链接不可以超过100个,如果超过,请分批发送请求
    urlsToRefresh := []string{
        "http://if-pbl.qiniudn.com/qiniu.png",
        "http://if-pbl.qiniudn.com/github.png",
    }
    
    ret, err := cdnManager.RefreshUrls(urlsToRefresh)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Code)
    fmt.Println(ret.RequestID)
    

    目录刷新

    // 刷新目录,刷新目录需要联系七牛技术支持开通权限
    // 单次请求链接不可以超过10个,如果超过,请分批发送请求
    dirsToRefresh := []string{
        "http://if-pbl.qiniudn.com/images/",
        "http://if-pbl.qiniudn.com/static/",
    }
    
    ret, err := cdnManager.RefreshDirs(dirsToRefresh)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Code)
    fmt.Println(ret.RequestID)
    fmt.Println(ret.Error)
    

    文件预取

    // 预取链接,单次请求链接不可以超过100个,如果超过,请分批发送请求
    urlsToPrefetch := []string{
        "http://if-pbl.qiniudn.com/qiniu.png",
        "http://if-pbl.qiniudn.com/github.png",
    }
    
    ret, err := cdnManager.PrefetchUrls(urlsToPrefetch)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(ret.Code)
    fmt.Println(ret.RequestID)
    

    获取域名流量

    domains := []string{
        "if-pbl.qiniudn.com",
        "qdisk.qiniudn.com",
    }
    startDate := "2017-07-30"
    endDate := "2017-07-31"
    granularity := "day"
    
    data, err := cdnManager.GetFluxData(startDate, endDate, granularity, domains)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("%v\n", data)
    

    获取域名带宽

    domains := []string{
        "if-pbl.qiniudn.com",
        "qdisk.qiniudn.com",
    }
    startDate := "2017-07-30"
    endDate := "2017-07-31"
    granularity := "day"
    
    data, err := cdnManager.GetBandwidthData(startDate, endDate, granularity, domains)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Printf("%v\n", data)
    

    获取日志下载链接

    domains := []string{
        "if-pbl.qiniudn.com",
        "qdisk.qiniudn.com",
    }
    
    day := "2017-07-30"
    
    ret, err := cdnManager.GetCdnLogList(day, domains)
    if err != nil {
        fmt.Println(err)
        return
    }
    
    domainLogs := ret.Data
    for domain, logs := range domainLogs {
        fmt.Println(domain)
        for _, item := range logs {
            fmt.Println(item.Name, item.URL, item.Size, item.ModifiedTime)
        }
    }
    

    构建时间戳防盗链访问链接

    具体算法可以参考:时间戳防盗链

    urlStr := "http://img.abc.com/test.jpg"
    cryptKey := "abc123"
    deadline := time.Now().Add(time.Second * 3600).Unix()
    accessUrl, err := cdn.CreateTimestampAntileechURL(urlStr, cryptKey, deadline)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(accessUrl)
    

    API 参考

    常见问题

    • Go SDK的 err 保留了请求的错误信息,回复信息和头部信息,遇到问题时,可以都打印出来提交给我们排查问题。
    if err != nil {
        if v, ok := err.(*rpc.ErrorInfo); ok {
            fmt.Println(v.Code, v.Reqid, v.Err)
        } else {
            fmt.Println(err)
        }
    }
    
    • API 的使用,可以参考我们为大家精心准备的使用实例

    相关资源

    如果您有任何关于我们文档或产品的建议和想法,欢迎您通过以下方式与我们互动讨论:

    • 技术论坛 - 在这里您可以和其他开发者愉快的讨论如何更好的使用七牛云服务
    • 提交工单 - 如果您的问题不适合在论坛讨论或希望及时解决,您也可以提交一个工单,我们的技术支持人员会第一时间回复您
    • 博客 - 这里会持续更新发布市场活动和技术分享文章
    • 微博
    • 常见问题

    贡献代码

    1. Fork

    2. 创建您的特性分支 git checkout -b my-new-feature

    3. 提交您的改动 git commit -am 'Added some feature'

    4. 将您的修改记录提交到远程 git 仓库 git push origin my-new-feature

    5. 然后到 github 网站的该 git 远程仓库的 my-new-feature 分支下发起 Pull Request

    许可证

    Copyright (c) 2014 qiniu.com

    基于 MIT 协议发布:

    以上内容是否对您有帮助?
  • 提交工单