Skip to content
DAILY QUOTE

“ ”

需求:按照点赞时间先后顺序,返回Top5的用户

三种数据结构选择的对比:

Set无序,无法满足需求,虽然List使用RPUSH可以满足有序性,但是不唯一,查找效率低,而SortedSet可以根据score值进行默认升序排序。

  • ZADD key value score向ZSet中添加元素,并指定score排序值

  • ZSCORE key value判断ZSet是否有value元素,如果存在,返回该value对应的score值,如果不存在,则返回nil

  • ZRANGE key start end根据score排序值,查询并返回序号从start到end区间的value值

  • 代码实现:改用ZSet后的存储结构,点赞后,value存储用户id、score存储点赞时的时间戳,并且实现查询点赞前5名的用户列表

java
/**
 * 判断当前登录用户是否点赞了这篇博客。
 *
 * 作用:
 * 1.获取当前用户;
 * 2.用户未登录,无需查询是否点赞;
 * 3.判断当前登录用户是否已点赞;
 *
 * @param blog当前博客对象
 * @return无返回值
 */
private void isBlogLiked(Blog blog) {
    //1.获取当前用户
    UserDTO user = UserHolder.getUser();
    if (user==null){
        //用户未登录,无需查询是否点赞
        return;
    }
    Long userId = user.getId();
    //2.判断当前登录用户是否已点赞
    String key = RedisConstants.BLOG_LIKED_KEY + blog.getId();
    Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
    blog.setIsLike(score!=null);

}

/**
 * 点赞或取消点赞博客。
 *
 * 作用:
 * 1.获取当前用户;
 * 2.判断当前登录用户是否已点赞;
 * 3.数据库点赞数+1;
 * 4.保存用户id到Redis的ZSet集合;
 * 5.数据库点赞数-1;
 *
 * @param id业务id
 * @return处理结果
 */
@Override
public Result likeBlog(@PathVariable("id") Long id) {
    //1.获取当前用户
    Long userId= UserHolder.getUser().getId();
    //2.判断当前登录用户是否已点赞
    String key= RedisConstants.BLOG_LIKED_KEY + id;
    //Boolean isMember = stringRedisTemplate.opsForSet().isMember(key, userId.toString());
    Double score=stringRedisTemplate.opsForZSet().score(key,userId.toString());
    if (score == null) { // 如果时间戳不存在,说明未点赞,可以点赞
        //数据库点赞数+1
        boolean isSuccess = update().setSql("liked = liked + 1").eq("id", id).update();
        if (isSuccess) {
            //保存用户到Redis的ZSet集合
            Boolean added = stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
            log.debug("点赞写入Redis,key={},userId={},result={}",key,userId,added);
        }
    }else { // 如果已经点赞,取消点赞
        //数据库点赞数-1
        boolean isSuccess = update().setSql("liked = liked - 1").eq("id", id).update();
        if (isSuccess) {
            // 把用户从Redis的ZSet集合移除
            stringRedisTemplate.opsForZSet().remove(key, userId.toString());
        }
    }

    return Result.ok();
}

/**
 * 查询博客前5个点赞用户。
 *
 * 作用:
 * 1.查询top5点赞用户zrangekey04;
 * 2.解析其中的用户id;
 * 3.根据用户id查询用户;
 * 4.返回;
 *
 * @param id业务id
 * @return处理结果
 */
@Override
public Result queryBlogLikes(Long id) {
    String key= RedisConstants.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查询用户
    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);
}

注意:如果直接使用MyBatis-Plus的listByIds方法,底层默认使用in进行条件查询,查出来的结果默认按照主键升序排序,返回给前端的点赞排行榜是不对的,这就用到了mysql的field函数,示例'select * from user where id in(3,2,1) order by field(id,3,2,1)',实现了按我们想要的顺序进行排序

  • 测试功能

先点赞的在前面

Redis中的点赞顺序按点赞时间排序