当前位置: 首页 > article >正文

黑马点评8——好友关注-SortedSet

文章目录

  • 关注和取关
  • 共同关注
  • Feed流实现方案分析
  • 推送到粉丝收件箱
    • Feed流
    • 基于推模式实现关注推送功能
  • 滚动分页查询收件箱的思路
  • 实现滚动分页查询

关注和取关

在这里插入图片描述

在这里插入图片描述
所以关注和取关就是简单的插入和删除数据库。

  @Override
    public Result isFollow(Long followUserId) {
        // 1. 获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 2.查询是否关注,select count(*) from tb_follow where user_id = ? and follow_user_id = ?
        Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
        // 3.判断
        return Result.ok(count > 0);
    }

    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        // 获取登录用户
        Long userId = UserHolder.getUser().getId();
        // 1. 判断到底是关注还是取关
        if(isFollow){
            // 2.关注,新增数据
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            save(follow);
        }else{
            // 3.取关,删除 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();
    }

共同关注

在这里插入图片描述
这两个接口主要是点击头像,查询用户和查询用户的博客
直接上写接口就行

@GetMapping("/{id}")
    public Result queryUserById(@PathVariable("id") Long userId){
        // 查询详情
        User user = userService.getById(userId);
        if(user == null){
            return Result.ok();
        }
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 返回
        return Result.ok(userDTO);
    }
    @GetMapping("/of/user")
    public Result queryBlogByUserId(
            @RequestParam(value = "current", defaultValue = "1") Integer current,
            @RequestParam("id") Long id){
        // 根据用户查询
        Page<Blog> page = blogService.query().eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
        // 获取当前页数据
        List<Blog> records = page.getRecords();
        return Result.ok(records);
    }

其实查询共同关注就是查询当前登录用户和查询的用户他们的关注的集合的交集
在这里插入图片描述
我们可以把当前用户的所有关注全部放在redis中,直接使用redis的set的求交集的功能

 @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        // 获取登录用户
        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 1. 判断到底是关注还是取关
        if(isFollow){
            // 2.关注,新增数据
            Follow follow = new Follow();
            follow.setFollowUserId(followUserId);
            follow.setUserId(userId);
            boolean isSuccess = save(follow);
            if(isSuccess){
                // 把关注用户的id, 放入redis的set集合,sadd userId followerUserId
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        }else{
            // 3.取关,删除 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){
                // 把关注用户的id从Redis集合中移除
                stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
            }
        }
        return Result.ok();
    }

    @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> userDTOS = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
        return Result.ok(userDTOS);
    }

Feed流实现方案分析

在这里插入图片描述

推送到粉丝收件箱

Feed流

在这里插入图片描述
我们的业务是把关注的用户发布的博客展示出来,比较适合使用Timeline模式
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
我们就使用推模式

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

在这里插入图片描述
Feed流的分页问题:
在这里插入图片描述
分页流不能采用传统的分页模式,因为我们的数据会动态变化,比如上面的情况就会出现重复读取6的情况,
因为我们采用滚动分页
在这里插入图片描述
因为他的查询不依赖角标,因此他的查询不会因为角标的变化、数据的变化而改变
list支持滚动分页吗?不行,因为list只支持角标的查询,或者首尾,都不行。
SortedSet可以吗?SortedSet会按照score值排序,然后有一个排名,如果按照排名查询,那和角标查询没什么区别,但是,我们的SortedSet也支持范围查询,score值就是我们的时间戳嘛,我们把时间戳从大到小的顺序进行一个排列,每一次查询的时候,记录下最小的时间戳,然后下次查询的时候,找比这个更小的,这样就实现了滚动分页了。

我们先实现,每当有人发布笔记时,把笔记保存到SortedSet中

 @Override
    public Result saveBlog(Blog blog) {
        // 获取登录用户
        UserDTO user = UserHolder.getUser();
        blog.setUserId(user.getId());
        // 保存探店笔记
        boolean isSuccess = save(blog);
        if(!isSuccess){
            return Result.fail("新增笔记失败!");
        }
        // 3.查询笔记作者的所有粉丝 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 推送
            String key = "feed:" + userId;
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }
        // 返回id
        return Result.ok(blog.getId());
    }

滚动分页查询收件箱的思路

在这里插入图片描述
在这里插入图片描述

实现滚动分页查询

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

    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1. 获取当前用户id
        Long userId = UserHolder.getUser().getId();
        // 2. 查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        String key = RedisConstants.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(时间戳minTime), offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0;
        int os = 1;
        for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
            // 4.1 获取id
            ids.add(Long.valueOf(typedTuple.getValue()));
            // 4.2 获取分数
            long time = typedTuple.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);
    }


http://www.kler.cn/news/293052.html

相关文章:

  • Vue3中引用的组件如果使用了插槽,如何做到引用的组件不显示某些元素
  • 9/3作业
  • 使用reflex的序章:安装cargo、fnm和bun
  • Vue计算属性(computed)的使用方法及使用场景总结
  • windows pg 数据库 配置远程链接
  • JS面试真题 part1
  • 基于yolov8的包装盒纸板破损缺陷测系统python源码+onnx模型+评估指标曲线+精美GUI界面
  • 【Unity】打包报错类型不存在于命名空间内
  • 算法篇_C语言实现霍夫曼编码算法
  • 科研绘图系列:R语言PCoA图(PCoA plot)
  • HarmonyOS开发实战( Beta5.0)DevEco Device Tool开发环境搭建实践
  • 卷轴模式系统源码开发之社交电商:融合传统与创新的新篇章
  • 解决职业摔跤手分类问题的算法与实现
  • Matlab 并联双振子声子晶体梁结构带隙特性研究
  • 算法训练营|图论第11天 Floyd算法 A*算法
  • 【微服务】接口的幂等性怎么设计?
  • Kubernetes 上安装 Jenkins
  • 5、Django Admin后台移除“删除所选”操作
  • 问:Java异常处理的日常?
  • 民宿小程序开发制作,开发优势分析
  • 《绝区零》全球累积收入突破1亿美金;《原神》斩获年度最佳手游大奖 | 手游和应用出海资讯
  • Linux 进程概念
  • Java笔试面试题AI答之JDBC(4)
  • 006-Sleuth(Micrometer)+ZipKin分布式链路追踪
  • Swift 运算符
  • 在 “Label Studio” 和 “Android Studio” 中,“studio”的含义
  • Signac R|如何合并多个 Seurat 对象 (2)
  • 联蔚盘云亮相CDIE消费品行业峰会
  • React 全屏问题解决方案
  • 8. GIS数据分析师岗位职责、技术要求和常见面试题