1. 程式人生 > 其它 >基於redis的點贊功能

基於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欄位

為什麼採用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。