这是我参与「第五届青训营 」伴学笔记创作活动的第 14 天
互动接口
小伙伴也写了很多。这里我写了favorite相关的部分。
点赞
app提供了点赞和撤销点赞两个接口。所以我们也需要实现这两个方法。从数据库开始:
表结构定义,采用双主键:
type Favorite struct {
UserId int64 `gorm:"primary_key;autoIncrement:false" json:"user_id,omitempty"`
VideoId int64 `gorm:"primary_key;autoIncrement:false" json:"video_id,omitempty"`
CreatedAt int64 `json:"created_at,omitempty"`
UpdatedAt int64 `json:"updated_at,omitempty"`
}
gorm的软删除比较坑,这里直接删了不用软删除。
新Favorite的建立部分,这里其实是一个factory了。
type FavouriteOption func(f *Favorite)
func SetUserID(id int64) FavouriteOption {
return func(f *Favorite) {
f.UserId = id
}
}
func SetVideoID(id int64) FavouriteOption {
return func(f *Favorite) {
f.VideoId = id
}
}
func NewFavourite(option ...FavouriteOption) *Favorite {
f := &Favorite{
UserId: 0,
VideoId: 0,
}
for _, opt := range option {
opt(f)
}
return f
}
DAO建立:
type FavoriteDao struct {
db *gorm.DB
}
var (
favoriteDao *FavoriteDao
once sync.Once
)
func NewFavouriteInstance() *FavoriteDao {
once.Do(func() {
favoriteDao = &FavoriteDao{
db: db,
}
})
return favoriteDao
}
看看需求,需要用到favorite表的地方有:点赞操作,取消点赞操作。获取喜欢列表的操作。
这边就可以提供三个接口,一个是Create对应点赞,Delete对应取消点赞,ListFavoirte对应喜欢列表操作。这里我们是使用userID作为list的条件。我还建了一个videoId的版本,但好像没用上:
func (dao *FavoriteDao) Create(f *Favorite) error {
return dao.db.Save(f).Error
}
func (dao *FavoriteDao) Delete(f *Favorite) error {
return dao.db.Delete(f).Error
}
func (dao *FavoriteDao) ListFavouriteByUserId(userId int64) (*[]Favorite, error) {
var f []Favorite
result := dao.db.Where("user_id = ?", userId).Find(&f)
return &f, result.Error
}
func (dao *FavoriteDao) ListFavouriteByVideoId(videoId int64) (*[]Favorite, error) {
var f []Favorite
result := dao.db.Where("video_id = ?", videoId).Find(&f)
return &f, result.Error
}
随后封装服务。我们的服务很简单,甚至你都可以不写服务之间用这几个接口。但这里存在一个问题,我们需要与其他表进行交互,这里要用到事务。
首先定义了两个常量,来表示哪个是删除哪个是增加:
var (
ACTION_CREATE int = 1
ACTION_DEL int = -1
)
我们的favorite会在video表和user表上都做修改,也就是我们的增加或者删除操作都涉及到了三个表。最近才看到gorm好像可以之间使用引用实体,可以省去这些麻烦事了,但是我们的框架已经定好了,也就不改了。
func TransacFavorRecordUpdateFavorCount(f *Favorite, userid int64, videoid int64, action int) error {
err := db.Transaction(func(tx *gorm.DB) error {
switch action {
case ACTION_CREATE:
if e := tx.Model(f).Save(f).Error; e != nil {
return e
}
case ACTION_DEL:
if e := tx.Model(f).Delete(f).Error; e != nil {
return e
}
}
if e := tx.Model(&User{Id: userid}).UpdateColumn("favorite_count", gorm.Expr("favorite_count + ?", action)).Error; e != nil {
return e
}
if e := tx.Model(&Video{Id: videoid}).UpdateColumn("favorite_count", gorm.Expr("favorite_count + ?", action)).Error; e != nil {
return e
}
return nil
})
return err
}
完成后开始封装服务:
func Do(f *repository.Favorite, userid int64, videoid int64) error {
return repository.TransacFavorRecordUpdateFavorCount(f, userid, videoid, repository.ACTION_CREATE)
// return repository.NewFavouriteInstance().Create(f)
}
func Undo(f *repository.Favorite, userid int64, videoid int64) error {
return repository.TransacFavorRecordUpdateFavorCount(f, userid, videoid, repository.ACTION_DEL)
// return repository.NewFavouriteInstance().Delete(f)
}
func ListByUserId(userId int64) (*[]repository.Favorite, error) {
return repository.NewFavouriteInstance().ListFavouriteByUserId(userId)
}
func ListByVideoId(videoId int64) (*[]repository.Favorite, error) {
return repository.NewFavouriteInstance().ListFavouriteByVideoId(videoId)
}
服务完成后,开始重构handle的处理流程。
对于点击操作:
func FavoriteAction(c *gin.Context) {
var err error
id := c.GetInt64("UserID")
// are there need a video legal verify?
video, _ := strconv.ParseInt(c.Query("video_id"), 10, 64)
favour := repository.Favorite{UserId: id, VideoId: video}
actioType := c.Query("action_type")
switch actioType {
case "1":
err = service_favor.Do(&favour, id, video)
case "2":
err = service_favor.Undo(&favour, id, video)
}
if err != nil {
log.Printf("[WARN] Favourite action on User[id: %d] faild, ERR: %s", id, err)
c.JSON(http.StatusOK, Response{StatusCode: 2, StatusMsg: "Action faild."})
}
c.JSON(http.StatusOK, Response{StatusCode: 0, StatusMsg: "Success"})
}
这边其实已经用上jwt中间件了,所以不需要再做用户的校验了(所以这里你就看看剩下的逻辑吧,如果你没有校验的中间件,还是要做用户校验的)。
我们取得发起这个动作的用户id和目标视频id。获取操作类型,是删除还是创建。
如果是创建就DO如果删除就UnDo。
随后返回是否成功即可。
List部分有点长,这里拆开来看吧。
这里的Favoritelist也包含请求他人的favoritelist,所以不能之间从上下文中来拿userid了,而是要看query中的userid。
id, err = strconv.ParseInt(c.Query("user_id"), 10, 64)
if err != nil {
c.JSON(http.StatusOK, UserResponse{
Response: Response{
StatusCode: 1,
StatusMsg: "Invaild user id",
},
})
}
通过id拉facorite列表。
favors, err := service_favor.ListByUserId(id)
if err != nil {
log.Printf("[WARN] User[id: %d] Fetch favourite list faild, ERR: %s.", id, err)
c.JSON(http.StatusOK, VideoListResponse{
Response: Response{
StatusCode: 3,
StatusMsg: "Fetch favorite list faild.",
},
VideoList: nil,
})
return
}
从返回的favor中拿videoID然后拉videolist。
videoIds := make([]int64, 0, len(*favors))
for _, f := range *favors {
videoIds = append(videoIds, f.VideoId)
}
videos, err := service_video.GetVideoBySet(videoIds)
if err != nil {
log.Printf("[WARN] User[id: %d] Fetch favourite list faild, ERR: %s.", id, err)
c.JSON(http.StatusOK, VideoListResponse{
Response: Response{
StatusCode: 3,
StatusMsg: "Fetch favorite list faild.",
},
VideoList: nil,
})
return
}
将得到的video转换为可接口指定的类型返回即可。
var respVideoList []Video
for _, video := range *videos {
respVideoList = append(respVideoList, *RepoVideoToCon(&video))
}
c.JSON(http.StatusOK, VideoListResponse{
Response: Response{
StatusCode: 0,
},
VideoList: respVideoList,
})
favorite结束。