在Gin中上传视频到七牛云 | 青训营笔记

739 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记。

简介

我们小组选择抖音项目,我负责视频方面功能的开发。本次笔记分享如何上传视频到七牛云。

关键词:Gin 七牛云

controller层

func Publish(c *gin.Context)

我们在controller层只干三件事:获取参数、业务处理、返回响应。

1. 获取参数

因为我们要进行的是文件的上传,所以获取的参数就是文件。

file, err := c.FormFile("data")

此处请注意file的类型为*multipart.FileHeader

2. 业务处理

这里我们直接调用service层进行视频的上传。

err = service.UploadVideo(file)

3. 返回响应

根据与前端的约定返回响应。

service层(重点)

func UploadVideo(file *multipart.FileHeader) (err error)

注意UploadVideo()函数的参数file的类型应该为*multipart.FileHeader

1. 获取文件的后缀名

filename := file.Filename //获取文件名
indexOfDot := strings.LastIndex(filename, ".") //获取文件后缀名前的.的位置
if indexOfDot < 0 {
   return errors.New("没有获取到文件的后缀名")
}
suffix := filename[indexOfDot+1 : len(filename)] //获取后缀名
suffix = strings.ToLower(suffix) //后缀名统一小写处理

strings.LastIndex(s string, str string) int判断str在s中最后出现的位置,如果没有出现,则返回-1。

strings.ToLower(str string)string转为⼩写。

2. 判断文件是否符合视频的格式

if !util.IsVideoAllowed(suffix) {
   return errors.New("上传的文件不符合视频的格式")
}

此处的IsVideoAllowed()函数写在了util包中。

var videoFileExt = []string{"mp4", "flv"} //此处可根据需要添加格式

func IsVideoAllowed(suffix string) bool {
   for _, fileExt := range videoFileExt {
      if suffix == fileExt {
         return true
      }
   }
   return false
}

3. 生成新的文件名

filename = strconv.FormatInt(snowflake.GenID(), 10)
filename = filename + "." + suffix

这里我使用了雪花算法为视频生成了一个新的文件名,大家也可以通过其他随机生成算法生成新的文件名。别忘了要把文件的后缀名加上。

为什么要有这一步?

因为视频上传七牛云 公开空间 后通过 外链域名+"/"+文件路径 进行访问。

文件路径为 目录名+"/"+文件名。

所以此处的filename后面要用来拼接文件路径,在云空间中定位视频文件。

我们不能确保用户上传的文件的名字是没有重复的,所以我们要自己重命名这些视频文件。

访问控制

4. 上传视频到七牛云(重点)

Go SDK

业务流程

请看一下Go SDK中有关文件上传的部分,鉴权等操作也请按照Go SDK进行。

data, err := file.Open()
folderName := "video"
key := folderName + "/" + filename
err = formUploader.Put(context.Background(), &ret, upToken, key, data, file.Size, &putExtra)

formUploader.Put()是上传的关键函数,该函数定义如下。

func (p *FormUploader) Put(
   ctx context.Context, ret interface{}, uptoken, key string, data io.Reader, size int64, extra *PutExtra) (err error)

其中需要我们注意的参数是key stringdata io.Readersize int64

  • 参数key要上传的文件访问路径

    此处的访问路径意思为在云空间的访问路径,通过该访问路径,可以在云空间中找到该视频。

  • 参数data文件内容的访问接口(重点)

    因为dataio.Reader类型,所以我们不能直接把*multipart.FileHeader类型的file传入需要io.Reader类型参数的formUploader.Put函数。 所以在调用formUploader.Put()函数前,需要使用data, err := file.Open()进行类型转换,随后传入data

  • 参数size要上传的文件大小

使用formUploader.Put()后就算是成功上传了,此后我们就可以通过外链域名+"/"+key对该视频文件进行访问。

举个例子:XXX4X2XXX.XX-XXX.clouddn.com/video/1899688888888888.mp4

外链域名为XXX4X2XXX.XX-XXX.clouddn.com的云空间下的video文件夹下的1899688888888888.mp4就是我们刚才上传的视频,而我们可以通过浏览器从http://XXX4X2XXX.XX-XXX.clouddn.com/video/1899688888888888.mp4直接访问到该视频。

5. 其他操作

6. 调用DAO层存储

测试

postman上传文件测试 - _Brenda - 博客园

视频封面

此处我们将使用视频单帧缩略图(vframe)中的持久化处理获得视频封面。

说人话就是在上传视频的同时,将视频中的某一秒截图,存储在七牛云空间中。

1. 给封面命名

为了方便管理,视频名称和封面名称相同(扩展名不同,存储的文件夹不同)。

newFilename := strconv.FormatInt(snowflake.GenID(), 10) //使用雪花算法
videoName := newFilename + "." + suffix                 //视频名
coverName := newFilename + "." + "jpg"                  //封面名

2. 生成EncodedEntryURI

请先阅读数据格式(很短)

coverFolderName := "cover"                    //七牛云中存放图片的目录名。用于与文件名拼接,组成文件路径
photoKey := coverFolderName + "/" + coverName //封面的访问路径,我们通过此路径在七牛云空间中定位封面
entry := viper.GetString("qiniuyun.bucket") + ":" + photoKey
encodedEntryURI := base64.StdEncoding.EncodeToString([]byte(entry))

base64

3. 上传策略(PutPolicy)

请看上传策略中的persistentOps 详解部分中的使用指定的存储空间和资源名

putPolicy.PersistentOps = "vframe/jpg/offset/1|saveas/" + encodedEntryURI //取视频第1秒的截图

数据处理命令vframe/jpg/offset/1意思是取视频第1秒的截图。

接口规格

4. 总结

上传策略中加了一个persistentOps配置,资源上传时自动触发,根据配置参数进行处理。

上传成功后,外链域名为XXX4X2XXX.XX-XXX.clouddn.com的云空间下的cover文件夹下的1899688888888888.jpg就是我们刚才上传的视频的第一秒的截图,而我们可以通过浏览器从http://XXX4X2XXX.XX-XXX.clouddn.com/cover/1899688888888888.jpg直接访问到该图片。

上传优化

//起一个协程实现上传的异步
go func() {
   err := formUploader.Put(context.Background(), &ret, upToken,
      key, data, file.Size, &putExtra)
   if err != nil {
      //问题:如果此处出现了问题导致上传失败,前端显示的也是上传成功。err信息没办法及时返回给controller
      fmt.Println("formUploader.Put()上传失败,错误信息:", err.Error())
      return
   }
   fmt.Println("formUploader.Put()上传成功") //本行供测试使用
}()