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

news2024/9/22 3:55:10

文章目录

  • 关注和取关
  • 共同关注
  • 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.coloradmin.cn/o/2111419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

J.U.C Review - Stream并行计算原理源码分析

文章目录 Java 8 Stream简介Stream单线程串行计算Stream多线程并行计算源码分析Stream并行计算原理Stream并行计算的性能提升 Java 8 Stream简介 自Java 8推出以来&#xff0c;开发者可以使用Stream接口和lambda表达式实现流式计算。这种编程风格不仅简化了对集合操作的代码&a…

无线信道中ph和ph^2的场景

使用 p h ph ph的情况&#xff1a; Rayleigh 分布的随机变量可以通过两个独立且相同分布的零均值、高斯分布的随机变量表示。设两个高斯随机变量为 X ∼ N ( 0 , σ 2 ) X \sim \mathcal{N}(0, \sigma^2) X∼N(0,σ2)和 Y ∼ N ( 0 , σ 2 ) Y \sim \mathcal{N}(0, \sigma^2)…

枚举: C++和Python实现鸡兔同笼问题

作者制作不易&#xff0c;关注、点赞、收藏一下吧&#xff01; 目录 1.Python实现 2.C实现 1.Python实现 首先&#xff0c;我们需要输入头和脚的数量: head int(input("请输入头的数量: ")) feet int(input("请输入脚的数量: ")) input() 实现输入…

优质的产业园都在怎么做运营?

产业园区作为区域经济发展的重要载体&#xff0c;其运营模式和管理水平直接影响着产业集聚的成效和区域经济的竞争力。在一线城市与新一线城市中&#xff0c;已经涌现出了一批以高效运营、创新服务为特色的优质产业园&#xff0c;今天&#xff0c;我们就城市标杆产业园的案例和…

10款古方突破1800亿元,康缘药业发力,市场迎井喷式增长……

在政策东风与市场需求的双重驱动下&#xff0c;中药创新领域正迎来前所未有的发展机遇。特别是古代经典名方制剂的研发与注册&#xff0c;正以前所未有的速度推进&#xff0c;不仅激发了行业的活力&#xff0c;也为患者带来了更多治疗选择。本文将深入探讨这一领域的最新动态&a…

828华为云征文|Flexus云服务器X实例快速部署在线测评平台,适用各种信息学教学

文章目录 如何选配Flexus云服务器X实例服务器HydroOJHOJ 服务器资源的选取基础配置实例规格镜像、存储、网络弹性公网IP云服务器名称 部署HydroOJ1.设置安全组、开放端口2.部署HydroOJ回到控制中心&#xff0c;远程登录 部署HOJ安装docker# 安装docker-compose部署HOJ 本篇幅为…

Vatee万腾平台:赋能企业,共筑智慧经济新高地

在智慧经济时代的大潮中&#xff0c;企业如何把握机遇&#xff0c;实现转型升级&#xff0c;成为行业内的佼佼者&#xff1f;Vatee万腾平台以其卓越的技术实力、前瞻性的战略眼光和全方位的服务体系&#xff0c;正逐步成为企业数字化转型的坚实后盾&#xff0c;赋能企业&#x…

Day-06-QFileDialog文件操作

一、实现打开文件选择对话框 1、程序演示 2、相关代码 void Widget::on_QFileDialog_clicked() {QString fileName QFileDialog::getOpenFileName(this, tr("Open File"),"D:/QT project/", /*注意修改自己的文件打开地址*/tr("Text (*.txt *.d…

又一个新的开源AI项目!!【送源码】

好家伙&#xff0c;国内大模型发展太猛了&#xff01; 旗舰端侧模型面壁「小刚炮」系列进化为全新 MiniCPM 3.0 基座模型&#xff0c;再次以小博大&#xff0c;以 4B 参数&#xff0c;带来超越 GPT-3.5 的性能&#xff0c;强得不像端侧模型。 并且&#xff0c;量化后仅 2GB 内…

【Hadoop|HDFS篇】HDFS的读写流程

1. HDFS的写流程 1.1 剖析文件的写入 副本存储节点的选择问题&#xff1a; 第一个副本在Client所在的节点上&#xff0c;如果客户端在集群外&#xff0c;随机选一个。第二个副本在另一个机架的随机一个节点上。第三个副本在第二个副本所在的机架的随机节点上。 2. HDFS的写流…

Redis 集群:如何实现数据的高效分片与负载均衡

Redis 集群&#xff1a;如何实现数据的高效分片与负载均衡 一 . 基本概念二 . 数据分片算法2.1 哈希求余算法2.2 一致性哈希算法2.3 哈希槽分区算法核心思路Redis 集群中最多只能有 16384 个分片吗 ?为什么一定要是 16384 个槽位 ? 三 . 基于 docker 进行集群的搭建3.1 创建目…

《MaPLe: Multi-modal Prompt Learning》中文校对版

系列论文研读目录 文章目录 系列论文研读目录题目&#xff1a;《Maple&#xff1a;多模态提示学习》摘要1.简介2.相关工作视觉语言模型&#xff1a;提示学习&#xff1a;视觉语言模型中的提示学习&#xff1a; 3.方法3.1.回看CLIP编码图像&#xff1a;编码文本&#xff1a;Zero…

【vue、UI】使用 Vue2 和 Element UI 封装 CSV 文件上传组件,实现csv回显

文章目录 前言组件功能概述实现效果组件模板结构组件的核心逻辑1.数据属性定义2.方法拆解3.CSV 文件解析方法4. 错误处理方法 组件样式完整组件代码总结待优化的地方 前言 在 Vue2 项目中&#xff0c;我们经常需要封装一些可重用的组件来提升开发效率。本文将介绍如何使用 Vue…

Linux工程管理文件Makefile-入门篇

1.Makefile简介 Makefile是在Linux环境下 C/C 程序开发必须要掌握的一个工程管理文件。当你使用make命令去编译一个工程项目时&#xff0c;make工具会首先到这个项目的根目录下去寻找Makefile文件&#xff0c;然后才能根据这个文件去编译程序。那Makefile在编译过程中到底起了…

T7:咖啡豆识别

T7&#xff1a;咖啡豆识别 **一、前期工作**1.设置GPU,导入库2.导入数据3.查看数据 **二、数据预处理**1.加载数据2.可视化数据3.配置数据集 **三、构建CNN网络模型**1、手动搭建2、直接调用官方模型 **四、编译模型****五、训练模型****六、模型评估****七、预测**八、暂时总结…

Spring-容器:IOC-基于注解管理Bean

目录 一、基于注解管理Bean&#xff08;重点&#xff09;1.1、概述1.2、开启组件扫描1.2.1、指定要排除的组件1.2.2、仅扫描指定组件 1.3、使用注解定义Bean1.4、使用Autowired注入1.4.1、属性注入1.4.2、set注入1.4.3、构造方法注入1.4.4、形参注入1.4.5、无注解注入1.4.6、联…

自幂数判断c++

题目描述 样例输入 3 152 111 153样例输出 F F T 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; long long m,a; int main(){cin>>m;for(int i1;i<m;i){cin>>a;long long ta,n[10],cc0,s0;while(t!0){//求位数与拆位n[cc]t%10;tt/…

报错:Reached the max session limit(DM8 达梦数据库)

报错:Reached the max session limit - - DM8 达梦数据库 1 环境介绍2 数据库启动SYSTEM IS READY后面日志3 数据库刚启动日志4 达梦数据库学习使用列表 1 环境介绍 某项目无法连接数据库,报错:超过最大会话数限制 , 检查 dmdba ulimit -a openfiles 已改检查 dm.ini 其中 MAX…

中间代码例题

答案&#xff1a;D 知识点&#xff1a; 中间代码是一种简单且含义明确的记号系统&#xff0c;可以有若干形式&#xff0c;它们的共同特征是与机器无关。 最常见的中间代码有&#xff1a;后缀式&#xff0c;语法树&#xff0c;三地址码&#xff0c;四元式 这些往往是数据&am…

迪普防火墙接口故障处理

一、防火墙故障初查 一台捷普防火墙&#xff0c;突然间业务不通&#xff0c;接口UP&#xff0c;策略正常&#xff0c;区域正常&#xff0c;互联地址就是不能ping通。 如上&#xff0c;接口状态很正常。 如上&#xff0c;互联设备ping不通。 二、锁定故障问题点 检查arp表&…