新手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
文件中添加输入,这里VideoFeedReq
的LastTime
字段定义成功common.LocalTime
的自定义类型是因为,Gin
的ShouldBindJson
方法解析不了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
}
实现Feed
与List
方法
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. 测试
- 视频流
五条视频(限制最多五条)
零条视频
三条视频
- 视频列表
7. 提交代码
git add .
git commit -m "video feed and user video list"
git push --set-upsream origin feature/video-feed-list
8. 遇到的问题
- 空指针异常
在videoServiceImpl.go
文件里,我在实现Feed
与List
函数时,调用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
格式,这样参数解析都能统一