黑马点评-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&…...
实时直播翻译神器:用Stream-Translator打破语言壁垒
实时直播翻译神器:用Stream-Translator打破语言壁垒 【免费下载链接】stream-translator 项目地址: https://gitcode.com/gh_mirrors/st/stream-translator 你是否曾因语言障碍而错过精彩的国际直播?无论是热门游戏赛事、外语教学课程还是国际新…...
告别安装烦恼:用 pyenv-win 在 Windows 上轻松管理多个Python版本(3.10.6/3.11/3.12自由切换)
告别安装烦恼:用 pyenv-win 在 Windows 上轻松管理多个Python版本(3.10.6/3.11/3.12自由切换) 你是否遇到过这样的场景:手头同时维护着基于Python 3.10.6的旧项目和采用Python 3.12新特性的开发任务?每次切换项目都要反…...
从 0 到 1 搭建客服 AI Agent Harness Engineering:意图识别、知识检索与对话管理完整实战
从 0 到 1 搭建客服 AI Agent Harness Engineering:意图识别、知识检索与对话管理完整实战 副标题: 基于 LangChain FastAPI Chroma Redis 构建高可用、低幻觉的 SaaS 级智能客服原型摘要/引言 问题陈述 你是否遇到过这样的场景: 公司官网…...
QVAC Genesis II:教育领域LLM预训练的高质量合成数据集
1. 项目概述 QVAC Genesis II是一个专注于为大型语言模型(LLM)预训练提供高质量多领域教育合成数据集的扩展项目。作为原始QVAC Genesis数据集的升级版本,它目前保持着同类型数据集中规模最大、质量最高的记录。这个项目特别针对教育领域的LLM训练需求,通…...
深圳GEO优化全科普:选型逻辑与本地服务商参考
据AI营销行业实操统计(来源:深圳万拓营销2026年本地企业服务数据),深圳10-200人中小微企业中,有68%存在传统SEO效果下滑、AI搜索品牌曝光缺失的问题,获客成本较行业均值高出35%。作为AI搜索时代的精准获客手…...
终极指南:如何快速将网页转换为可编辑的Figma设计稿
终极指南:如何快速将网页转换为可编辑的Figma设计稿 【免费下载链接】figma-html Convert any website to editable Figma designs 项目地址: https://gitcode.com/gh_mirrors/fi/figma-html 你是否曾经想要将任何网站的设计快速转换为Figma中的可编辑图层&a…...
如何在Mac上免费实现NTFS完美读写?终极解决方案来了!
如何在Mac上免费实现NTFS完美读写?终极解决方案来了! 【免费下载链接】Free-NTFS-for-Mac Nigate: An open-source NTFS utility for Mac. It supports all Mac models (Intel and Apple Silicon), providing full read-write access, mounting, and man…...
NCCL拓扑发现与Channel搜索:你的多GPU训练效率,可能就由这俩算法决定
NCCL拓扑发现与Channel搜索:多GPU训练效率的核心算法解析 在分布式深度学习训练中,NCCL(NVIDIA Collective Communications Library)作为GPU间通信的事实标准,其底层算法设计直接影响着多机多卡训练的效率。许多工程师…...
League Akari:如何用本地化智能工具提升英雄联盟游戏体验
League Akari:如何用本地化智能工具提升英雄联盟游戏体验 【免费下载链接】League-Toolkit An all-in-one toolkit for LeagueClient. Gathering power 🚀. 项目地址: https://gitcode.com/gh_mirrors/le/League-Toolkit 在英雄联盟的竞技对局中&…...
LiveAutoRecord技术深度解析:如何实现跨平台直播自动录制的模块化架构
LiveAutoRecord技术深度解析:如何实现跨平台直播自动录制的模块化架构 【免费下载链接】LiveAutoRecord 基于 Electron 的多平台直播自动录制软件 项目地址: https://gitcode.com/GitHub_Trending/li/LiveAutoRecord 在直播内容生态日益繁荣的今天࿰…...
