Redis进阶——点赞和点赞排行

目录

  • 发布达人探店笔记
    • 实现步骤
  • 查看探店笔记
  • 点赞功能
    • 问题分析:
    • 功能完善
    • 具体实现
  • 点赞排行榜
    • 实现需求
    • 实现步骤

发布达人探店笔记

实现类似于大众点评的发布个人笔记的效果
20240419-040548-CQ.png

实现步骤

  1. 准备数据表如下:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for tb_blog
-- ----------------------------
DROP TABLE IF EXISTS `tb_blog`;
CREATE TABLE `tb_blog` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`shop_id` bigint(20) NOT NULL COMMENT '商户id',`user_id` bigint(20) unsigned NOT NULL COMMENT '用户id',`title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '标题',`images` varchar(2048) NOT NULL COMMENT '探店的照片,最多9张,多张以","隔开',`content` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '探店的文字描述',`liked` int(8) unsigned DEFAULT '0' COMMENT '点赞数量',`comments` int(8) unsigned DEFAULT NULL COMMENT '评论数量',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=23 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;-- ----------------------------
-- Table structure for tb_blog_comments
-- ----------------------------
DROP TABLE IF EXISTS `tb_blog_comments`;
CREATE TABLE `tb_blog_comments` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',`user_id` bigint(20) unsigned NOT NULL COMMENT '用户id',`blog_id` bigint(20) unsigned NOT NULL COMMENT '探店id',`parent_id` bigint(20) unsigned NOT NULL COMMENT '关联的1级评论id,如果是一级评论,则值为0',`answer_id` bigint(20) unsigned NOT NULL COMMENT '回复的评论id',`content` varchar(255) NOT NULL COMMENT '回复的内容',`liked` int(8) unsigned DEFAULT NULL COMMENT '点赞数',`status` tinyint(1) unsigned DEFAULT NULL COMMENT '状态,0:正常,1:被举报,2:禁止查看',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPACT;SET FOREIGN_KEY_CHECKS = 1;

tb_blog:
探店店笔记表,包含笔记中的标题、文字、图片等

tb_blog_comments:
其他用户对探店笔记的评价

  1. 对应的实体类:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("tb_blog")
public class Blog implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 商户id*/private Long shopId;/*** 用户id*/private Long userId;/*** 用户图标*/@TableField(exist = false)private String icon;/*** 用户姓名*/@TableField(exist = false)private String name;/*** 是否点赞过了*/@TableField(exist = false)private Boolean isLike;/*** 标题*/private String title;/*** 探店的照片,最多9张,多张以","隔开*/private String images;/*** 探店的文字描述*/private String content;/*** 点赞数量*/private Integer liked;/*** 评论数量*/private Integer comments;/*** 创建时间*/private LocalDateTime createTime;/*** 更新时间*/private LocalDateTime updateTime;
}
  1. 发布笔记接口
@PostMapping
public Result saveBlog(@RequestBody Blog blog) {// 获取登录用户UserDTO user = UserHolder.getUser();blog.setUserId(user.getId());// 保存探店博文blogService.save(blog);// 返回idreturn Result.ok(blog.getId());
}
  1. 上传图片的代码
@PostMapping("blog")
public Result uploadImage(@RequestParam("file") MultipartFile image) {try {// 获取原始文件名称String originalFilename = image.getOriginalFilename();// 生成新文件名String fileName = createNewFileName(originalFilename);// 保存文件image.transferTo(new File(SystemConstants.IMAGE_UPLOAD_DIR, fileName));// 返回结果log.debug("文件上传成功,{}", fileName);return Result.ok(fileName);} catch (IOException e) {throw new RuntimeException("文件上传失败", e);}
}

注意:这里需要修改SystemConstants.IMAGE_UPLOAD_DIR 为自己图片所在的地址,在实际开发中图片一般会放在nginx上或者是云存储上。

查看探店笔记

需求:点击首页的探店笔记,会进入详情页面,我们现在需要实现页面的查询接口

20240419-041556-C0.png

实现代码:
BlogController

@RestController
@RequestMapping("/blog")
public class BlogController {@Resourceprivate IBlogService blogService;@PostMappingpublic Result saveBlog(@RequestBody Blog blog) {// 获取登录用户UserDTO user = UserHolder.getUser();blog.setUserId(user.getId());// 保存探店博文blogService.save(blog);// 返回idreturn Result.ok(blog.getId());}@PutMapping("/like/{id}")public Result likeBlog(@PathVariable("id") Long id) {// 修改点赞数量blogService.update().setSql("liked = liked + 1").eq("id", id).update();return Result.ok();}@GetMapping("/of/me")public Result queryMyBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {// 获取登录用户UserDTO user = UserHolder.getUser();// 根据用户查询Page<Blog> page = blogService.query().eq("user_id", user.getId()).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));// 获取当前页数据List<Blog> records = page.getRecords();return Result.ok(records);}@GetMapping("/hot")public Result queryHotBlog(@RequestParam(value = "current", defaultValue = "1") Integer current) {return blogService.queryHotBlog(current);}@GetMapping("/{id}")public Result queryById(@PathVariable Integer id){return blogService.queryById(id);}
}

BlogServiceImpl:

@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {@Resourceprivate IUserService userService;@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(this::queryBlogUser);return Result.ok(records);}@Overridepublic Result queryById(Integer id) {Blog blog = getById(id);if (blog == null) {return Result.fail("评价不存在或已被删除");}queryBlogUser(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());}
}

上述的功能实现是简单的添加和查询的mysql实现,但是功能并不完善,主要是为之后功能加入Redis做准备

点赞功能

需求:点击点赞按钮,查看发送的请求

请求网址: http://localhost:8080/api/blog/like/4
请求方法: PUT

之前BlogController中的like方法,源码如下:

@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {// 修改点赞数量blogService.update().setSql("liked = liked + 1").eq("id", id).update();return Result.ok();
}

问题分析:

这种方式会导致一个用户无限点赞,明显是不合理的
造成这个问题的原因是,我们现在的逻辑,发起请求只是给数据库+1,所以才会出现这个问题

功能完善

需求:

  1. 同一个用户只能对同一篇笔记点赞一次,再次点击则取消点赞
  2. 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)

实现步骤:

  1. 修改点赞功能,利用Redis中的set集合来判断是否点赞过,未点赞则点赞数+1,已点赞则点赞数-1
  2. 修改根据id查询的业务,判断当前登录用户是否点赞过,赋值给isLike字段
  3. 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段

具体实现

  1. Controller层
    具体业务逻辑写在Service层
@PutMapping("/like/{id}")
public Result likeBlog(@PathVariable("id") Long id) {return blogService.likeBlog(id);
}
  1. BlogServiceImpl业务实现
    在BlogService接口中创建对应方法,在Impl中实现
@Override
public Result likeBlog(Long id) {//1. 获取当前用户信息Long userId = UserHolder.getUser().getId();//2. 如果当前用户未点赞,则点赞数 +1,同时将用户加入set集合String key = BLOG_LIKED_KEY + id;Boolean isLiked = stringRedisTemplate.opsForSet().isMember(key, userId.toString());if (BooleanUtil.isFalse(isLiked)) {//点赞数 +1boolean success = update().setSql("liked = liked + 1").eq("id", id).update();//将用户加入set集合if (success) {stringRedisTemplate.opsForSet().add(key, userId.toString());}//3. 如果当前用户已点赞,则取消点赞,将用户从set集合中移除}else {//点赞数 -1boolean success = update().setSql("liked = liked - 1").eq("id", id).update();if (success){//从set集合移除stringRedisTemplate.opsForSet().remove(key, userId.toString());}}return Result.ok();
}

修改完毕之后,页面上还不能立即显示点赞完毕的后果,我们还需要修改查询Blog业务,判断Blog是否被当前用户点赞过

  1. 查询业务修改
@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 -> {queryBlogUser(blog);//追加判断blog是否被当前用户点赞,逻辑封装到isBlogLiked方法中isBlogLiked(blog);});return Result.ok(records);
}@Override
public Result queryById(Integer id) {Blog blog = getById(id);if (blog == null) {return Result.fail("评价不存在或已被删除");}queryBlogUser(blog);//追加判断blog是否被当前用户点赞,逻辑封装到isBlogLiked方法中isBlogLiked(blog);return Result.ok(blog);
}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. 如果点赞了,则将isLike设置为trueblog.setIsLike(BooleanUtil.isTrue(isMember));
}

至此通过简单的Redis的Set数据结构实现了点赞的功能,set的键值为"blogliked:笔记id",值为点赞的用户id。

点赞排行榜

实现需求

当我们点击探店笔记详情页面时,应该按点赞顺序展示点赞用户,比如显示最早点赞的TOP5,形成点赞排行榜,就跟QQ空间发的说说一样,可以看到有哪些人点了赞。
之前的点赞是放到Set集合中,但是Set集合是无序的存储的,所以这个时候,我们就可以改用SortedSet(Zset)

对比一下这些集合的区别:
20240419-043552-fX.png

实现步骤

  1. 修改BlogServiceImpl
    由于ZSet没有isMember方法,所以这里只能通过查询score来判断集合中是否有该元素,如果有该元素,则返回值是对应的score,如果没有该元素,则返回值为null
@Override
public Result likeBlog(Long id) {//1. 获取当前用户信息Long userId = UserHolder.getUser().getId();//2. 如果当前用户未点赞,则点赞数 +1,同时将用户加入set集合String key = BLOG_LIKED_KEY + id;//尝试获取scoreDouble score = stringRedisTemplate.opsForZSet().score(key, userId.toString());//为null,则表示集合中没有该用户if (score == null) {//点赞数 +1boolean success = update().setSql("liked = liked + 1").eq("id", id).update();//将用户加入set集合if (success) {stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}//3. 如果当前用户已点赞,则取消点赞,将用户从set集合中移除} else {//点赞数 -1boolean success = update().setSql("liked = liked - 1").eq("id", id).update();if (success) {//从set集合移除stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();
}
  1. 同时修改isBlogLiked方法,在原有逻辑上,判断用户是否已登录,登录状态下才会继续判断用户是否点赞
private void isBlogLiked(Blog blog) {//1. 获取当前用户信息UserDTO userDTO = UserHolder.getUser();//当用户未登录时,就不判断了,直接return结束逻辑if (userDTO == null) {return;}//2. 判断当前用户是否点赞String key = BLOG_LIKED_KEY + blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userDTO.getId().toString());blog.setIsLike(score != null);
}
  1. 继续完善显示点赞列表功能,查看浏览器请求,这个请求目前应该是404的,因为我们还没有写,他需要一个list返回值,显示top5点赞的用户

请求网址: http://localhost:8080/api/blog/likes/4
请求方法: GET

在Controller层中编写对应的方法,点赞查询列表,具体逻辑写到BlogServiceImpl中

@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;//zrange key 0 4  查询zset中前5个元素Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);//如果是空的(可能没人点赞),直接返回一个空集合if (top5 == null || top5.isEmpty()) {return Result.ok(Collections.emptyList());}List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());//将ids使用`,`拼接,SQL语句查询出来的结果并不是按照我们期望的方式进行排//所以我们需要用order by field来指定排序方式,期望的排序方式就是按照查询出来的id进行排序String idsStr = StrUtil.join(",", ids);//select * from tb_user where id in (ids[0], ids[1] ...) order by field(id, ids[0], ids[1] ...)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);
}

需要注意的点是:zrange key 0 4 查询zset中前5个元是我们需要的已经排好序的用户id,但是使用该list拼接sql使用in(,)的时候,返回的结果并不是按照我们期望的方式进行排序,所以我们需要用order by field来指定排序方式,期望的排序方式就是按照查询出来的id进行排序

select * from tb_user where id in (ids[0], ids[1] ...) order by field(id, ids[0], ids[1] ...)

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

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

相关文章

与助听器相关的职业主要有哪些?

助听装置是目前解决听觉障碍的几乎唯一科学的方法。然而助听装置改变的不是听力&#xff0c;而是外界的声音信息。也就是助听装置只能将外界的声音信息改变成能够适应听觉障碍患者听觉动态范围的声音。显然助听器并不知道听觉障碍患者的听觉动态范围是多少&#xff1f;也不知道…

分布式系统——全站监控

文章目录 全站监控要点**监控范围与对象****监控指标与数据****监控工具与技术****监控策略与实践****全站监控的价值** 实例展示 全站监控要点 全站监控是针对分布式架构中所有组件和服务进行全方位、多层次、实时的性能监控、状态检测和故障告警的系统化方法。在分布式环境下…

PMP每年考几次,费用如何?

今年的的考试分别分布在3月、6月、8月、11月&#xff0c;一般来说PMP的考试时间是3、6、9、12月&#xff0c;如果有特殊情况PMI也会及时进行调整&#xff0c;具体看他们官网的通知了。 PMP的考试费用全球是统一的&#xff0c;在国内考试报名费用是3900元&#xff0c;如果考试没…

【学习】自动化测试有哪些优势和不足

在当今这个数字化时代&#xff0c;软件测试已经成为了任何一款产品成功的关键因素之一。而在诸多的测试方法中&#xff0c;自动化测试凭借着其独特的魅力吸引着越来越多的企业。今天就让我们一起走进自动化测试的世界&#xff0c;探讨它的优势与不足。 一、自动化测试优势 1.…

你的mongodb客户端是哪个呢?

MongoDB 是一种流行的文档数据库&#xff0c;它可以支持多种场景和应用。有很多客户端工具可以用来管理和操作 MongoDB&#xff0c;以下是一些常用的工具&#xff0c;以及它们的介绍&#xff1a; 一、MongoDB Shell MongoDB Shell 是连接&#xff08;和使用&#xff09;MongoD…

追溯历史:SIEM 中的生成式人工智能革命

作者&#xff1a;来自 Elastic Mike Nichols, Mike Paquette 网络安全领域仿佛是现实世界的一个映射&#xff0c;安全运营中心&#xff08;security operation center - SOC&#xff09;就像是你的数字警察局。网络安全分析师就像是警察&#xff0c;他们的工作是阻止网络犯罪分…

数据中台规划与建设方案PPT(建议收藏)

推荐书籍&#xff1a; 《分布式商业生态战略&#xff1a;数字商业新逻辑与企业数字化转型新策略》 书籍介绍&#xff1a; 本书从新时代商业环境出发&#xff0c;紧随市场热点&#xff0c;如元宇宙、Web 3.0、资产数字化、反垄断、要素市场化配置、分布式自治组织&#xff08;d…

数据仓库作业五:第8章 关联规则挖掘

目录 第8章 关联规则挖掘作业题 第8章 关联规则挖掘 作业题 1、设4-项集 X { a , b , c , d } X\{a,b,c,d\} X{a,b,c,d}&#xff0c;试求出由 X X X 导出的所有关联规则。 解&#xff1a; 首先生成项集的所有非空真子集。这包括&#xff1a; { a } , { b } , { c } , {…

UE5集成gRPC

最近有项目需要在UE5里做RPC&#xff0c;对比了thrift、gRPC、rcplib等开源rpc框架&#xff0c;由于习惯使用protobuf&#xff0c;故选择了gRPC。然而&#xff0c;Google出品也是一言难尽啊&#xff0c;最起码编译太繁琐了。 本次使用的gRPC版本为1.62.1&#xff0c;UE5.2&…

基于机器学习的车辆状态异常检测

基于马氏距离的车辆状态异常检测&#xff08;单一传感器&#xff09; 基于多元自动编码器的车辆状态异常检测 基于单传感器平滑马氏距离的车辆状态异常检测 工学博士&#xff0c;担任《Mechanical System and Signal Processing》等期刊审稿专家&#xff0c;擅长领域&#xff1…

【加密周报】中东“惊雷”炸响币圈!比特币减半成功完成,市场情绪已被提前消化!中美突传USDT重磅消息!

周五(4月19日)&#xff0c;比特币经历惊魂一刻&#xff0c;伊朗核设施所处的中部城市伊斯法罕惊传爆炸&#xff0c;叙利亚与伊拉克也都传来爆炸声响&#xff0c;中东全面战争与核武攻击威胁触发加密市场恐慌情绪。比特币一度下探59600美元。但随后伊朗强调核设施未受损&#xf…

组件安全(Solr、Shiro、Log4j、Jackson、FastJson、XStream)

Solr 主要基于HTTP和 Apache Lucene 实现的全文搜索服务器。 特征&#xff1a;图标识别 端口&#xff1a;8393 CVE-2019-0193&#xff08;远程命令执行漏洞&#xff09; 漏洞版本&#xff1a;Apache Solr < 8.2.0 利用条件&#xff1a; Apache Solr 的 DataImportHandler 启…

二维码门楼牌管理应用平台建设:构建智能社区治理新模式

文章目录 前言一、二维码门楼牌管理应用平台的意义二、走访日志功能的重要性三、走访日志功能的具体应用四、走访日志功能的优势五、结语 前言 在数字化浪潮下&#xff0c;社区管理正面临着前所未有的机遇与挑战。二维码门楼牌管理应用平台的建设&#xff0c;不仅为社区治理提…

代码随想录训练营Day 27|Python|Leetcode|122.买卖股票的最佳时机II ● 55. 跳跃游戏 ● 45.跳跃游戏II

122.买卖股票的最佳时机II 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获…

fastjson转换json时默认将属性第一个字母转小写

描述&#xff1a; 我新建了一个实体类&#xff0c;但是实体类的首字母是大写的&#xff0c;但是使用fastjson后打印的&#xff0c;Json字符串首字母却是小写的&#xff0c;这是fastjson的一个bug 实体类&#xff1a; Json字符串&#xff1a; 解决方法&#xff1a; 一、使…

HoloLens2的Unity应用在电脑上发布成安装包,然后通过wifi安装到设备

一、VS工程中的鼠标右键 二、发布——>创建应用程序包 三、选择【旁加载】 四、选择签名方法&#xff1a; 五、选择和配置包 六、创建完毕 七、网络连接设备 八、登录设备 九、安装app

六边形酷科技特效单页源码

源码介绍 基于canvas画布绘制多个六边形追踪鼠标&#xff0c;科技感的几何图形酷炫动画特效&#xff0c; 单页html源码&#xff0c;可以做网站动态背景&#xff0c;喜欢的朋友可以拿去 效果截图 完整源码 <!doctype html> <html> <head> <meta charset…

项目7-音乐播放器3(删除模块+播放音乐模块设计)

1.播放音乐模块设计 1.1 请求响应设计 请求&#xff1a; { get, /music/get?pathxxx.mp3 } 响应&#xff1a; { 音乐数据本身的字节信息 } 1.2 后端代码 1. Files.readAllBytes(String path) : 读取文件中的所有字节&#xff0c;读入内存 &#xff…

SpringBoot集成FTP

1.加入核心依赖 <dependency><groupId>commons-net</groupId><artifactId>commons-net</artifactId><version>3.8.0</version></dependency> 完整依赖 <dependencies><dependency><groupId>org.springfra…

蛋白质亚细胞定位预测(生物信息学工具-017)

直奔主题&#xff0c;下面这张表图怎么制作&#xff0c;一般都是毕业论文hh&#xff0c;蛋白质的亚细胞定位如何预测&#xff1f; 01 方法 https://wolfpsort.hgc.jp/ #官网小程序&#xff0c;简单好用&#xff0c;不用R包&#xff0c;python包&#xff0c;linux程序&#x…