这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
视频投稿
需求
登录用户可以投稿发布视频,自定义视频标题,系统自动生成视频封面。
-
基本流程
-
- 用户点击“+”进入视频发布页面,填写描述信息,选择上传视频,点击发布。后端接收请求,开始业务逻辑。
- rpc调用VideoService,利用对象存储将视频、视频封面上传到minIO服务器,利用ffmpeg截取视频的第一帧作为封面,同时将user_id、视频url、封面url写入mysql数据库。
- 后端回复前端上传成功。
业务流程图
核心代码
采用单例模式作为设计模式,在rpcService中的handler进行实例化。
/*
/cmd/video/service/publish_video.go
*/
type PublishVideoService struct {
ctx context.Context
}
func NewPublishVideoService(ctx context.Context) *PublishVideoService {
return &PublishVideoService{ctx: ctx}
}
func (p *PublishVideoService) PublishVideo(req *douyinvideo.DouyinPublishActionRequest) error {
/*
1.首先将传过来的数据写入到minio
2.将minio中视频的url写入到数据库
3.制作视频封面并同样上传至minio、写入url至数据库
*/
client, err := minio.New(
consts.Endpoint,
consts.AccessKeyID,
consts.SecretAccessKey,
consts.UseSSL,
)
if err != nil {
log.Print(err)
return err
}
//视频的命名方式为: user_id + title
n, err := client.PutObjectWithContext(
p.ctx,
consts.BucketName,
req.Token+"_"+req.Title+".mp4",
bytes.NewBuffer(req.Data),
int64(len(req.Data)),
minio.PutObjectOptions{ContentType: "video/mp4"},
)
if err != nil {
log.Print("上传失败")
log.Print(err)
return err
}
//记录上传到服务器的时间
t := time.Now().Unix()
log.Printf("upload to minio %s of size %d", req.Token+req.Title, n)
fmt.Println("object name:", req.Token+"_"+req.Title+".mp4")
err = client.FGetObjectWithContext(
p.ctx,
consts.BucketName,
req.Token+"_"+req.Title+".mp4",
consts.TempVideoFilePath+req.Token+"_"+req.Title+".mp4",
minio.GetObjectOptions{},
)
if err != nil {
log.Print("下载失败:", err)
return err
}
//利用ffmpeg制作视频封面
imgPath, err := ffmpegPicture(consts.TempVideoFilePath + req.Token + "_" + req.Title)
if err != nil {
log.Print(err)
return err
}
fmt.Println("截图成功: ", imgPath)
picObject, err := client.FPutObjectWithContext(
p.ctx,
consts.BucketName,
req.Token+"_"+req.Title+".png",
imgPath,
minio.PutObjectOptions{ContentType: "image/png"},
)
if err != nil {
log.Print("上传截图失败:", err)
return err
}
fmt.Printf("Successfully uploaded %s of size %d\n", req.Token+req.Title+".png", picObject)
err = os.Remove(consts.TempVideoFilePath + req.Token + "_" + req.Title + ".mp4")
if err != nil {
log.Print("删除本地视频文件失败:", err)
return err
}
err = os.Remove(consts.TempVideoFilePath + req.Token + "_" + req.Title + ".png")
if err != nil {
log.Print("删除本地视频截图失败:", err)
return err
}
//将相关信息写入数据库
var video = douyinvideo.Video{
Id: 0,
Author: nil,
PlayUrl: consts.HttpTemplate + consts.LocalIp + consts.MinioPort + "/" + consts.BucketName + "/" + req.Token + "_" + req.Title + ".mp4",
CoverUrl: consts.HttpTemplate + consts.LocalIp + consts.MinioPort + "/" + consts.BucketName + "/" + req.Token + "_" + req.Title + ".png",
FavoriteCount: 0,
CommentCount: 0,
IsFavorite: false,
Title: req.Title,
}
token, _ := strconv.Atoi(req.Token)
err = db.InsertVideo(p.ctx, &video, token, t)
if err != nil {
log.Print("insert video failed: ", err)
return err
}
return nil
}
这一部分采用了minio分布式对象存储,并且运用ffmpeg对视频帧进行截取,制作视频封面。