human: 如何处理七牛云的对象存储Go SDK

gpt: [对象存储](https://developer.qiniu.com/kodo) > SDK 下载 > Go SDK

#  Go SDK

最近更新时间: 2024-01-10 17:43:00

# 简介

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

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

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

# 开源

  * [Go SDK 项目地址 __](https://github.com/qiniu/go-sdk)
  * [Go SDK 发布地址 __](https://github.com/qiniu/go-sdk/releases)
  * [Go SDK 历史文档](https://developer.qiniu.com/kodo/sdk/go-v6)

# 安装

推荐使用 go mod , `github.com/qiniu/go-sdk/v7` ，如 `require github.com/qiniu/go-
sdk/v7 v7.19.0` ；

# 鉴权

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

  1. 点击[注册🔗 __](https://portal.qiniu.com/signup)开通 七牛开发者帐号
  2. 如果已有账号，直接登录七牛开发者后台，点击[这里🔗 __](https://portal.qiniu.com/user/key)查看 Access Key 和 Secret Key

# 文件上传

  * 上传流程
  * 客户端上传凭证
    * 简单上传凭证
    * 覆盖上传凭证
    * 自定义上传回复凭证
    * 带回调业务服务器的凭证
    * 带数据处理的凭证
    * 带自定义参数的凭证
    * 综合上传凭证
  * 服务器直传
    * 构建配置类
    * 文件上传（表单方式）
    * 字节数组上传（表单方式）
    * 数据流上传（表单方式）
    * 文件分片上传（断点续传）
    * 解析自定义回复内容
    * 业务服务器验证存储服务回调

## 上传流程

文件上传分为客户端上传（主要是指网页端和移动端等面向终端用户的场景）和服务端上传两种场景，具体可以参考文档[业务流程](https://developer.qiniu.com/kodo/manual/1205/programming-
model)。

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

## 客户端上传凭证

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

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

## 简单上传的凭证

最简单的上传凭证只需要`AccessKey`，`SecretKey`和`Bucket`就可以。

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

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

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

## 覆盖上传的凭证

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

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

## 自定义上传回复的凭证

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

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

有时候我们希望能自定义这个返回的JSON格式的内容，可以通过设置`returnBody`参数来实现，在`returnBody`中，我们可以使用七牛支持的[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)和[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)。

    
    
    bucket := "your bucket name"
    
    putPolicy := storage.PutPolicy{
    	Scope:      bucket,
    	ReturnBody: `{"key":"$(key)","hash":"$(etag)","fsize":$(fsize),"bucket":"$(bucket)","name":"$(x:name)"}`,
    }
    mac := auth.New(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 := auth.New(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 := auth.New(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 := auth.New(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 := auth.New(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    

队列 pipeline 请参阅[创建私有队列 __](https://portal.qiniu.com/dora/media-
gate/pipeline)；转码操作具体参数请参阅[音视频转码](https://developer.qiniu.com/dora/api/audio-
and-video-transcoding-avthumb)；saveas
请参阅[处理结果另存](https://developer.qiniu.com/dora/api/processing-results-save-
saveas)。

## 带自定义参数的凭证

存储支持客户端上传文件的时候定义一些自定义参数，这些参数可以在`returnBody`和`callbackBody`里面和七牛内置支持的魔法变量（即系统变量）通过相同的方式来引用。这些自定义的参数名称必须以`x:`开头。例如客户端上传的时候指定了自定义的参数`x:name`和`x:age`分别是`string`和`int`类型。那么可以通过下面的方式引用：

    
    
    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)}`,
    }
    

## 综合上传凭证

上面的生成上传凭证的方法，都是通过设置[上传策略🔗](https://developer.qiniu.com/kodo/manual/1206/put-
policy)相关的参数来支持的，这些参数可以通过不同的组合方式来满足不同的业务需求，可以灵活地组织你所需要的上传凭证。

## 服务端直传

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

## 构建配置类

存储支持空间创建在不同的机房，在使用七牛的 Go SDK
中的`FormUploader`和`ResumeUploaderV2`上传文件之前，必须要构建一个上传用的`Config`对象，在该对象中，可以指定空间对应的`Zone`(如不指定Zone则会使用自动判断区域)以及其他的一些影响上传的参数。

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

其中关于`Region`对象和机房的关系如下：

机房 | Region 对象  
---|---  
华东 | `storage.ZoneHuadong`  
华东浙江 2 区 | `storage.ZoneHuadongZheJiang2`  
华北 | `storage.ZoneHuabei`  
华南 | `storage.ZoneHuanan`  
北美 | `storage.ZoneBeimei`  
新加坡 | `storage.ZoneXinjiapo`  
亚太首尔 1 区 | `storage.ZoneShouEr1`  
  
## 文件上传（表单方式）

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

    
    
    localFile := "/Users/jemy/Documents/github.png"
    bucket := "if-pbl"
    key := "github-x.png"
    
    putPolicy := storage.PutPolicy{
    	Scope:               bucket,
    }
    mac := auth.New(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    
    cfg := storage.Config{}
    // 空间对应的机房
    cfg.Region = &storage.ZoneHuadong
    // 是否使用https域名
    cfg.UseHTTPS = true
    // 上传是否使用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 := auth.New(accessKey, secretKey)
    upToken := putPolicy.UploadToken(mac)
    
    cfg := storage.Config{}
    // 空间对应的机房
    cfg.Region = &storage.ZoneHuadong
    // 是否使用https域名
    cfg.UseHTTPS = true
    // 上传是否使用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`方法，使用方式和上述的 `字节数组上传` 方式相同。

## 文件分片上传

对于大的文件，我们一般推荐使用分片上传的方式来上传文件，[分片上传
v2](https://developer.qiniu.com/kodo/api/6364/multipartupload-
interface)通过将一个文件切割为块，然后通过上传块的方式来进行文件的上传。服务端SDK中默认块大小为4MB，支持根据条件设置块大小（要求除最后一块外，其他块大于等于1MB，小于等于1G），块与块之间可以并发上传，以提高上传效率。分片上传不等于断点续传，但是分片上传可以支持断点续传。

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

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

## 文件断点续传

断点续传是基于分片上传来实现的，基本原理就是用一个文本文件记录下上传的进度，如果上传中断，下次再从这个文件读取进度，继续上传未完成的分块。用户只需要传入Recorder参数即可实现断点续传，其他逻辑内部封装，实例如下：

    
    
    package main
    
    import (
    	"context"
    	"fmt"
    	"os"
    	
    	"github.com/qiniu/go-sdk/v7/auth"
    	"github.com/qiniu/go-sdk/v7/storage"
    )
    
    var (
    	accessKey = "your access key"
    	secretKey = "your secret key"
    )
    
    func main() {
    	localFile := "your local file path"
    	bucket := "your bucket name"
    	key := "your file save key"
    	putPolicy := storage.PutPolicy{
    		Scope: bucket,
    	}
    	mac := auth.New(accessKey, secretKey)
    	upToken := putPolicy.UploadToken(mac)
    	cfg := storage.Config{}
    
    	resumeUploader := storage.NewResumeUploaderV2(&cfg)
    	ret := storage.PutRet{}
    	recorder, err := storage.NewFileRecorder(os.TempDir())
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    
    	putExtra := storage.RputV2Extra{
    		Recorder: recorder,
    	}
    	err = resumeUploader.PutFile(context.Background(), &ret, upToken, key, localFile, &putExtra)
    	if err != nil {
    		fmt.Println(err)
    		return
    	}
    	fmt.Println(ret.Key, ret.Hash)
    }
    
    

## 解析自定义回复内容

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

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

## 业务服务器验证存储服务回调

在上传策略里面设置了上传回调相关参数的时候，七牛在文件上传到服务器之后，会主动地向`callbackUrl`发送POST请求的回调，回调的内容为`callbackBody`模版所定义的内容，如果这个模版里面引用了[魔法变量](https://developer.qiniu.com/kodo/manual/1235/vars#magicvar)或者[自定义变量](https://developer.qiniu.com/kodo/manual/1235/vars#xvar)，那么这些变量会被自动填充对应的值，然后在发送给业务服务器。

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

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

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

# 下载文件

  * 生成文件的下载 URL
    * 生成公开空间文件的下载 URL
    * 生成私有空间文件的下载 URL
  * 通过接口下载文件

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

## 生成文件的下载 URL

文件下载的 URL 有两种：公开空间文件的下载 URL 和私有空间文件的下载 URL，公开空间文件的下载 URL 不需要签名，私有空间文件的下载 URL
需要进行签名。

### 生成公开空间文件的下载 URL

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

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

### 生成私有空间文件下载的 URL

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

    
    
    mac := auth.New(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)
    

## 通过接口下载空间文件

通过接口可以直接下载文件，需要注意：

  * 使用源站域名进行下载需要 URL 签名配置，不配置下载域名时默认会使用源站域名进行下载，此时 SDK 内部会对 URL 进行签名
  * 私有空间需要 URL 签名配置

    
    
    key := "文件保存的 key.jpg"
    bucket := "对象所在的 bucket"
    bm := NewBucketManager(mac, &Config{})
    
    // err 和 resp 可能同时有值，当 err 有值时，下载是失败的，此时如果 resp 也有值可以通过 resp 获取响应状态码等其他信息
    resp, err := bm.Get(bucket, key, &GetObjectInput{
    	DownloadDomains: []string{
    		bucket 绑定的域名, // 当前仅支持配置一个，不配置时，使用源站域名进行下载，会对下载的 URL 进行签名
    	},
    	PresignUrl: true, // 下载 URL 是否进行签名，源站域名或者私有空间需要配置为 true
    	Range:      "bytes=2-5",   // 下载文件时 HTTP 请求的 Range 请求头
    })
    if err != nil || resp == nil {
        return
    }
    body, err := io.ReadAll(resp.Body)
    if err != nil {
    	return
    }
    

# 资源管理

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

  * 获取文件信息
  * 修改文件MimeType
  * 修改文件存储类型
  * 修改文件MetaData
  * 移动或重命名文件
  * 复制文件副本
  * 删除空间中的文件
  * 设置或更新文件生存时间
  * 获取指定前缀文件列表
  * 抓取网络资源到空间
  * 更新镜像存储空间中文件内容
  * 资源管理批量操作
    * 批量获取文件信息
    * 批量修改文件类型
    * 批量删除文件
    * 批量复制文件
    * 批量移动或重命名文件
    * 批量更新文件的有效期
    * 批量更新文件存储类型

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

    
    
    mac := auth.New(accessKey, secretKey)
    cfg := storage.Config{
    	// 是否使用https域名进行资源管理
    	UseHTTPS: true,
    }
    
    // 指定空间所在的区域，如果不指定将自动探测
    // 如果没有特殊需求，默认不需要指定
    //cfg.Region=&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
    }
    

## 修改文件MetaData

    
    
    bucket := "if-pbl"
    key := "github.png"
    
    // map 中为需要修改的 metas，只包含需要更改的 metas，可增加；
    //				 服务接口中 key 必须包含 x-qn-meta- 前缀，SDK 会对 metas 参数中的 key 进行检测;
    //				 - key 如果包含了 x-qn-meta- 前缀，则直接使用 key；
    //				 - key 如果不包含了 x-qn-meta- 前缀，则内部会为 key 拼接 x-qn-meta- 前缀。
    err = bucketManager.ChangeMeta(bucket, key, map[string]string{
    	"x-qn-meta-c": "x-qn-meta-cc-value", // 如果 x-qn-meta-c 存在，则将值修改为 x-qn-meta-cc-value，如果不存在则增加 x-qn-meta-c 键值对
    })
    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, hasNext, 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 hasNext {
    		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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            fmt.Printf("%s\n", ret.Data.Error)
        }
    }
    

## 批量修改文件类型

    
    
    //每个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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            fmt.Printf("%s\n", ret.Data.Error)
        }
    }
    

## 批量复制文件

    
    
    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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            fmt.Printf("%s\n", ret.Data.Error)
        }
    }
    

## 批量移动或重命名文件

    
    
    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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            fmt.Printf("%s\n", ret.Data.Error)
        }
    }
    

## 批量更新文件的有效期

    
    
    //每个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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            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 len(rets) == 0 {
        // 处理错误
        if e, ok := err.(*storage.ErrorInfo); ok {
            fmt.Printf("batch error, code:%s", e.Code)
        } else {
            fmt.Printf("batch error, %s", err)
        }
        return
    }
    
    // 返回 rets，先判断 rets 是否
    for _, ret := range rets {
        // 200 为成功
        if ret.Code == 200 {
            fmt.Printf("%d\n", ret.Code)
        } else {
            fmt.Printf("%s\n", ret.Data.Error)
        }
    }
    

# 持久化数据处理

## 发送数据处理请求

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

使用数据处理，首先需要构建一个`OperationManager`对象：

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

进行视频的处理示例：

    
    
    bucket := "if-pbl"
    key := "qiniu.mp4"
    mac := auth.New(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 := auth.New(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)
    	}
    }
    

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

具体算法可以参考：[时间戳防盗链](https://developer.qiniu.com/fusion/kb/timestamp-hotlinking-
prevention)

    
    
    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 参考

  * [存储 API 参考](https://developer.qiniu.com/kodo/api/3939/overview-of-the-api)
  * [融合CDN API 参考](https://developer.qiniu.com/fusion)
  * [官方数据处理 API 参考](https://developer.qiniu.com/dora)

# 常见问题

  * Go SDK的 err 保留了请求的错误信息，回复信息和头部信息，遇到问题时，可以都打印出来提交给我们排查问题。

    
    
    if err != nil {
    	if v, ok := err.(*rpc.ErrorInfo); ok {
    		fmt.Println(v.Code, v.RequestID, v.Err)
    	} else if v, ok := err.(*storage.ErrorInfo); ok {
    		fmt.Println(v.Code, v.Reqid, v.Err)
    	} else {
    		fmt.Println(err)
    	}
    }
    

  * API 的使用，可以参考我们为大家精心准备的[使用实例 __](https://github.com/qiniu/go-sdk/tree/master/examples)。

# 相关资源

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

  * [技术论坛 __](http://segmentfault.com/qiniu)\- 在这里您可以和其他开发者愉快的讨论如何更好的使用七牛云服务
  * [提交工单 __](https://support.qiniu.com/tickets/new)\- 如果您的问题不适合在论坛讨论或希望及时解决，您也可以提交一个工单，我们的技术支持人员会第一时间回复您
  * [博客 __](http://blog.qiniu.com)\- 这里会持续更新发布市场活动和技术分享文章
  * [微博 __](http://weibo.com/qiniutek)
  * [常见问题 __](https://support.qiniu.com/question)

# 贡献代码

  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 © 2020 qiniu.com

基于 MIT 协议发布:

  * [www.opensource.org/licenses/MIT __](http://www.opensource.org/licenses/MIT)

以上内容是否对您有帮助？