问题:
我准备实现一个点赞功能,如何实现点赞的增加和取消?
思考:
1.首先增加和取消所需要的限制条件是{一个用户 对 一个文章/评论 进行点赞操作}
2.我需要判断当前用户是否已经进行了点赞操作
3.我并不希望一个用户永远只能给一篇文章点赞一次。所以我可以给用户点赞记录做一个有效期:如一星期后,该用户还可以给该文章点赞
思路:
1.首先我并不想将点赞操作放入文章,评论中各写一份逻辑。所以我将点赞操作独自抽离出来。
type LikeService struct {}
2.我们需要获取用户ID 文章/评论ID 判断是文章还是评论
//点赞请求体
type AddLike struct {
Id uint `json:"id"`
UserId uint `json:"userId"`
IsArt bool `json:"isArt"` //判断是文章还是评论,方便存储到缓存中,增加不同前缀
}
3.我们使用redis缓存记录
对于点赞数:我们直接设置两个hash集合来区分文章和评论(ArtLikeNum,ComLikeNum)所以直接存储id就行。
对于点赞用户:我们本身就会创建很多set集,所以无法用上面hash的结构去存储和区分。所以我们使用key:id + 前缀art_ com_来区分文章和评论
- 每个文章的点赞数(会持久化)
- 使用hash存储 key:"art" or "com"(对应类型前缀) + id,val:点赞数
- 该文章点赞用户的ID集合(会过期)
- 使用set存储 key:"art" or "com"(对应类型前缀) + id, val : 用户Id
贴上点赞逻辑
package system
import (
"GOGOGO/global"
"GOGOGO/model/system"
systemReq "GOGOGO/model/system/request"
"bytes"
"context"
"go.uber.org/zap"
"gorm.io/gorm"
"strconv"
"time"
)
type LikeService struct {
/**
点赞的逻辑是:使用set或hash 存储文章/评论 的点赞数、和点赞用户id集合
点赞数:hashkey:ArtLikeNum , key:帖子id,val:点赞数 hash
点赞用户id:key:art前缀+帖子id,val:用户id set
对于点赞数:我们直接设置两个hash集合来区分文章和评论(ArtLikeNum,ComLikeNum)所以直接存储id就行。
点赞用户我们本身就会创建很多set集,所以无法用上面的结构去存储和区分。我们使用key:id + 前缀art_ com_来区分文章和评论
*/
}
var LikeServiceApp = new(LikeService)
//将点赞相关的操作分离出来
//点赞事件
func (*LikeService)LikeEvent (artReq systemReq.AddLike)(err error) {
//不想重复传参,思考:直接在LikeService添加artReq变量。但会不会有数据问题。待优化
//缓存中查询
ok := LikeServiceApp.isLike(artReq)
if ok{ //已经点赞,取消点赞
err := LikeServiceApp.cancelLike(artReq)
if err != nil {
global.G_LOG.Error("点赞取消失败",zap.Error(err))
return err
}
}else { //点赞
err := LikeServiceApp.addLike(artReq)
if err != nil {
global.G_LOG.Error("点赞失败",zap.Error(err))
return err
}
}
return nil
}
//获取点赞数据列表
func (*LikeService)GetLikeList(setKey string)(reslist []string,err error){
//setKey 是 存储用户列表的set集合
reslist,err = global.G_REDIS.SMembers(context.Background(),setKey).Result()
return reslist,err
}
//点赞
func (*LikeService)addLike(artReq systemReq.AddLike) error {
LikeServiceApp.likeSynch(artReq,1) //点赞数同步
setKey := LikeServiceApp.artorcom(artReq) //记录点赞用户的当前文章set
//在缓存中添加该用户点赞信息
return global.G_REDIS.SAdd(context.Background(), setKey, artReq.UserId).Err()
}
//取消点赞
func (*LikeService)cancelLike(artReq systemReq.AddLike)error {
LikeServiceApp.likeSynch(artReq,-1) //点赞数同步
setKey := LikeServiceApp.artorcom(artReq) //记录点赞用户的当前文章set
//在缓存中删除该用户点赞信息
return global.G_REDIS.SRem(context.Background(), setKey, artReq.UserId).Err()
}
//点赞信息同步数据库并获取点赞数 (点赞数操作)
func (*LikeService)likeSynch(artReq systemReq.AddLike,incr int64)string {
//防止重复点赞
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
key := strconv.Itoa(int(artReq.Id)) //hashname的key,记录点赞数
var mod interface{} //存储类型
var liketype string
if artReq.IsArt { //判断是文章还是评论
mod = &system.SysArticle{}
liketype = global.RedisC.ArtLikeNum
}else {
mod = &system.SysComment{}
liketype = global.RedisC.ComLikeNum
}
//读取点赞数信息,若没有,则数据库读取,并添加进redis
ok := global.G_REDIS.HExists(ctx,liketype, key).Val()
if ok{ //在缓存中,则直接更新缓存中点赞数,并同步
global.G_REDIS.HIncrBy(ctx,liketype, key, incr)
err := global.G_DB.Model(mod).Where("id = ?", artReq.Id).Update("like_num", gorm.Expr("like_num + ?", incr)).Error
if err != nil {
global.G_LOG.Error("同步点赞数据失败!", zap.Error(err))
}
return global.G_REDIS.HGet(ctx,liketype,key).Val()
}else {
var likeNum int64
//缓存中没有数据,从数据库中获取该 文章、评论点赞数,放入缓存中
err := global.G_DB.Model(mod).Select("like_num").Where("id = ?", artReq.Id).Find(likeNum).Error
if err != nil {
global.G_LOG.Error("获取点赞数据失败!", zap.Error(err))
}
//更新缓存
global.G_REDIS.HIncrBy(ctx,liketype,key,incr + likeNum)
//再同步数据库
err = global.G_DB.Model(mod).Where("id = ?", artReq.Id).Update("like_num", gorm.Expr("like_num + ?", incr)).Error
if err != nil {
global.G_LOG.Error("同步点赞数据失败!", zap.Error(err))
}
return strconv.FormatInt(likeNum, 10)
}
}
//获取点赞数
func (*LikeService)GetlikeNum(artReq systemReq.AddLike)string {
return LikeServiceApp.likeSynch(artReq,0)
}
//测试用户是否已经点过赞
func (*LikeService)isLike(artReq systemReq.AddLike)bool {
//若集合本身不存在,返回为0.所以不需要判断是否存在
return global.G_REDIS.SIsMember(context.Background(),LikeServiceApp.artorcom(artReq),artReq.UserId).Val()
}
//判断当前文章、评论缓存是否存在
//func (*LikeService)IsExit(artReq systemReq.AddLike)bool {
// global.G_REDIS.Keys(context.Background(),artorcom(artReq))
//}
//返回前缀字符串
func (*LikeService)artorcom(artReq systemReq.AddLike)string {
istype := artReq.IsArt
var bt bytes.Buffer
if istype{ //是文章,拼接字符串,下面写法是效率优化问题
bt.WriteString("art_")
bt.WriteString(strconv.Itoa(int(artReq.Id)))
return bt.String()
}else {
bt.WriteString("com_")
bt.WriteString(strconv.Itoa(int(artReq.Id)))
return bt.String()
}
}