好友关注涉及到取Set的增加、删除、取交集,Feed流推送
关注和取关
查看博客或则个人主页时候都会有关注/取消关注按钮,取决于用户是否关注了该博主。因此查看博客时除了请求加载博客内容,还需要发送请求获取关注状态。
已关注 | 未关注 |
---|---|
![]() | ![]() |
获取关注状态,这里可以直接从redis的set中获取 |
user := utils.GetUser(h.Context).GetID()
//查找是否关注
if !errors.Is(redis.RedisClient.SIsMember(h.Context, constants.FOLLOW_USER_KEY+strconv.FormatInt(user, 10), targetUserID).Err(), nil) {
return &follow.IsFollowedResp{IsFollowed: false}, nil
}
return &follow.IsFollowedResp{IsFollowed: true}, nil
未关注时点击则关注,否则取消关注
myID := utils.GetUser(h.Context).GetID()
isFollow := req.GetIsFollow()
targetUserId := req.GetTargetUser()
f := follow.Follow{
UserId: myID,
FollowUserId: targetUserId,
}
// 如果是true,则添加关注,将用户id和被关注用户的id存入数据库
if isFollow {
// 判断是否已经关注
if !errors.Is(redis.RedisClient.SIsMember(h.Context, constants.FOLLOW_USER_KEY+strconv.FormatInt(myID, 10), targetUserId).Err(), nil) {
return &follow.FollowResp{RespBody: &f}, nil
}
// 将关注的用户存入redis的set中
if !errors.Is(redis.RedisClient.SAdd(h.Context, constants.FOLLOW_USER_KEY+strconv.FormatInt(myID, 10), targetUserId).Err(), nil) {
hlog.CtxErrorf(h.Context, "err = %s", err.Error())
return nil, err
}
if !errors.Is(mysql.DB.Create(&f).Error, nil) {
return nil, errors.New("关注失败")
}
return &follow.FollowResp{RespBody: &f}, nil
}
// 如果是false,则取消关注
if !errors.Is(mysql.DB.Where("user_id = ? and follow_user_id = ?", myID, targetUserId).Delete(&f).Error, nil) {
return nil, errors.New("取消关注失败")
}
// 将取消关注的用户从redis的set中删除
if !errors.Is(redis.RedisClient.SRem(h.Context, constants.FOLLOW_USER_KEY+strconv.FormatInt(myID, 10), targetUserId).Err(), nil) {
hlog.CtxErrorf(h.Context, "err = %s", err.Error())
return nil, err
}
return &follow.FollowResp{RespBody: &f}, nil
共同关注
这里是再用户主页查看你们共同关注的用户,用到set求交集来完成
- 步骤一:再关注时候除了将关注的用户存到数据库,还需要存入redis的set中(实现方法查看上一届节)
- 获取时先从redis用双方的关注列表找出共同的用户(set取交集)
- 从数据库查询交集用户的信息并转换未uderDTO
user := utils.GetUser(h.Context).GetID()
key1 := constants.FOLLOW_USER_KEY + strconv.FormatInt(user, 10)
key2 := constants.FOLLOW_USER_KEY + targetUserID
arr, err := redis.RedisClient.SInter(h.Context, key1, key2).Result()
if err != nil {
return nil, err
}
var users []*model.User
if !errors.Is(mysql.DB.Where("id in ?", arr).Find(&users).Error, nil) {
return nil, errors.New("查询失败")
}
var userDto []*model.UserDTO
// 遍历arr,转换为userDTO
for _, u := range users {
d := utils.UserToUserDTO(u)
userDto = append(userDto, d)
}
return &follow.CommonFollowResp{
CommonFollows: userDto,
}, nil
Feed流推送
Feed流
- Feed 流:是提供给用户的内容流,为用户持续的提供 “沉浸式” 的体验,通过无限下拉刷新获取新的信息。比如微博的关注页,抖音的关注页视频都叫Feed流
- Feed:Feed流中的一条信息,比如朋友发布的一条朋友圈
本项目中Feed流用再个人主页中,查看关注的用户发布的博客
关于Feed流的这里不做赘述,可以查看redis实现Feed流推送
在本文中采取推模式作为案例。redis中实现feed流需要使用zset,当博主发布一条动态时往粉丝的收件箱(redis的zset)写一条数据。数据格式为:
{
score:一般为时间戳,
member:消息内容
}
具体实现代码
将消息写入粉丝收信箱
fans, err := mysql.GetFansByID(h.Context, u)
if err != nil {
return nil, err
}
for _, fan := range fans {
key := constants.FEED_KEY + strconv.FormatInt(fan.ID, 10)
err = redis.RedisClient.ZAdd(h.Context, key, &redis2.Z{
Score: float64(time.Now().Unix()),
Member: req.ID,
}).Err()
}
粉丝读取收信箱,需要注意的是,由于Feed流中的数据是随时间变化不断更新的,传统的分页方式为根据每页几条pageSize和当前第几页页Page来计算查询范围,这对于Feed流中的动态列表而言会有重复读的问题,应当采用滚动分页模式。
u := utils.GetUser(h.Context).GetID()
key := constants.FEED_KEY + strconv.FormatInt(u, 10)
zSet, err := redis.GetBlogsByKey(h.Context, key, req.LastId, req.Offset)
var bids []string
for _, z := range zSet {
bids = append(bids, z.Member.(string))
}
var blogs []*blog.Blog
err = mysql.DB.Where("id in ?", bids).Find(&blogs).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, errors.New("没有更多数据")
}
if err != nil {
return nil, err
}
//fmt.Printf("blogs: %v\n", blogs)
var res blog.FollowBlogRresp
res.List = blogs
res.MinTime = "0"
if len(zSet) > 0 {
res.MinTime = strconv.FormatInt(int64(zSet[len(zSet)-1].Score), 10)
}
// 取最小分数的记录数
var offset int64 = 0
minScore := zSet[len(zSet)-1].Score
for _, element := range zSet {
if element.Score == minScore {
offset++
}
}
res.Offset = offset