Redis实现用户点赞、点赞top5用户排序

redis的sorted_set集合实现点赞功能和最早点赞top5功能,bug修正WRONGTYPE Operation against a key holding the wrong kind of value,数据库in( )查询排序

案例需求

方案分析

一个用户对于一个 blog 只能点赞一次,如果只使用数据库完成该功能,那么用户每点赞一次都要查询、修改数据库,这样无疑增加了数据库的压力,那么引入 redis,就可以使用 redis 的 Sorted_Set 集合解决该问题(中间件的引入当然是越少越好的,这样有利于系统的稳定性,那此处为了场景演示就引入 redis 了,毕竟现在有点规模的项目基本都离不开 redis)

redis 的 Sorted_Set 集合 集合中的元素具有唯一性、有序性,利用唯一性完成一个用户对于一个 blog 只能点赞一次,利用有序性完成点赞 top5 的用户排序,key 是 blog 的 id,value 是用户 id 和用于排序的时间戳,如果该 key 的 value 中有当前登录用户的 userId,那么说明该用户对该 blog 已点赞,那么可以利用该用户的时间戳进行排序,如果没有当前登陆用户的 userId,则未点赞。

点赞功能代码实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
   @Override
    public Result   likeBlog(Long id) {
        //获取当前用户
        Long loginUserId = UserHolder.getUser().getId();
        //判断该用户是否点赞
        String key="blog:liked:"+id;

        Double score = stringRedisTemplate.opsForZSet().score(key, loginUserId.toString());
        //score为空说明该用户未点赞
        if (score==null){
            //如果未点赞则点赞成功,数据库点赞数+1,用户对该blog的点赞信息添加到redis的set
            boolean isUpdate = update().setSql("liked = liked + 1").eq("id", id).update();
            if (isUpdate){
                //opsForZSet使用的是scoreSet集合,该集合较于set集合,可以对元素进行排序,基于该特性可以对点赞用户的顺序进行排名
                stringRedisTemplate.opsForZSet().add(key,loginUserId.toString(),System.currentTimeMillis());
            }
        }else {
            //如果已点赞则取消点赞,数据库点赞数-1,用户对该blog的点赞信息从redis的set移除
            boolean isUpdate = update().setSql("liked = liked - 1").eq("id", id).update();
            if (isUpdate){
                stringRedisTemplate.opsForZSet().remove(key,loginUserId.toString());
            }
        }

        return Result.ok();
    }

最早点赞 top5 功能代码实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
  @Override
    public Result queryBlogLikes(Long id) {
        //从redis查询top5的点赞用户
        Set<String> top5 = stringRedisTemplate.opsForZSet().range("blog:liked:" + id, 0, 4);
        if (top5==null||top5.isEmpty())
        {
            return Result.ok(Collections.emptyList());
        }
        //从查询的数据中得到top5的用户id
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        String idsStr = StrUtil.join(",", ids);
        //根据id查询top5的用户并返回
//        List<User> users = userService.listByIds(ids);
        //详见黑马点评 实战篇 P10
        List<User> users = userService.query().in("id", ids).last("ORDER BY FIELD (id," + idsStr + ")").list();
        List<UserDTO> userDTOS = users.stream()
                        .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());

        return Result.ok(userDTOS);
    }

细节注意

在最初使用 userService.listByIds(ids)查询数据库用户时,查询条件中的 id 顺序和查询结果的顺序是相反的,具体效果如下:

先查 id=5,再查 id=1,结果是 id=1 在前面,id=5 在后面,这样导致最早点赞 top5 的用户顺序相反

为解决上述问题,使用 order by 对 id 排序

注意这里演示的 id 是确定的数值,在实际代码中要把查询到的用户 id 列表转换成动态字符串,如下:

1
2
3
4
5
6
7
8
//从查询的数据中得到top5的用户id
        List<Long> ids = top5.stream().map(Long::valueOf).collect(Collectors.toList());
        String idsStr = StrUtil.join(",", ids);

 List<User> users = userService.query().in("id", ids).last("ORDER BY FIELD (id," + idsStr + ")").list();
        List<UserDTO> userDTOS = users.stream()
                        .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
                .collect(Collectors.toList());

Bug 修正

redis 报错

1
WRONGTYPE Operation against a key holding the wrong kind of value

原因是由于之前点赞功能是使用 set 集合,使得 redis 中已经存在了一个名为"blog:liked:23"的 key 值,添加点赞 top5 功能后由于把 set 集合换成了 sorted_set 集合导致 key 冲突,解决方案是删掉之前的 key 值

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计