黑马点评-10实现用户点赞和点赞排行榜功能
用户点赞功能
如果用户只要点赞一次就对数据库中blog表中的liked字段的值加1就会导致一个用户无限点赞
PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {// 修改点赞数量,update tb_blog set liked = liked + 1 where id = ? blogService.update().setSql("liked = liked + 1").eq("id", id).update();return Result.ok();
}
需求: 同一个用户只能对同一篇笔记点赞一次再次点击则取消点赞,如果当前用户已经点赞则点赞按钮高亮显示
增加isLike字段: 给Blog实体类中添加一个isLike字段,首页查询热门笔记或用户查看笔记详情内容时会根据isLike字段的属性值决定点赞按钮是否高亮显示点赞修改功能: 利用Redis中的set集合是否包含点赞用户的Id来判断用户是否点赞过,未点赞则点赞数+1,已点赞则点赞数-1查看笔记详情时根据id查询笔记: 判断当前登录用户是否点赞过,赋值给isLike字段决定点赞图标是否高亮显示访问首页时分页查询热门笔记: 判断当前登录用户是否点赞过,赋值给isLike字段决定点赞图标是否高亮显示

一人一赞
第一步: 给Blog实体类增加isLike字段,首页查询热门笔记或用户查看笔记详情内容时会根据isLike字段的属性值决定点赞按钮是否高亮显示
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_blog")
public class Blog implements Serializable {private static final long serialVersionUID = 1L;// isLike属性不属于Blog表中的字段@TableField(exist = false)private Boolean isLike;//..........
}
第二步: 编写控制器方法处理用户点赞的请求
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {return blogService.likeBlog(id);
}
第三步: 编写业务方法,以blog:liked:笔记Id作为Set集合的Key,集合中的元素就是点赞用户的Id
// 在RedisConstants类中声明一个常量作为Set集合的key,集合中包含了所有点赞的用户
public static final String BLOG_LIKED_KEY = "blog:liked:";
// 操作Redis,key和value要求是String类型
@Resource
private StringRedisTemplate stringRedisTemplate;@Override
public Result likeBlog(Long id) {//1. 获取当前登陆用户信息Long userId = UserHolder.getUser().getId();//2. 判断当前用户是否已经点赞//2.1如果用户未点赞则Blog表中like字段值加1,同时将点赞用户的Id加入set集合String key = BLOG_LIKED_KEY + id;Boolean isLiked = stringRedisTemplate.opsForSet().isMember(key, userId.toString());if (BooleanUtil.isFalse(isLiked)) {// update tb_blog set liked = liked + 1 where id = ?boolean success = update().setSql("liked = liked + 1").eq("id", id).update();// 更新成功将点赞用户Id加入set集合if (success) {stringRedisTemplate.opsForSet().add(key, userId.toString());}//2.2如果当前用户已点赞则取消点赞即like字段值减1,同时将点赞用户Id从set集合中移除}else {// update tb_blog set liked = liked - 1 where id = ?boolean success = update().setSql("liked = liked - 1").eq("id", id).update();if (success){// 更新成功则将点赞用户Id从set集合移除stringRedisTemplate.opsForSet().remove(key, userId.toString());}}return Result.ok();
}
修改查询笔记点赞高亮
访问首页时分页查询热门笔记: 判断当前登录用户是否点赞过,根据Blog实体类isLike属性的值决定点赞图标是否高亮显示
@Override
public 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 -> {// 查询用户昵称和头像封装到Blog实体类当中queryBlogUser(blog);// 判断笔记是否被当前用户点赞isBlogLiked(blog);});return Result.ok(records);
}
查看笔记详情时根据id查询笔记: 判断当前登录用户是否点赞过,根据Blog实体类isLike属性的值决定点赞图标是否高亮显示
@Override
public Result queryBlogById(Integer id) {Blog blog = getById(id);if (blog == null) {return Result.fail("评价不存在或已被删除");}// 查询用户昵称和头像封装到Blog实体类当中queryBlogUser(blog);// 判断笔记是否被当前用户点赞isBlogLiked(blog);return Result.ok(blog);
}
由于查看用户信息和判断笔记是否被当前用户点赞的业务逻辑比较通用,所以抽取成独立的方法
// 查看用户信息
private void queryBlogUser(Blog blog) {Long userId = blog.getUserId();User user = userService.getById(userId);blog.setName(user.getNickName());blog.setIcon(user.getIcon());
}// 判断用户是否已经点赞
private void isBlogLiked(Blog blog) {//1. 获取当前用户信息Long userId = UserHolder.getUser().getId();//2. 判断当前用户是否点赞String key = BLOG_LIKED_KEY + blog.getId();Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());//3. 如果点赞了则将Blog类的isLike属性设置为trueblog.setIsLike(BooleanUtil.isTrue(isMember));
}
点赞排行榜
需求: 当我们点击探店笔记详情页面时,应该按点赞顺序展示点赞过的用户,比如显示最早点赞的TOP5形成点赞排行榜

因为Set集合不能对点赞的用户进行排序,所以我们需要使用SortedSet(Zset)集合存储点赞的用户Id,score属性的值是当前时间戳(默认按照从小到大排序)

第一步: ZSet没有判断元素是否存在的方法,是通过获取集合中元素的score属性的值来判断集合中是否有该元素
ZSCORE key e1: 获取集合元素的score属性值,若元素存在则返回对应score值,若不存在则返回null
@Override
public Result likeBlog(Long id) {//1. 获取当前登陆用户信息Long userId = UserHolder.getUser().getId();//2. 判断当前用户是否已经点赞//2.1如果用户未点赞则Blog表中like字段值加1,同时将点赞用户的Id加入set集合String key = BLOG_LIKED_KEY + id;// 尝试获取当前用户的score属性值Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());// 如果score为null则表示集合中没有该用户if (score == null) {// update tb_blog set liked = liked + 1 where id = ?boolean success = update().setSql("liked = liked + 1").eq("id", id).update();//更新成功则将点赞用户Id加入SortedSet集合,score属性的值就是当前的时间戳(默认按照从小到大排序)if (success) {stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}//2.2如果当前用户已点赞则取消点赞即like字段值减1,同时将点赞用户Id从SortedSet集合中移除} else {// update tb_blog set liked = liked - 1 where id = ?boolean success = update().setSql("liked = liked - 1").eq("id", id).update();if (success) {//更新成功将点赞用户Id从SortedSet集合移除stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();
}
第二步: 判断用户是否点赞时如果用户没有登录就不需要判断用户是否点过赞了,因为用户没有登陆就获取不到userId此时会报空指针异常
private void isBlogLiked(Blog blog) {//1. 获取当前用户信息UserDTO userDTO = UserHolder.getUser();//2. 当用户未登录时就不判断用户是否点赞,直接return结束逻辑if (userDTO == null) {return;}//3. 判断当前用户是否点赞String key = BLOG_LIKED_KEY + blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userDTO.getId().toString());// score不等于null返回true表示用户已经点过赞同时给Blog类的isLike属性赋值trueblog.setIsLike(score != null);
}
第三步: 显示点赞排行列表当浏览器发起GET请求blog/likes/4时服务器返回一个List集合包含top5点赞的用户信息
ZRANGE key start end: 获取指定范围的元素,SortedSet内的元素会自动排序(默认升序)
@GetMapping("/likes/{id}")
public Result queryBlogLikes(@PathVariable Integer id){return blogService.queryBlogLikes(id);
}
@Override
public Result queryBlogLikes(Integer id) {String key = BLOG_LIKED_KEY + id;// 查询SortedSet集合的前5个元素即用户的id(不包含元素的分数)Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);// 如果返回的set集合是空的即没人点赞,直接返回一个空的List集合if (top5 == null || top5.isEmpty()) {return Result.ok(Collections.emptyList());}// 将Set集合中String类型的用户id转变为Long类型的id然后收集到List集合中List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());//select * from tb_user where id in (ids[0],...) order by field(id, ids[0],...)String idsStr = StrUtil.join(",", ids);// 把ids集合拼成一个以","分隔的字符串List<UserDTO> userDTOS = userService.query().in("id", ids).last("order by field(id," + idsStr + ")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))// 将查询出来的用户隐私信息隐藏.collect(Collectors.toList());return Result.ok(userDTOS);
}
由于MySQL默认会对查询出来的所有结果按照id从小到大的方式排序,它并不会按照我们查询到的用户Id顺序去排序
order by field(排序字段,排序顺序): 可以指定查询结果按照某个字段的排序方式
select * from tb_user where id in (ids[0], ids[1] ...): 这种方式并不会按照Set集合中Id的顺序对查询结果进行排序
List<UserDTO> userDTOS = userService.listByIds(ids).steram().map(user -> BeanUtil.copyProperties(user, UserDTO.class))// 将查询出来的用户隐私信息隐藏.collect(Collectors.toList());
select * from tb_user where id in (ids[0],...) order by field(id, ids[0],...): 指定查询结果按照我们指定的字段顺序排序
// 把ids集合拼成一个以","分隔的字符串
String idsStr = StrUtil.join(",", ids);
List<UserDTO> userDTOS = userService.query().in("id", ids).last("order by field(id," + idsStr + ")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class))// 将查询出来的用户隐私信息隐藏.collect(Collectors.toList());
相关文章:
黑马点评-10实现用户点赞和点赞排行榜功能
用户点赞功能 如果用户只要点赞一次就对数据库中blog表中的liked字段的值加1就会导致一个用户无限点赞 PutMapping("/like/{id}") public Result likeBlog(PathVariable("id") Long id) {// 修改点赞数量,update tb_blog set liked liked 1 where id …...
Spring配置其他注解Spring注解的解析原理
Spring配置其他注解 Primary注解用于标注相同类型的Bean优先被使用权,Primary是Spring 3.0引入的,与Component和Bean一起使用,标注该Bean的优先级更高,则在通过类型获取Bean或通过Autowired根据类型进行注入时,会选用优…...
TypeScript 学习笔记 第一部分 语法基础
【视频链接】尚硅谷TypeScript教程(李立超老师TS新课) TypeScript 1. 类型1.1 | 联合类型1.2 字面量类型1.3 any 任意类型1.4 unkown 类型1.5 as 类型断言1.6 object 对象类型1.7 { } 对象类型1.8 ? 对象中的可选属性1.9 对象中的任意属性1.1…...
【element优化经验】怎么让element-ui中表单多语言切换排版不乱
目录 前言: 痛点: 1.左对齐,右对齐在中文和外语情况下字数不同,固定宽度会使名称换行,不在整行对齐,影响美观。 2.如果名称和输入框不在一行,会使页面越来越长 3.label-width值给变量&#…...
软件设计中如何画各类图之一实体关系图(ER图):数据库设计与分析的核心工具
目录 1 前言2 符号及作用:3 绘制清晰的ER图步骤4 实体关系图的用途5 使用场景6 实际应用场景举例7 结语 1 前言 当谈到数据库设计与分析的核心工具时,实体关系图(ER图)无疑是其中最重要的一环。在软件开发、信息管理以及数据库设…...
【神印王座】龙皓晨美妆胜过月夜,魔神皇识破无视,撮合月夜阿宝
Hello,小伙伴们,我是拾荒君。 《神印王座》国漫第82集已更新,拾荒君和大多数人一样,更新就去看了。魔神皇枫秀,威严凛然,突然空降月魔宫,整个宫殿都在这股无与伦比的强大气息中颤栗。为了顺利躲避魔神皇的…...
汽车级全保护型六路半桥驱动器NCV7708FDWR2G 原理、参数及应用
NCV7708FDWR2G 是一款全保护型六路半桥驱动器,特别适用于汽车和工业运动控制应用。六个高压侧和低压侧驱动器可自由配置,也可单独控制。因此可实现高压侧、低压侧和 H 桥控制。H 桥控制提供正向、逆向、制动和高阻抗状态。驱动器通过标准 SPI 接口进行控…...
【小技巧】MyBatis 中 SQL 写法技巧小总结
最近有个兄弟在搞mybatis,问我怎么写sql ,说简单一点mybatis就是写原生sql,官方都说了 mybatis 的动态sql语句是基于 OGNL表达式的。可以方便的在 sql 语句中实现某些逻辑. 总体说来mybatis 动态SQL 语句主要有以下几类: if 语句 (简单的条件…...
C#编程题分享(4)
换行输出整数问题 输⼊任意⼀个位数未知的整数,输出这个数每⼀位上的数字。输出的时候,从个位开始输出,每输出⼀个数字换⼀⾏。样例输⼊:3547 输出:7 换行输出 4 换行输出5 换行输出3 int n Convert.ToInt32(Conso…...
CTF靶场搭建及Web赛题制作与终端docker环境部署
♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ ♡ ♥ 写在前面 ╔═══════════════════════════════════════════════════…...
nodejs express vue uniapp新闻发布系统源码
开发技术: node.js,mysql5.7,vscode,HBuilder nodejs express vue uniapp 功能介绍: 用户端: 登录注册 首页显示搜索新闻,新闻分类,新闻列表 点击新闻进入新闻详情࿰…...
FastText模型文本分类
项目地址:NLP-Application-and-Practice/07_FastText/7.2-FastText文本分类/text_classification at master zz-zik/NLP-Application-and-Practice (github.com) 加载数据 load_data.py # coding: UTF-8 import os import pickle as pkl from tqdm import tqdmMA…...
CentOS 7 使用Fmt库
安装 fmt Git下载地址:https://github.com/fmtlib/fmt 步骤1:首先,你需要下载fmt的源代码。你可以从https://github.com/fmtlib/fmt或者源代码官方网站下载。并上传至/usr/local/source_code/ 步骤2:下载完成后ÿ…...
如何通过宝塔面板搭建一个本地MySQL数据库服务并实现远程访问
宝塔安装MySQL数据库,并内网穿透实现公网远程访问 文章目录 宝塔安装MySQL数据库,并内网穿透实现公网远程访问前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网…...
普通话考试相关(一文读懂)
文章目录: 一:相关常识 1.考试报名时间 2.报名地方 费用 证件 3.考试流程 4.普通话等级说明 二:题型 三:技巧 1.前三题 2.命题说话 四:普通话考试题库 1.在线题库 2.下载题库 一:相关常识 …...
深度学习动物识别 - 卷积神经网络 机器视觉 图像识别 计算机竞赛
文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…...
【Redisson】基于自定义注解的Redisson分布式锁实现
前言 在项目中,经常需要使用Redisson分布式锁来保证并发操作的安全性。在未引入基于注解的分布式锁之前,我们需要手动编写获取锁、判断锁、释放锁的逻辑,导致代码重复且冗长。为了简化这一过程,我们引入了基于注解的分布式锁&…...
QT中样式表常见属性与颜色的设置与应用
常见样式表属性 在Qt中的样式表(QSS)中,有一些特定的英文单词和关键字用于指定不同的样式属性。以下是常见的一些英文单词和关键字: 颜色(Colors): color: 文本颜色 background-color: 背景颜色 border-color: 边框颜色 字体(Fonts): font: 字体 font-family: 字体…...
OpenCvSharp从入门到实践-(02)图像处理的基本操作
目录 图像处理的基础操作 1、读取图像 1.1、读取当前目录下的图像 2、显示图像 2.1、Cv2.ImShow 用于显示图像。 2.2、Cv2.WaitKey方法用于等待用户按下键盘上按键的时间。 2.3、Cv2.DestroyAllWindows方法用于销毁所有正在显示图像的窗口。 2.4实例1-显示图像 2.4实例…...
Spring Boot 升级3.x 指南
Spring Boot 升级3.x 指南 1. 升级思路 先创建一个parent项目,打包类型为pom,继承自spring boot的parent项目 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId&…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
安宝特案例丨Vuzix AR智能眼镜集成专业软件,助力卢森堡医院药房转型,赢得辉瑞创新奖
在Vuzix M400 AR智能眼镜的助力下,卢森堡罗伯特舒曼医院(the Robert Schuman Hospitals, HRS)凭借在无菌制剂生产流程中引入增强现实技术(AR)创新项目,荣获了2024年6月7日由卢森堡医院药剂师协会࿰…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
在Zenodo下载文件 用到googlecolab googledrive
方法:Figshare/Zenodo上的数据/文件下载不下来?尝试利用Google Colab :https://zhuanlan.zhihu.com/p/1898503078782674027 参考: 通过Colab&谷歌云下载Figshare数据,超级实用!!࿰…...
raid存储技术
1. 存储技术概念 数据存储架构是对数据存储方式、存储设备及相关组件的组织和规划,涵盖存储系统的布局、数据存储策略等,它明确数据如何存储、管理与访问,为数据的安全、高效使用提供支撑。 由计算机中一组存储设备、控制部件和管理信息调度的…...
