Redis应用之Feed流关注推送

news2025/1/1 21:49:27

我的博客大纲

我的后端学习大纲

-------------------------------------------------------------------------------------------------------------------------------------------------# 3.好友关注:

3.1.关注和取关:

a.接口说明:

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

b.编码实现:

  • 1.Controller层接口:
@RestController
@RequestMapping("/follow")
public class FollowController {

    @Resource
    private IFollowService followService;

    /**
     * 关注用户
     * @param followUserId 关注用户的id
     * @param isFollow 是否已关注
     * @return
     */
    @PutMapping("/{id}/{isFollow}")
    public Result follow(@PathVariable("id") Long followUserId, @PathVariable Boolean isFollow){
        return followService.follow(followUserId, isFollow);
    }

    /**
     * 是否关注用户
     * @param followUserId 关注用户的id
     * @return
     */
    @GetMapping("/or/not/{id}")
    public Result isFollow(@PathVariable("id") Long followUserId){
        return followService.isFollow(followUserId);
    }
}

  • 2.Service层代码:
@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    /**
     * 关注用户
     *
     * @param followUserId 关注用户的id
     * @param isFollow     是否已关注
     * @return
     */
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        Long userId = ThreadLocalUtls.getUser().getId();
        if (isFollow) {
            // 用户为关注,则关注
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            this.save(follow);
        } else {
            // 用户已关注,删除关注信息
            this.remove(new LambdaQueryWrapper<Follow>()
                    .eq(Follow::getUserId, userId)
                    .eq(Follow::getFollowUserId, followUserId));
        }
        return Result.ok();
    }

    /**
     * 是否关注用户
     *
     * @param followUserId 关注用户的id
     * @return
     */
    @Override
    public Result isFollow(Long followUserId) {
        Long userId = ThreadLocalUtls.getUser().getId();
        int count = this.count(new LambdaQueryWrapper<Follow>()
                .eq(Follow::getUserId, userId)
                .eq(Follow::getFollowUserId, followUserId));
        return Result.ok(count > 0);
    }
}

  • 3.页面上的关注测试:
    在这里插入图片描述

3.2.共同关注:

a.接口说明:

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

b.编码实现:

  • 1.我们想要查询出两个用户的共同关注对象,这就需要使用求交集,对于求交集,我们可以使用Set集合
@Service
public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private IUserService userService;

    /**
     * 关注用户
     *
     * @param followUserId 关注用户的id
     * @param isFollow     是否已关注
     * @return
     */
    @Override
    public Result follow(Long followUserId, Boolean isFollow) {
        Long userId = ThreadLocalUtls.getUser().getId();
        String key = FOLLOW_KEY + userId;
        if (isFollow) {
            // 用户为关注,则关注
            Follow follow = new Follow();
            follow.setUserId(userId);
            follow.setFollowUserId(followUserId);
            boolean isSuccess = this.save(follow);
            if (isSuccess) {
                // 用户关注信息保存成功,把关注的用户id放入Redis的Set集合中,
                stringRedisTemplate.opsForSet().add(key, followUserId.toString());
            }
        } else {
            // 用户已关注,删除关注信息
            boolean isSuccess = this.remove(new LambdaQueryWrapper<Follow>()
                    .eq(Follow::getUserId, userId)
                    .eq(Follow::getFollowUserId, followUserId));
            if (isSuccess) {
                stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
            }
        }
        return Result.ok();
    }

    /**
     * 是否关注用户
     *
     * @param followUserId 关注用户的id
     * @return
     */
    @Override
    public Result isFollow(Long followUserId) {
        Long userId = ThreadLocalUtls.getUser().getId();
        int count = this.count(new LambdaQueryWrapper<Follow>()
                .eq(Follow::getUserId, userId)
                .eq(Follow::getFollowUserId, followUserId));
        return Result.ok(count > 0);
    }

    /**
     * 查询共同关注
     *
     * @param id
     * @return
     */
    @Override
    public Result followCommons(Long id) {
        Long userId = ThreadLocalUtls.getUser().getId();
        String key1 = FOLLOW_KEY + userId;
        String key2 = FOLLOW_KEY + id;
        // 查询当前用户与目标用户的共同关注对象
        Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
        if (Objects.isNull(intersect) || intersect.isEmpty()) {
            return Result.ok(Collections.emptyList());
        }
        List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
        // 查询共同关注的用户信息
        List<UserDTO> userDTOList = userService.listByIds(ids).stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(userDTOList);
    }
}

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


3.3.Feed流关注推送

a.什么是Feed流?

  • 1.关注推送也叫做Feed流,直译为投喂。为用户持续的提供“沉浸式”的体验,通过无限下拉刷新获取新的信息。
  • 2.Feed流是一种基于用户个性化需求和兴趣的信息流推送方式,常见于社交媒体、新闻应用、音乐应用等互联网平台。
  • 3.Feed流通过算法和用户行为数据分析,动态地将用户感兴趣的内容以流式方式呈现在用户的界面上。
    在这里插入图片描述

b.Feed流产品有两种常见模式:

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

c.时间排序(Timeline)的实现方式:

  • 1.本例中的个人页面,是基于关注的好友来做Feed流,因此采用Timeline的模式。该模式的实现方案有三种:
    在这里插入图片描述
c1.拉模式:

1.拉模式概念:

  • 1.拉模式也叫做读扩散。
  • 2.在拉模式中,终端用户或应用程序主动发送请求来获取最新的数据流。它是一种按需获取数据的方式,用户可以在需要时发出请求来获取新数据。
  • 3.在Feed流中,数据提供方将数据发布到实时数据源中,而终端用户或应用程序通过订阅或请求来获取新数据。

2.拉模式优点:

  • 1.节约空间,可以减少不必要的数据传输,只需要获取自己感兴趣的数据,因为赵六在读信息时,并没有重复读取,而且读取完之后可以把他的收件箱进行清楚。

3.拉模式缺点:

  • 1.延迟较高,当用户读取数据时才去关注的人里边去读取数据,假设用户关注了大量的用户,那么此时就会拉取海量的内容,对服务器压力巨大
    在这里插入图片描述
c2.推模式:

1.推模式概念:

  • 1.推模式也叫做写扩散。在推模式中,数据提供方主动将最新的数据推送给终端用户或应用程序。数据提供方会实时地将数据推送到终端用户或应用程序,而无需等待请求。

2.推模式优点:

  • 1.优点:数据延迟低,不用临时拉取

2.推模式缺点:

  • 1.内存耗费大,假设一个大V写信息,很多人关注他, 就会写很多份数据到粉丝那边去
    在这里插入图片描述
c3.推拉结合:

定义:

  • 1.也叫做读写混合,兼具推和拉两种模式的优点。
  • 2.在推拉结合模式中,数据提供方会主动将最新的数据推送给终端用户或应用程序,同时也支持用户通过拉取的方式来获取数据。这样可以实现实时的数据更新,并且用户也具有按需获取数据的能力。
  • 3.推拉模式是一个折中的方案,站在发件人这一段:
    • 如果是个普通的人,那么我们采用写扩散的方式,直接把数据写入到他的粉丝中去,因为普通的人他的粉丝关注量比较小,所以这样做没有压力
    • 如果是大V,那么他是直接将数据先写入到一份到发件箱里边去,然后再直接写一份到活跃粉丝收件箱里边去
  • 4.现在站在收件人这端来看:
    • 如果是活跃粉丝,那么大V和普通的人发的都会直接写入到自己收件箱里边来
    • 而如果是普通的粉丝,由于他们上线不是很频繁,所以等他们上线时,再从发件箱里边去拉信息
      在这里插入图片描述

d.本案例模式选择:

在这里插入图片描述

  • 1.当前项目用户量比较小,所以这里我们选择使用推模式,延迟低、内存占比也没那么大
  • 2.由于我们需要实现分页查询功能,这里我们可以选择 list 或者 SortedSet,而不能使用Set,因为Set是无序的, list是有索引的,SortedSet 是有序的,那么我们该如何选择呢?
  • 3.如果我们选择 list 会存在索引漂移现象(这个在Vue中也存在),从而导致读取重复数据,所以我们不能选择使用 list
    在这里插入图片描述
  • 4.我们可以选择使用滚动分页,我们使用SortedSet,如果使用排名和使用角标是一样的,但是SortedSet可以按照Score排序(Score默认按照时间戳生成,所以是固定的),每次我们可以选择比之前Score较小的,这样就能够实现滚动排序,从而防止出现问题
    在这里插入图片描述

e.编码实现:

  • 1.代码实现:在BlogServiceImpl中修改原有的保存探店笔记的方法:
    /**
     * 保存探店笔记
     *
     * @param blog
     * @return
     */
    @Override
    public Result saveBlog(Blog blog) {
        Long userId = ThreadLocalUtls.getUser().getId();
        blog.setUserId(userId);
        // 保存探店笔记
        boolean isSuccess = this.save(blog);
        if (!isSuccess){
            return Result.fail("笔记保存失败");
        }
        // 查询笔记作者的所有粉丝
        List<Follow> follows = followService.list(new LambdaQueryWrapper<Follow>()
                .eq(Follow::getFollowUserId, userId));
        // 将笔记推送给所有的粉丝
        for (Follow follow : follows) {
            // 获取粉丝的id
            Long id = follow.getUserId();
            // 推送笔记
            String key = FEED_KEY + id;
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }
        return Result.ok(blog.getId());
    }

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

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

  • 1.Redis中的数据样例:
    在这里插入图片描述
  • 2.角标查询及其问题演示:
    在这里插入图片描述
  • 3.滚动查询演示:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

b.接口介绍:

在这里插入图片描述

b.编码实现:

    /**
     * 关注推送页面的笔记分页
     *
     * @param max
     * @param offset
     * @return
     */
    @Override
    public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1、查询收件箱
        Long userId = ThreadLocalUtls.getUser().getId();
        String key = FEED_KEY + userId;
        // ZREVRANGEBYSCORE key Max Min LIMIT offset count
        Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
                .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
        // 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; // 偏移量offset,用来计数
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
            // 获取id
            ids.add(Long.valueOf(tuple.getValue()));
            // 获取分数(时间戳)
            long time = tuple.getScore().longValue();
            if (time == minTime) {
                // 当前时间等于最小时间,偏移量+1
                os++;
            } else {
                // 当前时间不等于最小时间,重置
                minTime = time;
                os = 1;
            }
        }

        // 4、根据id查询blog(使用in查询的数据是默认按照id升序排序的,这里需要使用我们自己指定的顺序排序)
        String idStr = StrUtil.join(",", ids);
        List<Blog> blogs = this.list(new LambdaQueryWrapper<Blog>().in(Blog::getId, ids)
                .last("ORDER BY FIELD(id," + idStr + ")"));
        // 设置blog相关的用户数据,是否被点赞等属性值
        for (Blog blog : blogs) {
            // 查询blog有关的用户
            queryUserByBlog(blog);
            // 查询blog是否被点赞
            isBlogLiked(blog);
        }

        // 5、封装并返回
        ScrollResult scrollResult = new ScrollResult();
        scrollResult.setList(blogs);
        scrollResult.setOffset(os);
        scrollResult.setMinTime(minTime);

        return Result.ok(scrollResult);
    }

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

\


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2113017.html

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

相关文章

算法【Java】 —— 前缀和

模板引入 一维前缀和 https://www.nowcoder.com/share/jump/9257752291725692504394 解法一&#xff1a;暴力枚举 在每次提供 l 与 r 的时候&#xff0c;都从 l 开始遍历数组&#xff0c;直到遇到 r 停止&#xff0c;这个方法的时间复杂度为 O(N * q) 解法二&#xff1a;前…

不会Excel怎么制作桑基图?用什么软件绘制比较好呢?推荐2款简单好用的图表制作工具

桑基图制作很简单&#xff0c;不需要任何基础一次就会&#xff01; 2个桑基图制作工具&#xff0c;帮你一键解决问题~ 1、Dycharts 推荐指数&#xff1a;☆☆☆☆☆ 点击链接直达>>dycharts.com Dycharts是国内一款专业的在线图表制作工具&#xff0c;0代码、无门槛&…

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(2) &#xff08;*****生成数据结构类的方式特别有趣****&a…

AI基础 L5 Uninformed Search II 无信息搜索

How good is search? • Completeness: Does it always find a solution if one exists? 是否通解 • Optimality: Is the solution optimal (i.e., lowest cost)? 是否最优 • Time Complexity: How long does it take to find a solution? 时间多久 • Space Complexit…

程序员都在使用的画图工具

大家好&#xff0c;我是袁庭新。 程序员都在使用的画图工具&#xff0c;你一定没用过这款画图工具吧&#xff01;我教程中的架构图都是用它来画的。 比如我编写的RDB工作原理图就是用draw.io绘制的&#xff0c;如下图所示&#xff1a; 再例如Redis集群故障恢复原理图我也是通…

【论文阅读】DETRs Beat YOLOs on Real-time Object Detection

文章目录 摘要一、介绍二、相关工作2.1 实时目标检测器2.2 端到端目标检测器 三、检测器的端到端速度3.1 分析 NMS3.2 端到端速度基准 四、实时 DETR4.1 模型概述4.2 高效混合编码器4.3不确定性最小的查询选择4.4 缩放的RT - DETR 五、实验5.1 与SOTA对比5.2 混合编码器的消融研…

VMware Fusion Pro 13 for Mac虚拟机软件

Mac分享吧 文章目录 效果一、下载软件二、开始安装安装完成&#xff01;&#xff01;&#xff01; 效果 一、下载软件 下载软件 地址&#xff1a;www.macfxb.cn 二、开始安装 安装完成&#xff01;&#xff01;&#xff01;

F12抓包06-4:导出metersphere脚本

metersphere是一站式的开源持续测试平台&#xff0c;我们可以将浏览器请求导出为HAR文件&#xff0c;导入到metersphere&#xff0c;生成接口测试。 metersphere有2种导入入口&#xff08;方式&#xff09;&#xff0c;导入结果不同&#xff1a; 1.导入到“接口定义”&#xf…

ctfshow-web入门-sql注入(web237-web240)insert 注入

目录 1、web237 2、web238 3、web239 4、web240 1、web237 查询语句&#xff1a; //插入数据$sql "insert into ctfshow_user(username,pass) value({$username},{$password});"; 我们需要闭合单引号和括号 添加&#xff0c;查数据库名&#xff0c;payload&…

想要从OPPO手机恢复数据?免费OPPO照片视频恢复软件

此实用程序可帮助那些寻找以下内容的用户&#xff1a; 在OPPO手机中格式化存储卡后可以恢复图片吗&#xff1f;我删除了 OPPO上的视频和图片&#xff0c;我感觉很糟糕&#xff0c;因为里面有我在拉斯维加斯拍摄的视频和照片 免费OPPO照片视频恢复软件 您能恢复OPPO上已删除的…

解锁2024年PDF转PPT新技能,TOP4神器在手,职场晋升竟然如此简单

如今职场节奏快&#xff0c;信息传递和展示方式多样。PDF 兼容性强且稳定&#xff0c;用于分享和保存文件&#xff1b;PPT 演示功能强大&#xff0c;在开会、教学和汇报中不可或缺。实际工作中常需将 PDF 转 PPT&#xff0c;以便更好地演示和编辑。市场上因此出现众多高效方便的…

使用NetBackup GUI 图形化进行oracle备份和恢复

转载 一、环境介绍&#xff1a; 这个实验都是在vmware workstation里完成的。由于NetBackup7只能装在64位的系统上&#xff0c;所以这里采用了64位的rhel5.5系统&#xff0c;以及oracle 10gr2 for linux_x64的软件包。数据库的数据文件存储在ASM中。安装rhel、oracle、netback…

Selenium 实现图片验证码识别

前言 在测试过程中&#xff0c;有的时候登录需要输入图片验证码。这时候使用Selenium进行自动化测试&#xff0c;怎么做图片验证码识别&#xff1f;本篇内容主要介绍使用Selenium、BufferedImage、Tesseract进行图片 验证码识别。 环境准备 jdk&#xff1a;1.8 tessdata&…

关于CPP——std::future异步操作

目录 一、std::future 简介 1.1 概念 1.2 应用场景 1.3 关联的方法 1.3.1 std::async 1.3.2 std::package 1.3.3 std::promise 二、future 用法 2.1 使用std::async关联异步任务 2.2 使用std::packaged_task 1. 获取任务结果的机制&#xff1a; 2. 异步任务的管理&a…

jmeter之仅一次控制器

仅一次控制器作用&#xff1a; 不管线程组设置多少次循环&#xff0c;它下面的组件都只会执行一次 Tips&#xff1a;很多情况下需要登录才能访问其他接口&#xff0c;比如&#xff1a;商品列表、添加商品到购物车、购物车列表等&#xff0c;在多场景下&#xff0c;登录只需要…

【STM32 Blue Pill编程】-ADC数据采样(轮询、中断和DMA模式)

ADC数据采样(轮询、中断和DMA模式) 文章目录 ADC数据采样(轮询、中断和DMA模式)1、硬件准备及接线2、ADC轮询模式2.1 轮询模式配置2.2 代码实现3、ADC中断模式3.1 中断模式配置3.2 代码实现4、ADC的DMA模式4.1 DMA模式配置4.2 代码实现在本文中,我们将介绍如何使用 ADC 并…

[JAVA基础知识汇总-1] 创建线程的几种方式

文章目录 1. 继承Thread类2. 实现Runnable接口3. 实现Callable接口4. 线程池 可以认为有四种方式&#xff0c;也可以认为有一种&#xff0c;因为都跟Runnable接口有关 1. 继承Thread类 代码 public class Thread1ExtendsThread extends Thread { // public Thread1(String …

思维导图与头脑风暴:你值得拥有的四大工作与学习利器

在工作与学习中&#xff0c;我们都遇到过这样的情况&#xff1a;我们需要就某一问题或项目&#xff0c;汇集多人的智慧与创意&#xff0c;这时&#xff0c;头脑风暴便成为了我们不可或缺的利器&#xff1b;而为了更好地进行头脑风暴&#xff0c;选择一款合适的在线思维导图工具…

【Qt开发】QT6.5.3安装方法(使用国内源)亲测可行!!!

目录 &#x1f315;下载在线安装包&#x1f315; 把安装包放到系统盘&#x1f315;开始安装&#x1f315;参考文章 &#x1f315;下载在线安装包 https://mirrors.nju.edu.cn/qt/official_releases/online_installers/ &#x1f315; 把安装包放到系统盘 我的系统盘是G盘&…

uniapp 全屏日历,动态无限加载

不好用请移至评论区揍我 原创代码,请勿转载,谢谢! 注:本人仅在微信小程序测试过,未在其他app/h5尝试过,按理说应该是可以的,代码没有引用任何第三方组件 日历中每个日期下方的空白部分均可自定义,写在代码中的<view class="item">我是内容</view>…