faker-douyin-7. 获取视频流以及获取用户发布所有视频接口

158 阅读4分钟

新手Gopher,此前也不是从事开发工作的,因此码代码不是很规范,请大佬们谅解,最近也一直在学习,代码结构可能会经常变动。项目地址github.com/testerxiaod…,参考开源项目:github.com/HammerCloth…,个人博客地址:www.cengdong.top/

1. 解决上一次的债务

上一次写的上传视频接口,调用成功之后没有返回视频信息,不利于前端调用(虽然这个项目没有前端),此前videodao插入视频的函数签名是

func InsertTableVideo(title string, videoName string, imageName string, authorId uint64) error

调整之后的签名是

func InsertTableVideo(title string, videoName string, imageName string, authorId uint64) (entity.TableVideo, error)

整个项目的依赖链路大体是entity -> dao/dto -> service -> api,整个链路也需要相应调整,videoService接口中的方法成员

Publish(data *multipart.FileHeader, userId uint64, title string) (response.PublishVideoRes, error)

videoController.go中handler函数调整

func Publish(c *gin.Context) {
   // userId在jwt中间件中已被存入Context
   userId, err := strconv.ParseInt(c.GetString("userId"), 10, 64)
   if err != nil {
      log.Println("parse userId failed", err)
   }
   title := c.PostForm("title")
   file, err := c.FormFile("file")
   if err != nil {
      log.Println("服务端接收视频文件失败", err)
      common.FailWithMessage(err.Error(), c)
      return
   }
   vsi := service.VideoServiceImpl{}
   var video response.PublishVideoRes
   video, err = vsi.Publish(file, uint64(userId), title)
   if err != nil {
      common.FailWithMessage(err.Error(), c)
      return
   }
   common.OkWithDetailed(video, "upload success", c)
}

2. 输入输出模型定义

切换分支

git checkout main
git pull
git checkout -b feature/video-feed-list

model/dto/request/video.go文件中添加输入,这里VideoFeedReqLastTime字段定义成功common.LocalTime的自定义类型是因为,GinShouldBindJson方法解析不了time.Time类型,参考了一篇文章segmentfault.com/a/119000002…

package request

import (
   "faker-douyin/model/common"
)

type PublishVideoReq struct {
   Title string `json:"title" binding:"required"`
}

type VideoFeedReq struct {
   LastTime common.LocalTime `json:"last_time" binding:"required"`
}

type VideoListReq struct {
   UserId uint64 `json:"user_id" binding:"required"`
}

model/dto/response/video.go文件中添加

package response

import (
   "faker-douyin/model/entity"
   "time"
)

type PublishVideoRes struct {
   Video entity.TableVideo `json:"video"`
}

type VideoInfoRes struct {
   Video  entity.TableVideo
   Author UserInfoRes `json:"author"`
}

type VideoFeedRes struct {
   VideosInfo []VideoInfoRes `json:"videos_info"`
   LastTime   time.Time      `json:"last_time"`
}

type VideoListRes struct {
   VideosInfo []VideoInfoRes `json:"videos_info"`
}

3. videoService定义修改与实现

获取视频流以及获取用户视频列表需要获取用户信息(依赖用户服务)

type VideoServiceImpl struct {
   UserService
}

实现FeedList方法

func (v *VideoServiceImpl) Feed(lastTime time.Time) ([]response.VideoInfoRes, time.Time, error) {
   if v.UserService == nil {
      fmt.Println("VideoService is nil")
      return nil, time.Time{}, nil
   }
   videos := make([]response.VideoInfoRes, 0, global.VideoCount)
   tableVideos, err := dao.GetVideosByLastTime(lastTime)
   fmt.Println("feed videos: ", tableVideos)
   if err != nil {
      fmt.Println("dao.GetVideosByLastTime 失败", err)
      return videos, time.Time{}, err
   }
   if len(tableVideos) == 0 {
      return videos, time.Time{}, nil
   }
   for _, video := range tableVideos {
      videoAuthor, err := v.GetTableUserById(video.AuthorId)
      fmt.Println("UserService.GetTableUserById 成功", videoAuthor)
      if err != nil {
         fmt.Printf("UserServie.GetTableUserById 失败,user_id: %d", video.AuthorId)
         return videos, time.Time{}, err
      }
      var singleVideo response.VideoInfoRes
      singleVideo.Author = videoAuthor
      singleVideo.Video = video
      videos = append(videos, singleVideo)
   }
   return videos, tableVideos[len(videos)-1].CreatedAt, nil
}
func (v *VideoServiceImpl) List(userId uint64) ([]response.VideoInfoRes, error) {
   var userVideoList []response.VideoInfoRes
   if v.UserService == nil {
      return userVideoList, nil
   }
   videos, err := dao.GetVideosByAuthorId(userId)
   if err != nil {
      fmt.Println("dao.GetVideosByAuthorId 失败,user_id:", userId)
      return userVideoList, err
   }
   if len(videos) == 0 {
      return userVideoList, nil
   }
   for _, video := range videos {
      var userVideoInfo response.VideoInfoRes
      userVideoInfo.Video = video
      user, err := v.UserService.GetTableUserById(video.AuthorId)
      if err != nil {
         fmt.Println("UserService.GetTableUserById 失败,user_id:", video.AuthorId)
         return userVideoList, err
      }
      userVideoInfo.Author = user
      userVideoList = append(userVideoList, userVideoInfo)
   }
   return userVideoList, nil
}

/model/common目录下添加time.go文件

package common

import (
   "database/sql/driver"
   "time"
)

const TimeFormat = "2006-01-02 15:04:05.000"

type LocalTime time.Time

func (t *LocalTime) UnmarshalJSON(data []byte) (err error) {
   if len(data) == 2 {
      *t = LocalTime(time.Time{})
      return
   }

   now, err := time.Parse(`"`+TimeFormat+`"`, string(data))
   *t = LocalTime(now)
   return
}

func (t LocalTime) MarshalJSON() ([]byte, error) {
   b := make([]byte, 0, len(TimeFormat)+2)
   b = append(b, '"')
   b = time.Time(t).AppendFormat(b, TimeFormat)
   b = append(b, '"')
   return b, nil
}

func (t LocalTime) Value() (driver.Value, error) {
   if t.String() == "0001-01-01 00:00:00" {
      return nil, nil
   }
   return []byte(time.Time(t).Format(TimeFormat)), nil
}

func (t *LocalTime) Scan(v interface{}) error {
   tTime, _ := time.Parse("2006-01-02 15:04:05 +0800 CST", v.(time.Time).String())
   *t = LocalTime(tTime)
   return nil
}

func (t LocalTime) String() string {
   return time.Time(t).Format(TimeFormat)
}

4. 定义视频流获取以及用户发布视频列表Handler

func Feed(c *gin.Context) {
   var videoFeedReq request.VideoFeedReq
   err := c.ShouldBindJSON(&videoFeedReq)
   if err != nil {
      log.Println("绑定参数失败")
      common.FailWithMessage(err.Error(), c)
      return
   }
   vsi := service.VideoServiceImpl{UserService: service.UserServiceImpl{}}
   videoInfo, lastTime, err := vsi.Feed(time.Time(videoFeedReq.LastTime))
   if err != nil {
      common.FailWithMessage(err.Error(), c)
      return
   }
   common.OkWithData(response.VideoFeedRes{VideosInfo: videoInfo, LastTime: lastTime}, c)
}
func List(c *gin.Context) {
   var VideoListReq request.VideoListReq
   err := c.ShouldBindJSON(&VideoListReq)
   if err != nil {
      fmt.Println("参数绑定失败")
      common.FailWithMessage(err.Error(), c)
      return
   }
   vsi := service.VideoServiceImpl{UserService: service.UserServiceImpl{}}
   videoList, err := vsi.List(VideoListReq.UserId)
   if err != nil {
      fmt.Println("VideoService.List 失败,user_id:", VideoListReq.UserId)
      common.FailWithMessage(err.Error(), c)
      return
   }
   common.OkWithData(response.VideoListRes{VideosInfo: videoList}, c)
}

5. 注册路由

apiRouter.GET("/video/feed/", v1.Feed)
apiRouter.GET("/video/publish/list/", v1.List)

6. 测试

  • 视频流

五条视频(限制最多五条)

WechatIMG63.jpeg 零条视频

WechatIMG64.jpeg

三条视频

WechatIMG65.jpeg

  • 视频列表

WechatIMG66.jpeg

7. 提交代码

git add .
git commit -m "video feed and user video list"
git push --set-upsream origin feature/video-feed-list

8. 遇到的问题

  • 空指针异常

videoServiceImpl.go文件里,我在实现FeedList函数时,调用userService的函数,但是我在初始化videoServiceImpl时没有初始化UserService,初始化之后问题解决,并加入下面代码

if v.UserService == nil { 
    fmt.Println("VideoService is nil") return nil, time.Time{}, nil 
}
  • time.Time解析失败

前端的入参是2006-01-02 15:04:05.000格式,但是Gin还是报错,原因以及参考解决方法在这篇文章中:segmentfault.com/a/119000002…,其实还有更好的解决办法,就是前后端的传参都用timestamp格式,这样参数解析都能统一