黑马点评-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属性设置为true
    blog.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属性赋值true
    blog.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());

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript编程基础 – 布尔值(Booleans)

JavaScript编程基础 – 布尔值(Booleans) Javascript Programming Essentials – Booleans 一个JavaScript布尔值包含两个值中的一个&#xff0c;即 true 或者 false。 本文简要介绍JavaScript布尔值的具体应用&#xff0c;以及可能作为对象的布尔值等。 1. 布尔值(Booleans)…

Py之PyPDF2:PyPDF2的简介、安装、使用方法之详细攻略

Py之PyPDF2&#xff1a;PyPDF2的简介、安装、使用方法之详细攻略 目录 PyPDF2的简介 PyPDF2的安装 PyPDF2的使用方法 1、基础用法 PyPDF2的简介 PyPDF2是一个免费的、开源的纯python PDF库&#xff0c;能够拆分、合并、裁剪和转换PDF文件的页面。它还可以为PDF文件添加自定…

springcloud超市管理系统源码

技术说明&#xff1a; jdk1.8&#xff0c;mysql5.7&#xff0c;idea&#xff0c;vscode springcloud springboot mybatis vue elementui mysql 功能介绍&#xff1a; 后台管理&#xff1a; 统计分析&#xff1a;查看用户&#xff0c;商品&#xff0c;销售数量&#xff1b;…

JavaWeb——感谢尚硅谷官方文档

JavaWeb——感谢尚硅谷官方文档 XML一、xml简介二、xml的语法1、文档申明2、xml注释3、xml元素4、xml属性5、xml语法规则 三、xml解析技术1、使用dom4j解析xml Tomcat一、JavaWeb的概念二、web资源的分类三、常见的web服务器四、Tomcat的使用1、安装2、Tomcat的目录介绍3 启动T…

数据结构-leetcode(设计循环队列)

1.学习内容&#xff1a; 今天 我们讲解一道能够很好的总结所学队列知识的题目---设计循环队列 622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; 2.题目描述&#xff1a; 让我们设计一个队列 要求是循环的 这和我们的双向链表有些类似 让我们按要求设计出这些相对…

视频云存储EasyCVR平台国标接入获取通道设备未回复是什么原因?该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

Python中的解析器argparse

import argparse## 构造解析器 argparse.ArgumentParser() parse argparse.ArgumentParser(description"caculateing the area of rectangle")## 添加参数 .add_argument() parse.add_argument("--length",typeint,default20,helpThe length of rectangle…

西米支付:如何设计和构建游戏支付系统?

如何设计和构建游戏支付系统&#xff1f; 目前&#xff0c;游戏开发中最常见的支付方式包括微信支付、支付宝支付和苹果支付等。今天&#xff0c;我将与大家分享游戏支付系统的架构和设计。 游戏支付的主要业务流程是指游戏玩家在游戏中购买虚拟物品或服务所进行的支付过程。一…

深入了解Java8新特性-日期时间API_LocalDate类

阅读建议 嗨&#xff0c;伙计&#xff01;刷到这篇文章咱们就是有缘人&#xff0c;在阅读这篇文章前我有一些建议&#xff1a; 本篇文章大概12000多字&#xff0c;预计阅读时间长需要10分钟。本篇文章的实战性、理论性较强&#xff0c;是一篇质量分数较高的技术干货文章&…

使用 PowerShell 中的命令来删除共享目录

Remove-SmbShare -Name "ShareName" 请将 "ShareName" 替换为您要删除的实际共享目录的名称。 请注意&#xff0c;执行此命令需要具有适当的权限。确保您以管理员身份运行 PowerShell 或具有足够的权限来删除共享目录。

超详细的自动化测试教程

什么是自动化测试&#xff1f; 做测试好几年了&#xff0c;真正学习和实践自动化测试一年&#xff0c;自我感觉这一个年中收获许多。一直想动笔写一篇文章分享自动化测试实践中的一些经验。终于决定花点时间来做这件事儿。 首先理清自动化测试的概念&#xff0c;广义上来讲&a…

微信小程序汽车租赁系统

微信小程序汽车租赁系统 本系统包含了3类用户&#xff0c;分别为客户、员工以及管理员&#xff0c;客户主要是满足自身的租车需求&#xff0c;员工主要负责车辆的调度问题和维修状况&#xff0c;管理员则是主要对人员、车辆和订单的管理。以下是对各自功能的详细介绍: 客户可…

简单聊聊加密和加签的关系与区别

大家好&#xff0c;我是G探险者。 平时我们在项目上一定都听过加密和加签&#xff0c;加密可能都好理解&#xff0c;知道它是保障的数据的机密性&#xff0c;那加签是为了保障啥勒&#xff1f;它和加密有啥区别&#xff1f; 带着这个疑问&#xff0c;我们就来聊聊二者的区别。…

测试15k薪资第1步 —— 自动化测试理论基础

目录 1、自动化测试定义 2、自动化测试分类&工具 3、未来发展趋势 1.1、什么是自动化测试 自动化测试指的是利用软件工具或脚本来执行测试任务&#xff0c;以替代手动测试过程的一种测试方法。它的主要目的是通过自动化执行、验证和评估软件应用的功能、稳定性、性能等方面…

Redis(哨兵模式)

哨兵模式的定义&#xff1a; 是Redis的一种高可用解决方案&#xff0c;通过运行多个Redis实例来监控主从Redis实例的状态&#xff0c;当主实例出现故障时&#xff0c;哨兵会自动选举一个从实例作为新的主实例&#xff0c;从而保证系统的高可用性。哨兵模式可以监控多个主从Red…

HCIP---MPLS---LDP

文章目录 前言一、pandas是什么&#xff1f;二、使用步骤 1.引入库2.读入数据总结 前言 MPLS 基于标签转发表进行转发&#xff0c;与路由表类似&#xff0c;标签转发表有两种获取渠道&#xff1a;一是手动配置(类似静态路由)&#xff0c;二是通过协议自动学习(类似OSPF)。手动配…

ubuntu搭建phpmyadmin+wordpress

Ubuntu搭建phpmyadmin wordpress Linux系统设置&#xff1a;Ubuntu 22配置apache2搭建phpmyadmin配置Nginx环境&#xff0c;搭建wordpress Linux系统设置&#xff1a;Ubuntu 22 配置apache2 安装apache2 sudo apt -y install apache2设置端口号为8080 sudo vim /etc/apache…

【MISRA-C 2012】浓缩版解读

文章目录 1、前言2、简介2.1、如何看待MISRA-C 20122.2、准则(guidelines)里面的指示(Directive)和规则(Rule)2.3、准则(guidelines)的级别(Category) 3、若干重要的Directive和Rule3.1、指示(Directive)Dir 2.1&#xff08;必要&#xff09; 所有的源文件编译过程不得有编译错…

【机器学习 | ARIMA】经典时间序列模型ARIMA定阶最佳实践,确定不来看看?

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

2023年广东成考成绩公布!分数线也出来了!

广东考试院最新公布&#xff0c;参加2023年成人高考的考生&#xff0c;在今天&#xff08;11月23日&#xff09;18:20分后将受到官方的成绩短信。 考生也可以通过考试院小程序和官网网站查询成绩。 另外&#xff0c;考生收到的短信分数为卷面分&#xff0c;不含25岁年龄加分&am…
最新文章