基於redis的點贊功能
初始程式碼
@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欄位
因為我們的資料是不能重複的,當用戶操作過之後,無論他怎麼操作,都是
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()); } }
之前的點贊是放到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。