用户点赞功能
如果用户只要点赞一次就对数据库中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());