Redis实战案例及问题分析之好友关注功能(关注、共同好友、消息推送)

关注和取关

需求:基于该表数据结构,实现两个接口

关注和取关接口

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

判断是否关注的接口

 

 关注是User之间的关系,是博主与粉丝的关系,数据库中有一张tb_follow表来标示:

 

    @Override
    public Result isFollow(Long followUserId) {
        //1.获取用户id
        Long userId = UserHolder.getUser().getId();
        //2.查询是否关注
        Integer count = query().eq("user_id", userId)
                .eq("follow_user_id", followUserId).count();
        return  Result.ok(count > 0);

    }

共同关注

需求:利用Redis中恰当的数据结构,实现共同关注功能。在博主个人页面展示出当前用户与博主的共同好友。

其中redis中的set可以查询两个集合的交集。所以需要把当前用户、目标用户的关注列表放入set集合。

 

 

 

    @Override
    public Result followCommons(Long followUserId) {
        //1、获取当前用户
        Long userId = UserHolder.getUser().getId();
        String userKey = "follow:" + userId;
        String followKey = "follow:" + followUserId;
        //2.求交集
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(userKey, followKey);
        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流产品有两种常见模式:

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

本例中的个人页面,是基于关注的好友来做Feed流,因此采用Timeline的模式。该模式的实现方案有三种:

  1. 拉模式

 也叫读扩散,优点是节省内存空间,缺点是耗时比较久,读取延时高

 2.推模式

也叫写扩散,把消息写到所有粉丝的收件箱。优点是延时低,缺点是内存占有高。

 

3.推拉结合

 也叫做读写混合,兼具了推和拉两种模式的优点。对普通人,粉丝不多的就用推模式,对于大V,如果是活跃粉丝,就用推模式,普通粉丝就用拉模式

 

 基于推模式实现关注关注推送功能

需求:

  1. 修改新增探店笔记的业务,在保存blog到数据库的同时,推送到粉丝的收件箱
  2. 收件箱满足可以根据时间戳排序,必须用Redis的数据结构实现
  3. 查询收件箱数据时,可以实现分页查询

feed流的分页问题 

Feed流中的数据会不断更新,所以数据的角标也在变化,因此不能采用传统的分页模式。

 feed流的滚动分页

 因为数据会有变化,所以如果采用list结构来保存会出现分页的时候重复读的问题,而sortset数据结构中提供一个根据score范围查的功能,只需要记录下每一次查询最后一条的score值,下一次查询去查小于这个score值的数据(根据时间戳的先后顺序)

@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_KEY + userId;
            stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
        }
        // 5.返回id
        return Result.ok(blog.getId());
    }

实现关注推送页面的分页查询

需求:在个人主页的“关注”卡片中,查询并展示推送的Blog信息

 

  @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        //1.获取当前用户
        Long userId = UserHolder.getUser().getId();
        //2.查询收件箱
        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、score(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) {
            //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) {
            queryBlogUser(blog);
            //判断是否已经给当前博客点赞
            //如果已经点赞给isliked字段设置为true
            isBlogLiked(blog);
        }
        //6.封装并返回
        ScrollResult result = new ScrollResult();
        result.setList(blogs);
        result.setOffset(os);
        result.setMinTime(minTime);
        return Result.ok(result);
    }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值