初始代码
@GetMapping("/likes/{id}") public Result queryBlogLikes(@PathVariable("id") Long id) { //修改点赞数量 blogService.update().setSql("liked = liked +1 ").eq("id",id).update(); return Result.ok(); }
问题分析:这种方式会导致一个用户无限点赞,明显是不合理的
造成这个问题的原因是,我们现在的逻辑,发起请求只是给数据库+1,所以才会出现这个问题
完善点赞功能
需求:
-
同一个用户只能点赞一次,再次点击则取消点赞
-
如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
实现步骤:
-
给Blog类中添加一个isLike字段,标示是否被当前用户点赞
-
修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
-
修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
-
修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
为什么采用set集合:
因为我们的数据是不能重复的,当用户操作过之后,无论他怎么操作,都是
1、在Blog 添加一个字段
@TableField(exist = false) private Boolean isLike;
2、修改代码
@Override 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(BooleanUtil.isFalse(isMember)){ //3.如果未点赞,可以点赞 //3.1 数据库点赞数+1 boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update(); //3.2 保存用户到Redis的set集合 if(isSuccess){ stringRedisTemplate.opsForSet().add(key,userId.toString()); } }else{ //4.如果已点赞,取消点赞 //4.1 数据库点赞数-1 boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update(); //4.2 把用户从Redis的set集合移除 if(isSuccess){ stringRedisTemplate.opsForSet().remove(key,userId.toString()); } }
点赞排行榜
在探店笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的TOP5,形成点赞排行榜:
之前的点赞是放到set集合,但是set集合是不能排序的,所以这个时候,咱们可以采用一个可以排序的set集合,就是咱们的sortedSet
我们接下来来对比一下这些集合的区别是什么
所有点赞的人,需要是唯一的,所以我们应当使用set或者是sortedSet
其次我们需要排序,就可以直接锁定使用sortedSet啦
思路: 首选排序是需要用sortedset , set 的ismember判断是否已经存在, sortedSet 存储都是带有分数的,可以判断这个KEY是否有分数
我们对分数的要求是越晚发布的内容应该在最上面,即倒叙, 我们可以用一个自增长的分数,时间戳作为分数,时间戳是一直增长的。
修改代码
BlogServiceImpl
点赞逻辑代码
@Override public 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.数据库点赞数 + 1 boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update(); // 3.2.保存用户到Redis的set集合 zadd key value score if (isSuccess) { stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis()); } } else { // 4.如果已点赞,取消点赞 // 4.1.数据库点赞数 -1 boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update(); // 4.2.把用户从Redis的set集合移除 if (isSuccess) { stringRedisTemplate.opsForZSet().remove(key, userId.toString()); } } return Result.ok(); } private void isBlogLiked(Blog blog) { // 1.获取登录用户 UserDTO user = UserHolder.getUser(); if (user == null) { // 用户未登录,无需查询是否点赞 return; } Long userId = user.getId(); // 2.判断当前登录用户是否已经点赞 String key = "blog:liked:" + blog.getId(); Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString()); blog.setIsLike(score != null); }
点赞列表查询列表
BlogService
@Override public Result queryBlogLikes(Long id) { String key = BLOG_LIKED_KEY + id; // 1.查询top5的点赞用户 zrange key 0 4 Set<String> top5 = stringRedisTemplate.opsForZSet().range(key, 0, 4); if (top5 == null || top5.isEmpty()) { return Result.ok(Collections.emptyList()); } // 2.解析出其中的用户id List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList()); String idStr = StrUtil.join(",", ids); // 3.根据用户id查询用户 WHERE id IN ( 5 , 1 ) ORDER BY FIELD(id, 5, 1) List<UserDTO> userDTOS = userService.query() .in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list() .stream() .map(user -> BeanUtil.copyProperties(user, UserDTO.class)) .collect(Collectors.toList()); // 4.返回 return Result.ok(userDTOS); }
userService.query()
.in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list() 使用 ORDER BY FIELD(id, 5, 1) 是为了让数据库查询按照我们的字段顺序输出。
这里使用了stream的流操作,实际逻辑为 list<user>.stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class) user 作为入参的 转化DTO操作。 .collect为遍历操作 ,
.collect() 遍历list中的元素,将user 作为入参的 转化DTO操作, Collectors.toList() 将结果收集为list。
标签:基于,set,liked,redis,用户,key,点赞,id From: https://www.cnblogs.com/kisshappyboy/p/16945777.html