[达人探店]-好友关注

文章介绍了如何在系统中实现关注、取消关注功能,使用RedisSet进行共同关注操作,并详细讨论了Feed流的实现,包括拉模式、推模式和结合模式,以及基于ZSet的滚动分页查询算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

主要实现三个功能

  • 1.关注和取消关注
  • 2.共同关注
  • 3.Feed流实现的博客推送
关注和取关

根据传来的参数,为true还是false来判断操作是关注,还是取关

    /**
     * 关注,取关
     * @param followUserId
     * @param isFollow
     * @return
     */
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        Long userId = UserHolder.getUser().getId();
        //关注
        if(isFollow) {
            Follow follow=new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            follow.setCreateTime(LocalDateTime.now());
            save(follow);
        } else {
            //取关
            remove(new QueryWrapper<Follow>().eq("user_id",userId).eq("follow_user_id",followUserId));
        }
        return Result.ok();
    }
共同关注

利用Redis的Set集合中求交集的方法来实现。

那么关注之后数据也要在缓存中添加和删除

    /**
     * 关注,取关
     * @param followUserId
     * @param isFollow
     * @return
     */
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        Long userId = UserHolder.getUser().getId();
        //关注
        if(isFollow) {
            Follow follow=new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            follow.setCreateTime(LocalDateTime.now());
            boolean success= save(follow);
            if(success){
                stringRedisTemplate.opsForSet().add(RedisConstants.FOLLOW_KEY+userId,followUserId.toString());
            }
        } else {
            //取关
            boolean isSuccess = remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id", followUserId));
            if(isSuccess) {
                stringRedisTemplate.opsForSet().remove(RedisConstants.FOLLOW_KEY+userId,followUserId.toString());

            }
        }
        return Result.ok();
    }

    /**
     * 共同关注
     * @param id
     * @return
     */
    @Override
    public Result followCommons(Long id) {
        Long userId = UserHolder.getUser().getId();
        String key1=RedisConstants.FOLLOW_KEY+userId;
        String key2=RedisConstants.FOLLOW_KEY+id;
        //求交集
        Set<String> collect = stringRedisTemplate.opsForSet().intersect(key1, key2);
        if(collect==null || collect.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        //解析出ID集合
        List<Long> ids = collect.stream().map(Long::valueOf).collect(Collectors.toList());
        List<UserDTO> collect1 = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
        return  Result.ok(collect1);
    }

实现关注推送功能
  • 就是展示你关注的博主,他们的动态信息。

关于推送Feed流

什么是Feed流

  • Feed:Feed流中的每一条信息都是Feed,比如朋友圈的每一条信息。
  • Feed流:持续更新并呈现给用户的信息流,通过无限下拉刷新获取新的信息。B站的动态页面就是Feed流。

Feed流的实现有两种模式:

  • TimeLine
  • 智能排序

实现Feed流的三种方式

1.拉模式(读扩散):up发的动态会存在发件箱,粉丝想要去查看自己所有关注up的动态信息,会在收件箱拉去关注up的发件箱的信息。不看的时候收件箱会清空。缺点是当你关注的up比较多的时候不好搞

2.推模式(写扩散):up发动态会自己推到粉丝的收件箱,粉丝不用主动拉去信息。缺点是粉丝过多的时候不好搞

3.推拉结合:小up粉丝少,可以直接推模式,把信息推给所有粉丝。大V粉丝多,对于活跃的粉丝推模式,僵尸粉采用拉模式。

所以当我们发博客时候,推送一下:

    /**
     * 发布博客
     * @param blog
     * @return
     */
    @Override
    public Result saveBlog(Blog blog) {
        // 1.获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 2.保存探店博文
        boolean save = save(blog);
        if(!save){
            return Result.fail("发布博客笔记失败惹");
        }
        //sql:select * from tb_follow where follow_user_id = {userid}
        // 3.得到粉丝ids
        List<Long> follow_user_id = followService.query().eq("follow_user_id", user.getId())
                .list()
                .stream()
                .map(follow_user -> follow_user.getUserId()).collect(Collectors.toList());
        //4.推送博客笔记id给所有粉丝
        follow_user_id.forEach(ids-> {
            String key=RedisConstants.FEED_KEY+ids;
            stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(), System.currentTimeMillis());
        });
        // 5.返回id
        return Result.ok(blog.getId());
    }

然后我们去查询收件箱笔记时候,要做一个滚动分页查询:

因为up发动态到我们的收件箱,我们查看动态的时候,是按照最近发布的时间顺序,来展示的。

所以传统的角标来固定分页查询的信息不行啦。

滚动分页,每一次滚动到顶部或者底部会刷新,做一次查询。

我们利用ZSet的Score来做查询

  • 查看收件箱 ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
根据score 从高向低开始查询(max,min是范围), offset:符合范围的第几个开始查,0的话就是小于等于max的第一个就开始,count:查询几个

举个例子,score为:6  5   4  3  2   1

 第一次:max:6   min:0   offset:0 ,count:2 会查询到 6 ,5

 第二次:max:5   min:0   offset:1 ,count:2 会查询到 4 ,3

若是 score为:6  5   5   4   3   2   1

 第一次:max:6   min:0   offset:0 ,count:3 会查询到 6 ,5,5

 第二次:max:5   min:0   offset:1 ,count:3 会查询到 5,4,3(不是想要的4,3,2)

所以,我们的难点在于

  • 1.下一次查询,要得到上一次查询的score作为这次查询的max。
  • 2.上一次查询时,有几个重复的score,offset为几。
    /**
     * 收件箱的笔记滚动分页
     * @param max
     * @param offset
     * @return
     */
    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        // 2.查看收件箱 ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
        // 根据score 从高向低开始查询(max,min是范围), offset:符合范围的第几个开始查,0的话就是小于等于max的第一个就开始,count:查询几个
        String key=RedisConstants.FEED_KEY+userId;
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 3);
        if(typedTuples == null || typedTuples.isEmpty()) {
            return  Result.ok();
        }
        // 3.把信息解析一下,博客id,score(时间戳),offset
        //我们要拿到一个数据,就是最小分数有几个重复的,  5  4 4  3  2  1
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long MinScore = 0;
        int count = 1;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            //获取id
            String value = typedTuple.getValue();
            ids.add(Long.valueOf(value));
            //获取score
            long score = typedTuple.getScore().longValue();
            if(score == MinScore) {
                count++;
            } else {
                MinScore = score;
                count = 1;
            }
        }
        // 4.根据博客id查询博客
        List<Blog> blogs = blogMapper.queryBlogByIds(ids);
        for (Blog blog : blogs) {
            //4.1博客关联的用户
            queryBlogUser(blog);
            //4.2博客是否点赞了
            isBlogLiked(blog);
        }
        // 5.封装返回
        ScrollResult scrollResult=new ScrollResult();
        scrollResult.setList(blogs);
        scrollResult.setMinTime(MinScore);
        scrollResult.setOffset(count);
        return  Result.ok(scrollResult);
    }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值