当前位置: 首页 > news >正文

Redis--黑马点评--达人探店功能实现详解

达人探店

发布探店笔记

探店笔记类似于点评网站的评价,往往是图文结合,对应的表有两个:

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

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

tb_blog表结构如下:

image-20250702181240618

可以从结构中看出,跟发布探店笔记相关的字段有shop_id,title,images,content,liked,comments

业务流程:

点击首页最下方菜单栏的+按钮,即可发布探店图文:

image-20250702181724959

image-20250702181739831

需要注意的是发布照片与发布笔记是两个分离的功能,因为上传照片的功能不仅仅是发笔记有这个需求,在其他业务中可能也会需要发布照片,因此上传照片功能是一个独立功能,点击上传照片时会先发出一个请求实现上传,上传成功后返回这张图片的地址,也就是上传之后的可访问的地址,这个地址就会做为表单的参数,在发布笔记时一起提交到后台,即在发布笔记时上传的其实不是照片的本身,而是上传成功后的图片地址,因此发布照片和发布笔记是不同的功能,会发起两个不同的请求。

而黑马点评这边已经将接口写好了,http: //127.0.0.1:8081/upload/blog,这个接口是为了上传图片的,因此需要修改存放的地址,是将其存放在前端服务器中的目录中,另一个接口http: //127.0.0.1:8081/blog,上传博客。

测试接口:

image-20250702221715696

image-20250702222000809

可以查看数据库表:

image-20250702222120371

而在发布完探店笔记后,发现没有实现查看探店笔记的接口。

image-20250702222431898

因此:

案例展示:实现查看探店笔记的接口

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

controller层:

@GetMapping("/{id}")
public Result queryBlog(@PathVariable("id") Long id){return blogService.queryBlogById(id);
}

service层

@Override
public Result queryBlogById(Long id) {// 1.查询blogBlog blog = getById(id);if (blog == null) {return Result.fail("笔记不存在!");}// 2.查询blog有关的用户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());}

测试:

image-20250702230047012

点赞

在首页的探店笔记排行榜和探店图文详情页面都有点赞功能:

在Java代码中直接利用MyBatisPlus来修改数据库中的liked字段,每一次点赞都是进行一次接口请求,并且没有点赞限制,没有进行用户判断。而且是直接修改数据库,影响性能。

案例:完善点赞功能

需求:

  • 同一个用户只能点赞一次,再次点击则为取消点赞

  • 如果当前用户已经点赞,则点赞按钮高亮(前端已实现,判断字段blog类的isLike属性)

实现思路:可以在新建一个表,记录笔记的id,以及点赞用户的id,这样每次点赞只需要看表中存在不存在这个用户的值即可,不需要去使用MySQL数据库,可以使用轻量级的数据库,比如redis,可以使用集合类的数据结构,key为笔记的id,value则为点赞的用户id,并且一个用户只能点赞一次,所以value不能重复,所以可以使用set数据结构。

实现步骤:

  1. 给blog类中添加一个isLike字段,标识是否被当前用户点赞

  2. 修改点赞功能,利用redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1,并且点赞过的用户需要添加在set集合中。

  3. 修改根据id查询blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段

  4. 修改分页查询blog业务,判断当前登录用户是否点赞过,赋值给isLike字段

代码实现:

 /*** 用户图标*/@TableField(exist = false)private String icon;/*** 用户姓名*/@TableField(exist = false)private String name;/*** 是否点赞过了*///该注解表示该成员变量不是数据库表中的字段@TableField(exist = false)private Boolean isLike;

service层业务代码:

 public Result likeBlog(Long id) {//1.获取登录用户Long userId = UserHolder.getUser().getId();//2.判断当前登录用户是否已经点赞String key = BLOG_LIKED_KEY + id;Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());if(Boolean.FALSE.equals(isMember)){//3.如果未点赞,可以点赞//3.1点赞数+1boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() + 1).eq(Blog::getId, id).update();//3.2,将用户id存储到redis的set集合中if (success) {stringRedisTemplate.opsForSet().add(key, userId.toString());}}else {//4.如果已点赞,取消点赞//4.1.数据库点赞数-1,boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() - 1).eq(Blog::getId, id).update();//4.2将用户id从Redis的set集合中删除if (success) {stringRedisTemplate.opsForSet().remove(key, userId.toString());}}return Result.ok();}

其次,我们还需要修改查询blog的业务代码,在查询用户之后,还需要查询该博客是否被点赞,再去修改isLiked的值。

由于查询有两种查询,一种分页查询,一种普通查询,因此我们将是否被点赞这个业务封装成函数,进行简化代码。

代码实现:

private void isBlogLiked(Blog blog) {Long userId = UserHolder.getUser().getId();String key = BLOG_LIKED_KEY + blog.getId();Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());//防止包装类为空blog.setIsLike(Boolean.TRUE.equals(isMember));}

进行测试:

进行点赞:

image-20250705160213204

检查redis数据库:

image-20250705160227537

取消点赞:

image-20250705160325467

再次查看Redis数据库:

发现已经不存在。

至此业务实现成功。

点赞排行榜

业务详情:

在探店笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的top5,形成点赞排行榜:

我们可以借鉴微信朋友圈点赞的机制。谁先点赞,谁在前面

接口信息:

image-20250705161123523

案例实现:实现查询点赞排行榜的接口

需求:按照点赞时间先后排序,返回top5的用户

思路分析:

我们需要按照点赞时间先后排序,但是点赞的用户信息存储在redis中的set集合里,set集合是无序集合,因此我们需要去更换集合,同时还要满足唯一性与有序性,将Redis中的集合数据结构进行对比。

ListSetSortedSet
排序方式按添加顺序排序无序可排序,根据score值
唯一性不唯一唯一唯一
查找方式按照索引查找或者首尾查找按照元素查找根据元素查找

由此看来,SortedSet更符合业务需求。

但是SortedSet和set有很多不同之处,就别去set中有sismember命令去查询某个元素是否在集合内,但是SortedSet就没有这样的命令,但是我们可以使用zscore命令来查询score值,如果元素不存在则score值就不存在,如果存在则有score值。这个命令也可以判断元素是否存在。如果查询排行榜可以使用zrange命令来查询。zrange命令就是查找范围内的元素。并且会天然的排序,可以按照时间戳来排序时,就会按照时间戳从小到大排序,最早插入的在最前面,这样就可以完成查询排行榜的业务。

思路实现:

改造之前的点赞业务,将set集合替换为SortedSet集合:

 private void isBlogLiked(Blog blog) {Long userId = UserHolder.getUser().getId();String key = BLOG_LIKED_KEY + blog.getId();Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());//防止包装类为空blog.setIsLike(score != null);}​@Overridepublic Result likeBlog(Long id) {//1.获取登录用户Long userId = UserHolder.getUser().getId();//2.判断当前登录用户是否已经点赞String key = BLOG_LIKED_KEY + id;Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());if(score == null){//3.如果未点赞,可以点赞//3.1点赞数+1boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() + 1).eq(Blog::getId, id).update();//3.2,将用户id存储到redis的set集合中if (success) {stringRedisTemplate.opsForZSet().add(key, userId.toString(),System.currentTimeMillis());}}else {//4.如果已点赞,取消点赞//4.1.数据库点赞数-1,boolean success = lambdaUpdate().set(Blog::getLiked, getById(id).getLiked() - 1).eq(Blog::getId, id).update();//4.2将用户id从Redis的set集合中删除if (success) {stringRedisTemplate.opsForZSet().remove(key, userId.toString());}}return Result.ok();}

实现排行榜接口:

 @Overridepublic Result queryBlogLikes(Long id) {//1.获取当前blog的点赞top5String key = BLOG_LIKED_KEY + id;Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4);//2.解析出其中的用户id,map映射,将long类型的字符串转为Longif (top5 == null || top5.isEmpty()) {return Result.ok(Collections.emptyList());}List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());//3.根据用户ID查询用户,这里直接返回的是user对象,但是在前端页面应该显示的是UserDTO,所以这里需要将user对象转为UserDTO对象List<UserDTO> userDTOS = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());//4.返回return Result.ok(userDTOS);}

进行测试:

先尝试一个账号点赞:

image-20250705172826834

测试成功,在进行多个账户点赞:

发现bug:新开网页时,发现下面的博客不登录看不到,根据报错提示发现是空指针异常,即userid为空,解决方案,在从UserHolder中取user时先进行判断,如果为空,则直接return;

代码演示:

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

解决;

继续测试:

image-20250705173141042

发现顺序不对,去IDEA中检查:

image-20250705173222559

发现顺序是对的,在进入数据库中查询:

image-20250705172647714

发现是使用关键字in的问题,

因此要使用order by ,但不能直接指定id,因为默认order by id 的话是按顺序从小到大的,因此要指定顺序:

image-20250705173758401

因此需要修改查询语句,又因为MyBatisPlus没有封装该方法,只能去自定义SQL语句。

修改代码如下:

 //3.根据用户ID查询用户,这里直接返回的是user对象,但是在前端页面应该显示的是UserDTO,所以这里需要将user对象转为UserDTO对象//where id in (ids) order by field(id,ids)List<UserDTO> userDTOS = userService.lambdaQuery().in(User::getId, ids).last("order by field(id,"+ idStr+")").list().stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());

再次进行刷新测试:

image-20250705180255556

至此,点赞排行榜业务实现成功。

希望对大家有所帮助!

http://www.dtcms.com/a/267678.html

相关文章:

  • C++ 11中lock_guard和unique_lock的区别
  • 初识Linux:Linux开发工具gcc/g++和gdb以及Makefile的使用
  • Python小工具之PDF合并
  • Redux 扩展与标准化模板方案
  • LINUX75 LAMP
  • 字节一面整理
  • C++ 模板参数匹配、特化
  • 智能监控算法助力工厂高温高效管理
  • 淘系怎么做?
  • hiredis window之RFDMap
  • 基于大模型的肾积水全周期预测与诊疗方案研究报告
  • 如何使用backtrace定位Linux程序的崩溃位置
  • 【STM32】定时器中断 + 含常用寄存器和库函数配置(提供完整实例代码)
  • 洛谷 P11967 [GESP202503 八级] 割裂-普及+/提高
  • 百度文心 4.5 大模型详解:ERNIE 4.5 Technical Report
  • 水下航行器外形之变体式与回转体的区别
  • 线程锁和线程同步
  • 从“电话催维修“到“手机看进度“——售后服务系统开发如何重构客户体验
  • Linux网络配置与故障排除完全指南
  • 12 nacos配置中心
  • 使用Kahn算法处理节点依赖关系
  • ABB焊接机器人智能节气仪
  • 汽车制造车间检测机器人与PLC无线以太网实时控制方案
  • 数据库学习笔记(十七)--触发器的使用
  • Java SE--数组
  • 前端相关性能优化笔记
  • TEXT Complete Search
  • 【RK3568 编译rtl8723DU驱动】
  • Write-up:hacker_dns
  • 安达发|告别低效排产:APS高级排程如何助力电池企业智造升级?