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

2024-04-20 19:20
文章标签 进阶 redis 排行 点赞

本文主要是介绍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] ...)

这篇关于Redis进阶——点赞和点赞排行的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/921108

相关文章

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

深度解析Python装饰器常见用法与进阶技巧

《深度解析Python装饰器常见用法与进阶技巧》Python装饰器(Decorator)是提升代码可读性与复用性的强大工具,本文将深入解析Python装饰器的原理,常见用法,进阶技巧与最佳实践,希望可... 目录装饰器的基本原理函数装饰器的常见用法带参数的装饰器类装饰器与方法装饰器装饰器的嵌套与组合进阶技巧

Redis的持久化之RDB和AOF机制详解

《Redis的持久化之RDB和AOF机制详解》:本文主要介绍Redis的持久化之RDB和AOF机制,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录概述RDB(Redis Database)核心原理触发方式手动触发自动触发AOF(Append-Only File)核

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模

SpringBoot连接Redis集群教程

《SpringBoot连接Redis集群教程》:本文主要介绍SpringBoot连接Redis集群教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 依赖2. 修改配置文件3. 创建RedisClusterConfig4. 测试总结1. 依赖 <de

SpringBoot+Redis防止接口重复提交问题

《SpringBoot+Redis防止接口重复提交问题》:本文主要介绍SpringBoot+Redis防止接口重复提交问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录前言实现思路代码示例测试总结前言在项目的使用使用过程中,经常会出现某些操作在短时间内频繁提交。例

Redis 配置文件使用建议redis.conf 从入门到实战

《Redis配置文件使用建议redis.conf从入门到实战》Redis配置方式包括配置文件、命令行参数、运行时CONFIG命令,支持动态修改参数及持久化,常用项涉及端口、绑定、内存策略等,版本8... 目录一、Redis.conf 是什么?二、命令行方式传参(适用于测试)三、运行时动态修改配置(不重启服务

浅析如何保证MySQL与Redis数据一致性

《浅析如何保证MySQL与Redis数据一致性》在互联网应用中,MySQL作为持久化存储引擎,Redis作为高性能缓存层,两者的组合能有效提升系统性能,下面我们来看看如何保证两者的数据一致性吧... 目录一、数据不一致性的根源1.1 典型不一致场景1.2 关键矛盾点二、一致性保障策略2.1 基础策略:更新数

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚