点评项目——好友关注模块

2023.12.12

        本章实现好友关注模块,包含以下功能的实现:关注与取关、共同关注、关注推送。

关注与取关        

        当点击某个用户的主页时,会调用如下接口:

        该接口是用来判断是否已经关注该用户,最后一个参数是该用户的id。

        在我们点击关注之后,又会调用一个接口:

        该接口就是用来实现关注或者取关的操作的,倒数第二个参数是该用户的id,最后一个参数是当前是否已经关注该用户,根据这个bool值来判断是执行关注操作还是取关操作。

       关注与取关操作 和 判断是否关注 的实现类方法定义为:follow和isFollow,代码如下:

@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        //1.获取登陆用户
        Long userId = UserHolder.getUser().getId();
        //2.判断是关注还是取关
        if(isFollow){
            //2.1 关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            save(follow);
        }else {
            //2.2取关 直接删除表中的数据即可
            remove(new QueryWrapper<Follow>()
                    .eq("user_id",userId).eq("follow_user_id",followUserId));
        }
        return Result.ok();
    }

    @Override
    public Result isFollow(Long followUserId) {
        //1.获取登陆用户
        Long userId = UserHolder.getUser().getId();
        //2.查询数据库看是否关注
        Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();

        return Result.ok(count > 0);
    }
}

共同关注

        接下来实现共同关注功能。当点进一个用户的主页时,会显示该用户的基本信息(头像、id),还会显示该用户发布的博客。

         右边还有一个共同关注的列表,显示的内容是我与这个用户共同关注的用户,点击共同关注,接口信息如下:

        1022为当前操作用户的id,这里需要实现当前操作用户与点击的这个用户的共同关注列表,思路就是将两个用户的关注列表取一个交集即可,那么适合此操作的数据结构为redis中的set集合,set集合有取交集的操作。

        这里得先修改我们之前关注和取关操作的逻辑,在关注博主的同时,需要将数据放到set集合中,方便后期我们实现共同关注,当取消关注时,也需要将数据从set集合中删除。

        原代码改为:

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        //1.获取登陆用户
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        //2.判断是关注还是取关
        if(isFollow){
            //2.1 关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            boolean isSuccess = save(follow);
            if(isSuccess){
                //把关注用户的id,存入redis的set集合中
                stringRedisTemplate.opsForSet().add(key,followUserId.toString());
            }
        }else {
            //2.2取关 直接删除表中的数据即可
            boolean isSuccess = remove(new QueryWrapper<Follow>()
                    .eq("user_id", userId).eq("follow_user_id", followUserId));
            if(isSuccess){
                // 把关注用户的id 从redis集合中移除
                stringRedisTemplate.opsForSet().remove(key,followUserId.toString());
            }
        }
        return Result.ok();
    }

        即在修改数据库之后,对redis中的set集合数据进行保存或者移除。

        接下来,实现共同关注的功能,大致思路为:从redis中取出两个用户关注列表,再使用redis中的intersect操作取出两个关注列表的集合,再返回给前端页面。代码如下:

    @Override
    public Result followCommons(Long id) {
        //1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        //2.求交集
        String key2 = "follows:" + id;
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
        if(intersect == null || intersect.isEmpty()){
            //无交集
            return Result.ok(Collections.emptyList());
        }
        //3.解析id集合
        List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
        //4.查询用户
        List<UserDTO> users = userService.listByIds(ids)
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(users);
    }

测试结果如下:

 关注推送

        当我们关注了用户之后,这个用户发布了动态,那我们应该把这些动态推送给粉丝,即关注推送,关注推送也叫作Feed流,直译为投喂,为用户提供沉浸式体验,通过无限下拉刷新获取新的信息。

  • Feed流的实现有两种模式
    1. Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注(B站关注的up,朋友圈等)
      • 优点:信息全面,不会有缺失,并且实现也相对简单
      • 缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低
    2. 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容,推送用户感兴趣的信息来吸引用户
      • 优点:投喂用户感兴趣的信息,用户粘度很高,容易沉迷
      • 缺点:如果算法不精准,可能会起到反作用(给你推的你都不爱看)

        这里我们采用的是Timeline方式,有三种方案:

  1. 拉模式:也叫读扩散,博主将发布的博客保存至发件箱,粉丝需要将关注的内容拉到自己的收件箱中。
  2. 推模式:也叫写扩散,博主会将发布的博客发布至所有粉丝的收件箱中。
  3. 推拉结合:上述两种方式的结合版。

        这里考虑到粉丝量不会有很多,采取推模式。需要修改新增探店博客的业务,在保存blog到数据库的同时,推送到粉丝的收件箱,收件箱需要满足可以根据时间戳排序,必须用Redis的数据结构实现,这里使用ZSet集合来充当收件箱。

        这里为什么使用redis中的Zset集合呢?Zset支持滚动分页,传统的分页模式在这里会有问题,因为feed流中的数据是不断变化的,数据的角标也在变化,这里需要使用滚动分页。

        改造新增探店博客的代码:

    @Override
    public Result saveBlog(Blog blog) {
        // 1.获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 2.保存探店博文
        boolean isSuccess = save(blog);
        if(!isSuccess){
            return Result.fail("新增笔记失败!");
        }
        //3.查询该博客作者的所有粉丝
        List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
        //4.推送笔记id给所有粉丝
        for(Follow follow : follows){
            //4.1获取粉丝id
            Long userId = follow.getUserId();
            //4.2推送
            String key = "feed:" + userId;
            stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
        }
        // 5.返回id
        return Result.ok(blog.getId());
    }

        点击关注栏,会发送请求:

        实现该接口的实现类:

    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        String key = FEED_KEY + userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 3.非空判断
        if (typedTuples == null || typedTuples.isEmpty()) {
            return Result.ok();
        }
        // 4.解析数据:blogId、minTime(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0; // 2
        int os = 1; // 2
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
            // 4.1.获取id
            ids.add(Long.valueOf(tuple.getValue()));
            // 4.2.获取分数(时间戳)
            long time = tuple.getScore().longValue();
            if(time == minTime){
                os++;
            }else{
                minTime = time;
                os = 1;
            }
        }
        // 5.根据id查询blog
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();

        for (Blog blog : blogs) {
            // 5.1.查询blog有关的用户
            queryBlogUser(blog);
            // 5.2.查询blog是否被点赞
            isBlogLiked(blog);
        }

        // 6.封装并返回
        ScrollResult r = new ScrollResult();
        r.setList(blogs);
        r.setOffset(os);
        r.setMinTime(minTime);

        return Result.ok(r);
    }

        最终效果如图:

  • 18
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值