Redis——实现关注与取关等功能

目录

实现关注与取关

实现共同关注功能

关注推送

Feed流的模式

拉模式

推模式

推拉结合模式

总结

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

Feed流的分页问题

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


实现关注与取关

实现

Controller层

@RestController
@RequestMapping("/follow")
public class FollowController {
    @Resource
    private IFollowService followService;
    @PutMapping("/{id}/{isFollow}")
    public Result follow(@PathVariable("id")Long followUserId,@PathVariable("isFollow") Boolean isFollow){
        return followService.follow(followUserId,isFollow);
    }

    @GetMapping("/or/not/{id}")
    public Result follow(@PathVariable("id")Long followUserId){
        return followService.isFollow(followUserId);
    }
}

 

Service层

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        //1.获取登录用户
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        //判断是取关还是关注
        if(isFollow){
            //2.关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            follow.setCreateTime(LocalDateTime.now());
            boolean isSuccess = save(follow);
        }
        else{
            //取关,删除数据
            //sql: delete from tb_follow where user_id = ? and follow_user_id = ?
            remove(new QueryWrapper<Follow>()
                    .eq("user_id",userId)
                    .eq("follow_user_id",followUserId)
            );
        }
        return Result.ok();
    }

    @Override
    public Result isFollow(Long followUserId) {
        //1.获取用户
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        //查询是否关注
        //sql: select * from tb_follow where user_id = ? and follow_user_id
        Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();

        return Result.ok(count>0);
    }


实现共同关注功能

 

修改前面的关注接口,关注的对象不仅仅存入数据库中,还需存入redis中(共同关注功能需要set数据结构完成) 

修改

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        //1.获取登录用户
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        String key = "follows:"+userId;
        //判断是取关还是关注
        if(isFollow){
            //2.关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            follow.setCreateTime(LocalDateTime.now());
//修改部分
            boolean isSuccess = save(follow);
            if(isSuccess){
                //按关注用户的id放入redis的set集合中
                stringRedisTemplate.opsForSet().add(key,followUserId.toString());
            }
        }
        else{
            //取关,删除数据
            //sql: delete from tb_follow where user_id = ? and follow_user_id = ?
//修改部分
            boolean isSuccess = remove(new QueryWrapper<Follow>()
                    .eq("user_id", userId)
                    .eq("follow_user_id", followUserId)
            );
            if(isSuccess){
                stringRedisTemplate.opsForSet().remove(key,followUserId.toString());
            }
        }
        return Result.ok();
    }

实现

Controller层

    @GetMapping("/common/{id}")
    public Result followCommons(@PathVariable("id")Long id){
        return followService.followCommons(id);
    }

 

Service层

    @Override
    public Result followCommons(Long id) {
        //1.获取当前用户id
        Long userId = UserHolder.getUser().getId();
        String key1 = "follows:"+userId;
        //2.求交集
        String key2= "follows:"+id;
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
        if(intersect==null||intersect.isEmpty()){
            //无共同关注
            return Result.ok(Collections.emptyList());
        }
        //解析id集合
        List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());

        //查询用户
        List<UserDTO> users = userService.listByIds(ids)
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(users);
    }


关注推送

 

Feed流的模式

 

拉模式

(msg后面数据为时间戳)如下所示,赵六收件箱一般是空的,只有在赵六在读消息时才会来拉取他关注目标的发件箱,并按照时间排序

优点:节省内存空间,收件箱读完以后,就不用了可以清理,所以消息只保存一份在发件人的发件箱中

缺点:每次去读消息时,都要去拉取再做排序,耗时时间长,读取时间较

推模式

弥补拉模式的缺点,每次都会把消息发给每一个粉丝,并排序,等粉丝读取

缺点:这种扩散方式,每次更新消息时,都会发给每个粉丝,所以内存占用率很高

推拉结合模式

普通人采取推模式

大V,活跃粉丝采用推模式,普通的粉丝采用拉模式

总结

 


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

 

Feed流的分页问题

思考应该用Redis中哪种数据结构进行时间戳的排序?是sortedset还是list

        list查询数据只能按照角标查询,或者首尾查询,不支持滚动分页

        sortedset按照score值排序,得出排名,如果按照排名查询则跟角标查询无区别,但是sortedset支持按照score值范围进行查询(把时间戳按照从大到小进行排列,每次排序都记住最小的时间戳,然后下次查询时再找比这个时间戳更小的,这样就实现了滚动分页查询)

小结:如果数据会有变化,尽量使用sortedset实现分页查询

采用传统角标查询会出现下列问题

 

 

使用滚动分页,每次存入页的最后一次角标

 

实现

Controller层

    @PostMapping
    public Result saveBlog(@RequestBody Blog blog) {
        return blogService.saveBlog(blog);
    }

Service层

    @Override
    public Result saveBlog(Blog blog) {
        //获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());

        //2.保存笔记
        boolean isSuccess = save(blog);
        if(isSuccess == false){
            return Result.fail("新增笔记失败!");
        }
        //3.查询笔记作者所有粉丝
        //sql: select * from tb_follow where follow_user_id = ?
        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推送sortedSet
            String key = "feed:" + userId;
            stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(),System.currentTimeMillis());
        }
        //3.返回id
        return Result.ok(blog.getId());
    }

 


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

角标查询 和 通过记录最后一个的范围查询的对比

一开始redis中z1集合存入的数据("m6" : 6,"m5" : 5,"m4" : 4,"m3" : 3,"m2" : 2,"m1" : 1)

实现滚动分页查询一定要传入四个参数:

分数的最大值(max),分数的最小值(min),偏移量(offset),数量(count)(其中分数最小值,数量固定不变)

最大值每次都要找上一次查询的最小分数(除了第一次)

偏移量(第一次采取0,以后采取1,小于等于与小于的区别)

注意:如果分数一样会出现以下问题,所以offset应是上一次查询的最小分数的总个数

 

接口说明

 

Controller层


    @GetMapping("/of/follow")
    public Result queryBlogOfFollow(@RequestParam("lastId") Long max,@RequestParam(value = "offset",defaultValue = "0") Integer offset){
        return blogService.queryBlogOfFollow(max,offset);
    }

 

Service层

@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:" + userId;
        //元组
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);

        //非空判断
        if(typedTuples==null||typedTuples.isEmpty()){
            return Result.ok();
        }

        //3.解析数据:blogId、minTime(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;//偏移量
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            //3.1获取id(blogId)
            ids.add(Long.valueOf(typedTuple.getValue()));
            //3.2获取分数(时间戳)
            //执行到最一定是最小时间戳
            long time = typedTuple.getScore().longValue();
            if(time == minTime){
                os++;
            }
            else{
                minTime = time;
                os = 1;
            }
        }

        //4。根据id查询blog
        //不能采用下面方法查询 in 语句无序
        //List<Blog> blogs = listByIds(ids);
        //应使用
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = query().in("id", ids)
                .last("ORDER BY FIELD(id," + idStr + ")")
                .list();

        //5.封装并返回
        //需要将blog的信息完善
        for (Blog blog : blogs) {
            //查询blog相关用户
            queryBlogUser(blog);
            //查询blog是否被点赞
            isBlogLiked(blog);
        }
        ScrollResult scrollResult = new ScrollResult();
        scrollResult.setList(blogs);
        scrollResult.setOffset(offset);
        scrollResult.setMinTime(minTime);
        return Result.ok(scrollResult);
    }

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值