这是我参与「第三届青训营 -后端场」笔记创作活动的的第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进行。
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 string
、data io.Reader
、size int64
。
-
参数
key
是要上传的文件访问路径此处的访问路径意思为在云空间的访问路径,通过该访问路径,可以在云空间中找到该视频。
-
参数
data
是文件内容的访问接口(重点)因为
data
是io.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层存储
测试
视频封面
此处我们将使用视频单帧缩略图(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))
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()上传成功") //本行供测试使用
}()