【redis实战篇】第七天

article/2025/6/29 18:33:28

摘要:

        本文介绍了黑马点评中点赞、关注和推送功能的实现方案。点赞功能采用Redis的ZSET结构存储用户点赞数据,实现点赞状态查询、热门博客排行和点赞用户展示。关注功能通过关系表和Redis集合实现用户关注关系管理,包含共同关注查询。推送功能采用推拉结合模式,活跃用户使用推模式实时接收关注用户的内容更新,通过ZSET存储并按时间排序实现滚动分页查询。

 

一,点赞和点赞排行榜功能 

1,根据id查询博客和用户信息

不仅需要查询博客基本信息还要返回用户信息,方便用户知道博客发布者或者关注

    @Overridepublic Result queryBlogById(Long id) {Blog blog = getById(id);if (blog==null) {return Result.fail("博客不存在!");}queryUserById(blog);isBlogLiked(blog);return Result.ok(blog);}

2,分页查询热点博客

查询结果根据点赞数“liked”排序,通过forEach循环查询每个热点博客的发布者

    @Overridepublic Result queryHotBlog(Integer current) {// 根据用户查询Page<Blog> page = query().orderByDesc("liked").page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));// 获取当前页数据List<Blog> records = page.getRecords();// 查询用户和用户是否点赞records.forEach(blog -> {this.isBlogLiked(blog);this.queryUserById(blog);});return Result.ok(records);}

3,判断当前用户是否点赞博客方法

判断用户为空是为了防止用户未登录报错,如果查询的score不为空说明该博客已经被当前用户点过赞

    private void isBlogLiked(Blog blog) {UserDTO user = UserHolder.getUser();if (user==null) {return;}Long userId = UserHolder.getUser().getId();String key = BLOG_LIKED_KEY+blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());blog.setIsLike(score!=null);}

4,点赞博客功能实现

使用redis的zset结构(key value1 score1 value2 score2......)存储博客点赞的用户信息(userId)和时间戳(System.currentTimeMillis())

(1)如果score为空说明未点赞 ,对应博客的点赞数字段+1,同时将当前用户id和时间戳存入redis的zset集合

(2)如果已点赞,点赞数字段-1,同时删除zset集合的对应元素

    @Overridepublic Result likeBlog(Long id) {Long userId = UserHolder.getUser().getId();String key = BLOG_LIKED_KEY+id;Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());//如果未点赞if (score==null) {boolean success = this.update().setSql("liked = liked + 1").eq("id", id).update();if (success) {stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}}else{boolean success = this.update().setSql("liked = liked - 1").eq("id", id).update();if (success) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();}

5,查询点赞时间前五用户信息(userId)

Zrange key 0 4命令查询zset集合根据score(点赞时间)返回前五的value

(1)因为使用stringRedisTemplate所以返回的是Set<String>集合,这里通过stream流的map映射将字符串转化为Long型

(2)使用通用mapper查询用户信息(ListById(集合))的sql语句使用in(2,3,1....)进行查询,但是mysql返回的结果是id有序的结果,与预想不符(先点赞的人先显示)

(3)所以使用order by field(id,id集合字符串),通过StrUtil将id集合转化为“,”分隔的字符串拼接到order by field中,这样就是按照id集合的顺序返回结果

    @Overridepublic Result queryBlogLikes(Long id) {//查询top5的点赞数Zrange key 0 4String key = BLOG_LIKED_KEY+id;Set<String> set = stringRedisTemplate.opsForZSet().range(key, 0, 4);if (set==null||set.isEmpty()) {return Result.ok(Collections.emptyList());}List<Long> listIds = set.stream().map(Long::valueOf).toList();String join = StrUtil.join(",",listIds);List<UserDTO> userDTOList = userService.query().in("id", listIds).last("order by field(id,"+ join +")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).toList();return Result.ok(userDTOList);}

 

二,关注,取关和共同关注

用户和关注的用户之间的联系作为一张表tb_follow表,关注成功将用户id拼接前缀作为key,关注用户id作为元素放到redis的set集合中。

1,判断当前用户是否关注某个用户

    public Result isFollow(Long followUserId) {Long userId = UserHolder.getUser().getId();Long count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();return Result.ok(count > 0);}

2,关注与取关

(1)如果关注,将关注信息(当前用户和关注用户)存入数据库,并且存入redis的set集合中

(2)如果取消关注,根据用户id和关注的拥护的id删除数据,并且清理set中的数据

    public Result follow(Long followUserId, Boolean isFollow) {Long userId = UserHolder.getUser().getId();String key = FOLLOWS_KEY +userId;if (isFollow) {Follow follow = new Follow();follow.setUserId(userId);follow.setFollowUserId(followUserId);boolean success = this.save(follow);if (success) {stringRedisTemplate.opsForSet().add(key, followUserId.toString());}} else {QueryWrapper<Follow> wrapper = new QueryWrapper<>();wrapper.eq("user_id", userId).eq("follow_user_id", followUserId);boolean success = this.remove(wrapper);if (success) {stringRedisTemplate.opsForSet().remove(key, followUserId.toString());}}return Result.ok();}

3,共同关注

(1)通过当前用户关注表的key和该用户关注用户列表的key求共同关注用户id

(2)根据这些共同用户id查询用户信息,最后映射到UserDTO返回

    public Result followCommons(Long id) {Long userId = UserHolder.getUser().getId();String key1 = FOLLOWS_KEY +userId;String key2 = FOLLOWS_KEY +id;//求当前登录用户和目标用户的交集Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);if (intersect == null || intersect.isEmpty()) {return Result.ok("还没有没有共同关注!");}List<Long> list = intersect.stream().map(Long::valueOf).toList();List<UserDTO> userDTOList = userService.listByIds(list).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).toList();return Result.ok(userDTOList);}

 

三,feed流实现推送

(1)拉模式(延时高,内存占用较低)

(2)推模式(实时性强,延时低,内存占用高)

(3)推拉结合模式

动态结合推送和拉取,根据用户状态选择最适合的机制,在用户活跃或需要实时更新时使用推送,其他情况则使用定期拉取。

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

zrevrangebyscore key max min withscores limit 0 3                                滚动查询参数:

max:当前时间戳 | 上一次查询的最小的时间戳,min:0,offset:0 | 上一次查询一样的最小时间戳的个数,count:查询个数

2,发布博客并推送

(1)发布博客时,通过将当前用户id作为被关注id字段,查询笔记作者所有粉丝follows

(2)推送博客id到每一个粉丝收件箱(zset),粉丝id拼接前缀作为key,博客id作为value,且发布时间戳作为score保存到redis中

    public Result saveBlog(Blog blog) {UserDTO user = UserHolder.getUser();blog.setUserId(user.getId());boolean success = save(blog);if (!success) {return Result.fail("新增笔记失败!");}// 查询笔记作者所有粉丝 select userId from tb_follow where follow_user_id = userIdList<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();//推送博客id到每一个粉丝收件箱(zset)for (Follow follow : follows) {Long followId = follow.getUserId();String key = FEED_KEY+followId;stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());}return Result.ok(blog.getId());}

3,获取关注的用户的博客信息并且实现滚动查询

(1)前端发送请求时携带变量max和offset,根据这些参数通过zrevrangebyscore命令返回value和score

Long userId = UserHolder.getUser().getId();
String key = FEED_KEY+userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet().reverseRangeByScoreWithScores(key, 0, max, offset, 2);
if (typedTuples==null||typedTuples.isEmpty()) {return Result.ok();
}

(2)offset默认为1(时间戳最小至少一个),minTime(此次查询的最小时间戳)为0;遍历zset集合取出value即博客id和score即时间戳,如果此次循环的时间戳与最小时间戳相等,累加最小时间戳个数,否则将当前时间戳覆盖最小时间戳并且重置最小时间戳个数为默认值1。

        //解析zset中的offset,minTime和所有博客idint os = 1;long minTime = 0;ArrayList<Long> ids = new ArrayList<>(typedTuples.size());for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {ids.add(Long.valueOf(typedTuple.getValue()));long time = typedTuple.getScore().longValue();if (minTime==time) {os++;} else{minTime = time;os = 1;}}

(3)根据id查询blog,并且需按照ids集合里的数据顺序返回结果,且还需要查询每个博客的用户信息以及是否被当前用户点赞

        String join = StrUtil.join(",",ids);List<Blog> blogs = query().in("id", ids).last("order by field(id," + join + ")").list();//查询每一个博客的用户信息和是否被点赞for (Blog blog : blogs) {queryUserById(blog);isBlogLiked(blog);}

(4)封装ScrollResult并返回前端,下次携带此次信息进行查询

        ScrollResult result = new ScrollResult();result.setList(blogs);result.setOffset(os);result.setMinTime(minTime);return Result.ok(result);


http://www.hkcw.cn/article/MuQmwRPLrW.shtml

相关文章

[yolov11改进系列]基于yolov11引入特征融合注意网络FFA-Net的python源码+训练源码

【FFA-Net介绍】 北大和北航联合提出的FFA-net: Feature Fusion Attention Network for Single Image Dehazing图像增强去雾网络&#xff0c;该网络的主要思想是利用特征融合注意力网络&#xff08;Feature Fusion Attention Network&#xff09;直接恢复无雾图像&#xff0c;…

Baklib领跑三强:知识管理高效优选

Baklib技术架构解析 Baklib的技术底座基于全链路数字化管理理念&#xff0c;通过知识中台的三层架构实现企业级知识资产的深度整合。核心层采用分布式存储引擎与多模态数据处理技术&#xff0c;支持文档、音视频、代码等20格式的智能化解析&#xff0c;确保非结构化数据的精准…

零基础学习计算机网络编程----socket实现UDP协议

本章将会详细的介绍如何使用 socket 实现 UDP 协议的传送数据。有了前面基础知识的铺垫。对于本章的理解将会变得简单。将会从基础的 Serve 的初始化&#xff0c;进阶到 Client 的初始化&#xff0c;以及 run。最后实现一个简陋的小型的网络聊天室。 目录 1.UdpSever.h 1.1 构造…

深入了解linux系统—— 进程间通信之管道

前言 本篇博客所涉及到的代码一同步到本人gitee&#xff1a;testfifo 迟来的grown/linux - 码云 - 开源中国 一、进程间通信 什么是进程间通信 在之前的学习中&#xff0c;我们了解到了进程具有独立性&#xff0c;就算是父子进程&#xff0c;在修改数据时也会进行写时拷贝&…

电脑使用VPN后直接关机,再次打开后无法上网的问题

出现这种问题&#xff0c;都是在使用VPN后&#xff0c;以前自己都是通过杀毒软件的网络修复工具进行解决的。 但现在有了一个更简单的方法&#xff1a; 打开设置&#xff0c;找到网络中的代理,然后关闭即可。

【Linux】线程控制

&#x1f4dd;前言&#xff1a; 这篇文章我们来讲讲Linux——线程控制 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;Linux &#x1f380;CSDN主页 愚润求学 &#x1f304;其他专栏&#xff1a;C学习笔记&#xff0c;C语言入门基础&#xf…

CppCon 2014 学习:Hardening Your Code

“Hardening Your Code” 是指增强代码的健壮性、安全性、可维护性和可测试性&#xff0c;确保在各种边界条件、错误场景甚至恶意输入下&#xff0c;代码依然稳定运行&#xff0c;不崩溃、不泄露资源&#xff0c;也不产生未定义行为。 什么是“Hardening Your Code”&#xff…

【js逆向_AES】某专业技术人员继续教育平台登录分析及模拟实践

目标&#xff1a;account&#xff0c;password加密 网址&#xff1a;aHR0cHM6Ly93d3cuZ3N6eGp5cHguY24vd2ViL2luZGV4 请求载荷加密方式 账号加密&#xff1a; 网页调试输出&#xff1a; python代码&#xff1a; from Cryptodome.Cipher import AES import base64 from Crypto…

《信号与系统》--期末总结V1.0

《信号与系统》–期末总结V1.0 学习链接 入门&#xff1a;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;【拯救期末】期末必备&#xff01;8小时速成信号与系统&#xff01;_哔哩哔哩_bilibili 精通&#xff1a;2022浙江大学信号与系统&#xff08;含配…

可视化大屏通用模板Axure原型设计案例

本文将介绍一款基于Axure设计的可视化大屏通用模板&#xff0c;适用于城市、网络安全、园区、交通、社区、工业、医疗、能源等多个领域。 模板概述 这款Axure可视化大屏通用模板集成了多种数据展示模块和组件&#xff0c;旨在为用户提供一个灵活、可定制的数据展示平台。无论…

AI来敲门:我们该如何与焦虑共舞

最近一份覆盖国内上万职场人的调研报告像一颗深水炸弹&#xff0c;在职场圈激起层层涟漪——85.53%的人担心AI会抢走自己的饭碗&#xff0c;67.57%的人认为这会在五年内发生。更令人意外的是&#xff0c;这些焦虑的职场人中&#xff0c;高达34.13%出现了抑郁症状&#xff0c;这…

单调栈(打卡)

本篇基于b站灵茶山艾府。 下面是灵神上课讲解的题目与课后作业&#xff0c;课后作业还有三道实在写不下去了&#xff0c;下次再写。 739. 每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是…

【C语言入门级教学】冒泡排序和指针数组

文章目录 1.冒泡排序2.⼆级指针3.指针数组4.指针数组模拟⼆维数组 1.冒泡排序 冒泡排序的核⼼思想&#xff1a;两两相邻的元素进⾏⽐较。 //⽅法1 void bubble_sort(int arr[], int sz)//参数接收数组元素个数 { int i 0;for(i0; i-1; i) { int j 0; for(j0; j-1; j) { …

源码解析(三):Stable Diffusion

原文 技术博客 &#x1f600; Stable Diffusion是一种基于扩散模型&#xff08;Diffusion Model&#xff09;的生成式AI技术&#xff0c;通过逐步去噪过程将随机噪声转化为高质量图像。其核心优势在于开源免费、支持本地部署&#xff0c;且能通过文本提示&#xff08;prompt&am…

洛雪音乐+多种音源同步更新,附带安装教程 -【PC端/安卓端】音乐软件

今天&#xff0c;就为大家介绍一款全网免费听歌神器——‌洛雪音乐‌&#xff01; &#x1f3b6; 洛雪音乐&#xff1a;&#xff08;文末获取软件&#xff09; 一、软件亮点 全平台支持‌&#xff1a;无论是Windows系统还是安卓手机&#xff0c;洛雪音乐都能随时伴你左右&am…

【CATIA的二次开发18】根对象Application涉及用户交互相关方法

在CATIA VBA开发中&#xff0c;对根对象Application涉及用户交互相关方法进行详细总结&#xff0c;并且用不同形式展示出来。供大家后续开发全面了解Application对象的方法&#xff0c;以便在开发过程中快速查找和使用&#xff1a; 一、Application常用方法分类 1、基础控制与…

密码学:解析Feistel网络结构及实现代码

概述 Feistel网络是由IBM密码学家Horst Feistel在20世纪70年代提出的对称加密结构&#xff0c;已成为现代分组密码的核心框架。DES、Blowfish、RC5等经典加密算法均基于此结构。其核心思想是将输入明文分组分成左右两半&#xff0c;通过多轮迭代操作实现加密&#xff0c;每轮使…

JavaSE知识总结(集合篇) ~个人笔记以及不断思考~持续更新

目录 集合 List List的各种接口API List的五种遍历方式 List的删除是内部是怎么做的&#xff1f; ArrayList和LinkedList的区别 Vetor和Stack是什么&#xff1f; Set Set的特点 HashSet TreeSet LinkedHashSet Map HashMap LinkedHashMap TreeMap 集合 在Java…

Linux中的mysql备份与恢复

一、安装mysql社区服务 二、数据库的介绍 三、备份类型和备份工具 一、安装mysql社区服务 这是小编自己写的&#xff0c;没有安装的去看看 Linux换源以及yum安装nginx和mysql-CSDN博客 二、数据库的介绍 2.1 数据库的组成 数据库是一堆物理文件的集合&#xff0c;主要包括…

也说字母L:柔软的长舌

英语单词 tongue&#xff0c;意为“舌头” tongue n.舌&#xff0c;舌头&#xff1b;语言 很显然&#xff0c;“语言”是引申义&#xff0c;因为语言是抽象的&#xff0c;但舌头是具象的&#xff0c;根据由简入繁的原则&#xff0c;tongue显然首先是象形起义&#xff0c;表达…