基于Redis实现关注、取关、共同关注及消息推送(含源码)

微信公众号访问地址:基于Redis实现关注、取关、共同关注及消息推送(含源码)

推荐文章:

    1、springBoot对接kafka,批量、并发、异步获取消息,并动态、批量插入库表;

    2、SpringBoot用线程池ThreadPoolTaskExecutor异步处理百万级数据;

    3、为什么引入Redisson分布式锁?

    4、Redisson可重入锁原理

    5、SpringBoot整合多数据源,并支持动态新增与切换(详细教程)

一、简介

       实现用户之间的关注和取消关注、查询是否关注、共同关注及关注后消息采用feed方式推送及滚动分页查看效果等相关功能。利用redis里面的Set集合实现关注,取关,共同关注,消息推送等,结合Java代码实现具体的功能。

二、实现关注和取关

2.1、关注和取消关注功能

业务流程图:

核心代码实现:

public Result follow(Long followUserId, Boolean isFollow,Long userId) {
        // 1.获取登录用户 暂时先缓存前端传用户ID
//        Long userId = UserHolder.getUser().getId();
        String key = "follows:" + userId;
        // 1.判断到底是关注还是取关
        if (isFollow) {
            // 2.关注,新增数据
            Follow follow = new Follow();
            follow.setUserId(userId);  //当前登录人员ID
            follow.setFollowUserId(followUserId);  //关注用户的id
            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();
    }
 

结果展示:

2.2、共同关注功能

2.3、使用的redis命令如下:

SINTER key1 [key2]返回给定所有集合的交集

2.4、代码实现

public Result followCommons(Long id,Long userId) {
        // 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> users = userService.listByIds(ids)
                .stream()
                .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());
        return Result.ok(users);
    }

2.5、结果展示

三、关注推送

3.1、Feed流模式

3.2、常见模式

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

●Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如:朋友圈。

    >优点:信息全面,不会有缺失。并且实现也相对简单。

    >缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低。

●智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户

    >优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷。

    >缺点:如果算法不精准,可能起到反作用。

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

①拉模式   ②推模式    ③推拉结合

3.3、三种实现方案

3.3.1、拉模式

说明:粉丝主动去拉取相关信息。

3.3.2、推模式

说明:博主主动推送相关信息给粉丝。

3.3.3、推拉结合模式

说明活跃粉丝:博主主动推送相关信息给粉丝。普通粉丝:粉丝主动去拉取相关信息。

3.3.4、总结

       对于大部分中小型公司需求,采取推模式基本上满足需求,对于超千万的用户量的大型公司,需要采取推拉模式,但是实现上相比就复杂多了。

四、案例

4.1、需求分析

滚动分页:

4.2、使用到的redis命令

1、ZREVRANGEBYSCORE:Redis的一个Sorted Set命令,用于按照分数从高到低的顺序返回满足指定分数范围条件的元素。它的语法如下:

ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]

参数解释如下:

    key:排序集合的键名。

    max:分数范围的上限,可以使用"+inf"表示正无穷大。

    min:分数范围的下限,可以使用"-inf"表示负无穷大。

    WITHSCORES:可选参数,表示返回结果时同时返回元素与分数。

    LIMIT:可选参数,用于限制返回结果的偏移量和数量。

示例用法:

ZREVRANGEBYSCORE myset 100 0ZREVRANGEBYSCORE myset (100 0 WITHSCORESZREVRANGEBYSCORE myset 100 0 LIMIT 0 10

第一个示例命令将返回分数从最高到最低的所有元素,分数范围为(100,0],不包括100。

第二个示例命令将返回分数从最高到最低的所有元素及其对应的分数,分数范围为(100,0],不包括100。

第三个示例命令将返回分数从最高到最低的前10个元素,分数范围为(100,0],不包括100。

注意:ZREVRANGEBYSCORE是按照分数从高到低的顺序返回结果的,如果需要按照分数从低到高的顺序返回结果,可以使用ZRANGEBYSCORE命令。

2、ZADD:在Redis中,ZADD命令用于向有序集合(Sorted Set)中添加一个或多个成员。有序集合是一种将成员与分数(score)关联的数据结构,通过分数可以对成员进行排序。

ZADD命令的语法如下:

ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

参数说明:

    key:有序集合的键名。

    NX:仅在键不存在时添加成员。

    XX:仅在键已经存在时添加成员。

    CH:返回修改的成员数量,包括新增和更新的成员。

    INCR:对已经存在的成员的分数进行自增操作。

    score:成员的分数,用于排序。

    member:要添加的成员。

示例:

ZADD myset 1 "member1"ZADD myset 2 "member2" 3 "member3"

注意:ZADD命令在Redis版本2.4以及以上版本可用。

reids中实现滚动分页功能:

4.3、业务流程图

4.4、核心代码展示

4.4.1、保存探店笔记并发送收件箱

  public Result saveBlog(Blog blog) {
        Long userId = 999L;  //暂时写死
        // 1.获取登录用户
//        UserDTO user = UserHolder.getUser();
//        blog.setUserId(user.getId());
        blog.setUserId(userId);
        // 2.保存探店笔记
        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", userId).list();
        //非空判断
        if(CollUtil.isEmpty(follows)){
            return Result.ok();
        }
        // 4.推送笔记id给所有粉丝
        for (Follow follow : follows) {
            // 4.1.获取粉丝id
            Long uId = follow.getUserId();
            // 4.2.feed推送消息
            String key = FEED_KEY + uId;
            stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
        }
        // 5.返回id
        return Result.ok(blog.getId());
    }

4.4.2、查询关注的所有探店笔记(并含滚动分页)

public Result queryBlogOfFollow(Long max, Integer offset) {
        // 1.获取当前用户
//        Long userId = UserHolder.getUser().getId();
        Long userId = 111L;
        // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
        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、minTime(时间戳)、offset
        List<Long> ids = new ArrayList<>(typedTuples.size());
        long minTime = 0; // 2
        int os = 1; // 2
        for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
            // 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) {
            // 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);
    }

备注:

4.4.3、请求参数

参考案例:

{  "comments": 0,  "content": "生活就是一半烟火·一半诗意",  "createTime": "2023-08-17T05:17:34.287Z",  "id": 90,  "images": "111111",  "liked": 0,  "name": "11111",  "shopId": 10,  "title": "生活就是一半烟火",  "updateTime": "2023-08-17T05:17:34.287Z",  "userId": 999}

五、源码获取方式

     更多优秀文章,请关注个人微信公众号或搜索“程序猿小杨”查阅。然后回复:源码,可以获取该项目对应的源码及表结构,开箱即可使用。

说明:后面redis相关操作的功能都会放在此文件夹中,需要相关功能的,只需要获取最新的资源,替换项目即可

       如果大家对相关文章感兴趣,可以关注微信公众号"程序猿小杨",会持续更新优秀文章!欢迎大家 分享、收藏、点赞、在看,您的支持就是我坚持下去的最大动力!谢谢!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

程序员如何利用公网远程访问查询本地硬盘【内网穿透】

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《高效编程技巧》《cpolar》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 公网远程访问本地硬盘文件【内网穿透】 文章目录 公网远程访问本地硬盘文件【内网穿透】前言1. 下载cpolar和Everything软件1.…

小象课堂在线授课教育系统

此项目包含后端全部代码&#xff0c;前端包括后台和web界面的源码&#xff0c;数据库用的mysql,可当作课设或者毕设&#xff0c;还可写入自己的简历中 web界面展示&#xff1a; 前端后台界面展示&#xff1a; 用户管理 课程管理 内容配置 订单管理 系统管理 系统监控

【山河送书第七期】:《强化学习:原理与Python实战》揭秘大模型核心技术RLHF!

《强化学习&#xff1a;原理与Python实战》揭秘大模型核心技术RLHF&#xff01; 一图书简介二RLHF是什么&#xff1f;三RLHF适用于哪些任务&#xff1f;四RLHF和其他构造奖励模型的方法相比有何优劣&#xff1f;五什么样的人类反馈才是好反馈&#xff1f;六如何减小人类反馈带来…

【C++】模板template

&#x1f525;&#x1f525; 欢迎来到小林的博客&#xff01;&#xff01;       &#x1f6f0;️博客主页&#xff1a;✈️林 子       &#x1f6f0;️博客专栏&#xff1a;✈️ C       &#x1f6f0;️社区 :✈️ 进步学堂       &#x1f6f0;️欢…

stack 、 queue的语法使用及底层实现以及deque的介绍【C++】

文章目录 stack的使用queue的使用适配器queue的模拟实现stack的模拟实现deque stack的使用 stack是一种容器适配器&#xff0c;具有后进先出&#xff0c;只能从容器的一端进行元素的插入与提取操作 #include <iostream> #include <vector> #include <stack&g…

ansible 修改远程主机nginx配置文件

安装ansible brew install ansible 或者 pip3 install ansible 添加远程主机 设置秘钥 mac登录远程主机 ssh -p 5700 root192.168.123.211 ssh localhost #设置双机信任 ssh-kyegen -t rsa #设置主机两边的ssh配置文件 vi /etc/ssh/sshd_config/ PermitRootL…

K8S核心组件etcd详解(下)

1 k8s如何使用etcd 在k8s中所有对象的manifest都需要保存到某个地方&#xff0c;这样他们的manifest在api server重启和失败的时候才不会丢失。 只有api server能访问etcd&#xff0c;其它组件只能间接访问etcd的好处是 增强乐观锁系统及验证系统的健壮性 方便后续存储的替换…

C++新经典03--共用体、枚举类型与typedef

共用体 共用体&#xff0c;也叫联合&#xff0c;有时候需要把几种不同类型的变量存放到同一段内存单元&#xff0c;例如&#xff0c;把一个整型变量、一个字符型变量、一个字符数组放在同一个地址开始的内存单元中。这三个变量在内存中占的字节数不同&#xff0c;但它们都从同…

【LeetCode75】第三十一题 反转链表

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 最经典的链表题&#xff0c;没有之一&#xff01;&#xff01;&#xff01; 强烈建议直接把模板记住&#xff01;&#xff01;&#xf…

LeetCode150道面试经典题-- 环形链表(简单)

1.题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&…

Python中import模块导入的实现原理

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起探讨和分享Linux C/C/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。 Python中import模块导入的实现原理 什么是模块import搜索路径import导入模块的原理图书推荐 专栏&…

vue 数字递增(滚动从0到)

使用 html <Incremental :startVal"0" :endVal"1000" :duration"500" />js&#xff1a; import Incremental from /utils/num/numViewjs let lastTime 0 const prefixes webkit moz ms o.split( ) // 各浏览器前缀let requestAnimatio…

树莓派的自启动与桌面应用程序

目录 1 打开终端自启动 .bashrc 2 触发时机较早的开机自启动rc.local 3 桌面应用程序 4 触发时机较晚的的开机自启动 autostart 1 打开终端自启动 .bashrc .bashrc的程序也可以在开机时进行自启动&#xff0c;但是每一次打开终端时同样会运行一遍&#xff0c;所以只需…

5G科技防汛,助力守护一方平安

“立秋虽已至&#xff0c;炎夏尚还在”&#xff0c;受台风席卷以及季节性影响全国多地正面临强降水的严峻挑战。“落雨又顺秋&#xff0c;绵绵雨不休”&#xff0c;正值“七下八上” 防汛关键时期&#xff0c;贵州省水文水资源局已全面进入备战状态。 为确保及时响应做好防汛抢…

Eclipse如何设置快捷键

在eclopse设置注释行和取消注释行 // 打开eclipse&#xff0c;依次打开&#xff1a;Window -> Preferences -> General -> Key&#xff0c;

ARM64 程序调用标准

ARM64 程序调用标准 1 Machine Registers1.1 General-purpose Registers1.2 SIMD and Floating-Point Registers 2 Processes, Memory and the Stack2.1 Memory Addresses2.2 The Stack2.2.1 Universal stack constraints2.2.2 Stack constraints at a public interface 2.3 Th…

【Leetcode】98. 验证二叉搜索树

一、题目 1、题目描述 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例1: 输入:root = …

Nevron 3DChart Crack,可视化界面在运行时可用

Nevron 3DChart Crack,可视化界面在运行时可用 3DChart使用OpenGL 3D图形引擎创建复杂的2D和3D图表&#xff0c;这些图表可以包含静态或动画图像。3DChart包括一个用于生成图表模板的独立应用程序和一个ASP服务器配置实用程序。该组件还包括一个专门设计用于与3DChart集成的工具…

阿里云服务器安装部署Docker使用教程

本文阿里云百科分享如何在云服务ECS实例上&#xff0c;部署并使用Docker。Docker是一款开源的应用容器引擎&#xff0c;具有可移植性、可扩展性、高安全性和可管理性等优势。开发者可将应用程序和依赖项打包到一个可移植的容器中&#xff0c;快速发布到Linux机器上并实现虚拟化…

每日汇评:黄金在 200 日移动平均线附近似乎很脆弱,关注美国零售销售

1、金价预计将巩固其近期跌势&#xff0c;至 6 月初以来的最低水平&#xff1b; 2、对美联储再次加息的押注继续限制了贵金属的上涨&#xff1b; 3、金融市场现在期待美国零售销售报告带来一些有意义的推动&#xff1b; 周二金价难以获得任何有意义的牵引力&#xff0c;并在…