对象存储 | 青训营笔记

150 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天

在青训营大项目的开发中,会有需要提供一个静态文件地址的需求。例如提供视频、封面的http直链。一般情况下我们虽然可以使用Nginx或者其他类似的http服务器直接提供静态文件服务。但是这样提供的服务,视频、图片等静态资源会受到该服务器上传网速的影响, 造成访问缓慢的问题。

因此,我们可以使用对象存储服务,专门存储此类静态文件。

服务商

对象存储有很多主流云计算服务商都有提供,这里我们使用的是腾讯云的COS服务。

注册好服务商账号之后,开通COS服务,创建存储桶,获取cos地址和appid以及密钥。

SDK

我们可以使用服务商提供的SDK来快速构建工具函数,提供文件的上传,下载服务。

这里我们使用的腾讯云的cos的sdk

go get github.com/tencentyun/cos-go-sdk-v5

代码编写

首先新建一个文件,存储好存储桶地址、appid、secret,并定义*cos.client变量,它是cos上传的客户端类。

var (
   client    *cos.Client
   addr      string
   secretId  string
   secretKey string
)

接下来,通过cos.NewClient来创建cos客户端,并将其赋值给client变量

// GetClient 返回cos client
func GetClient() *cos.Client {
   u, _ := url.Parse(addr)
   b := &cos.BaseURL{BucketURL: u}
   client := cos.NewClient(b, &http.Client{
      Transport: &cos.AuthorizationTransport{
         SecretID:  secretId,  // 用户的 SecretId
         SecretKey: secretKey, // 用户的 secretKey
      },
   })
   return client
}

上传

通过client.Object.Put函数,将io.Reader中的文件内容上传到服务器,其中key是文件上传的目录和文件名。

// UploadFile 上传文件
func UploadFile(key string, file io.Reader) error {
   _, err := client.Object.Put(
      context.Background(), key, file, nil,
   )
   return err
}

访问URL

要访问对象存储中的文件,可以使用签名URL直接访问。签名URL都有有效时间,超过有效时间就需要重新签名。

ctx := context.Background()
presignedUrl, err := client.Object.GetPresignedURL(ctx, http.MethodGet, key, secretId, secretKey, time.Hour, nil)

其中上面的time.Hour就代表了签名的有效时间。

但是如果每次需要获取url的时候,都要重新签名,就会很浪费系统资源。因为每一个url都可以使用1小时,这时我们可以配合redis做一个缓存,示例代码如下:

// GetSignUrl 返回预签名Url
func GetSignUrl(key string) string {
   ctx := context.Background()
   redisConn := models.GetRedis()
   defer redisConn.Close()
   redisKey := common.RedisPrefixCos + common.RedisKeySplit + key
   signedUrl, err := redis.String(redisConn.Do("GET", redisKey))
   if err == nil && signedUrl != "" {
      return signedUrl
   }
   presignedUrl, err := client.Object.GetPresignedURL(ctx, http.MethodGet, key, secretId, secretKey, time.Hour, nil)
   if err != nil {
      log.Println(err)
      return ""
   }
   err = redisConn.Send("SET", redisKey, presignedUrl.String())
   if err != nil {
      log.Println(err)
      return ""
   }
   err = redisConn.Send("EXPIRE", redisKey, 60*60-1)
   if err != nil {
      log.Println(err)
      return ""
   }
   _, err = redisConn.Do("")
   if err != nil {
      log.Println(err)
      return ""
   }
   redisConn.Close()
   return presignedUrl.String()
}

我们将这个文件的路径(key),存储到Redis中的某个字段下,这样每次要获取签名的时候,都优先访问Redis查找是否已经有签名的URL,如果有URL就直接返回。

如果没有或者已经过期,就重新签名,并且将新的url存储到redis中,并设置过期时间为3599秒(因为1小时是3600秒,少一秒防止拿到的时候就是过期的)。