【Redis】hmdp点赞、排行榜、分页功能的实现
文章目录
- 【Redis】hmdp点赞、排行榜、分页功能的实现
- 1.点赞功能实现
- 1.1 需求
- 1.2 实现步骤
- 1.3 思路分析
- 1.4 代码实现
- 1.5 逻辑解析
- 2. 排行榜功能实现
- 2.1 需求
- 2.2 实现步骤
- 2.3 思路分析
- 2.4 代码实现
- 2.5 逻辑解析
- 3. 分页功能实现
- 3.1 需求
- 3.2 实现步骤
- 3.3 代码实现
- 3.4 逻辑解析
1.点赞功能实现
1.1 需求
需求:
- 同一个用户只能点赞一次,再次点击则取消点赞
- 如果当前用户已经点赞,则点赞按钮高亮显示(前端已实现,判断字段Blog类的isLike属性)
1.2 实现步骤
实现步骤:
- 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
- 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
- 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
- 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段
1.3 思路分析
在实现点赞功能前,我们还需要确定如何使用Redis来存储用户点赞记录?我们知道Redis存储的是键值对数据,那么我们可以先确定“键”的取值为:blog:liked:用户id
,“值”的取值为 用户id
。确定好存储的数据之后,我们还需要考虑究竟是我们使用redis的哪种数据结构?因为点赞是有先后顺序的,所以我们采用 SortSet
结构,需求和实现步骤都明晰了后我们开始实现。
点击点赞按钮,发送请求:
1.4 代码实现
代码如下:
@Override
public Result likeBlog(Long id) {
//1.获取当前用户
Long userId = UserHolder.getUser().getId();
//2.判断当前用户是否已经点赞
String key = "blog:liked:" + id;
Double score = stringRedisTemplate.opsForZSet().score(key, userId.toString());
if (score == null) {
//3.如果未点赞,可以点赞
//3.1数据库点赞+1
boolean isSuccess = lambdaUpdate().setSql("liked=liked+1").eq(Blog::getId, id).update();
//3.2.保存用户到redis的SortSet集合 ZADD KEY VALUE SCORE
if (isSuccess) {
stringRedisTemplate.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());
}
} else {
//4.如果已点赞,取消点赞
//4.1.数据库点赞数-1
boolean isSuccess = lambdaUpdate().setSql("liked=liked-1").eq(Blog::getId, id).update();
if (isSuccess) {
//4.2.把用户从Redis的set集合移除
stringRedisTemplate.opsForZSet().remove(key, userId.toString());
}
}
return Result.ok();
}
1.5 逻辑解析
代码逻辑:首先获得笔记id,拼接获得key值,再获得当前登录用户的id,根据用户id和key值查看redis中是否有数据,如果没有数据,说明未点赞,此时可以点赞,数据库对应字段+1,并且将用户id存入redis当中,score值存入当前时间戳,表示递增;如果有数据,说明已点赞,此时取消点赞,数据库对应字段-1,并且将用户id从redis中移除。
2. 排行榜功能实现
在笔记的详情页面,应该把给该笔记点赞的人显示出来,比如最早点赞的TOP5,形成点赞排行榜:
按照点赞顺序,将用户的头像先后展示。
2.1 需求
需求:按照点赞时间先后排序,返回Top5的用户
List | Set | SortedSet | |
---|---|---|---|
排序方式 | 按添加顺序排序 | 无法排序 | 根据score值排序 |
唯一性 | 不唯一 | 唯一 | 唯一 |
查找方式 | 按索引查找 或首尾查找 | 根据元素查找 | 根据元素查找 |
2.2 实现步骤
实现步骤:
- 去redis中查询top5的点赞用户
- 解析出其中的用户id
- 根据用户id去数据库查询用户
2.3 思路分析
没啥思路需要理清,很清晰。
2.4 代码实现
代码如下:
@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.lambdaQuery()
.in(User::getId, ids).last("ORDER BY FIELD(id," + idStr + ")").list()
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class))
.collect(Collectors.toList());
return Result.ok(userDTOS);
}
2.5 逻辑解析
代码逻辑:我们在实现点赞功能时,score值存储的是当前时间戳,一定是递增的,所以用户id的存储顺序也是按照时间先后顺序存储的。于是我们直接采用 range
方法得到前五个点赞用户的id。然后去到数据库中查询这前五个用户的信息,在查询语句中加上 ORDER BY FIELD(id,5,1)
是因为如果不加的话会按照id的数值大小依次返回用户数据,这样就无法实现排行榜的功能了。
3. 分页功能实现
我们已经实现了点赞功能和点赞排行榜功能,现在我们需要进一步改进分页功能。
3.1 需求
需求:按照点赞数量的多少进行分页查询,点赞量多的排在前面,点赞量少的排在后面。
3.2 实现步骤
实现步骤:
- 根据笔记的点赞量倒序排序并分页
- 获取当前页的数据
- 查询每条数据对应的用户信息和点赞信息
3.3 代码实现
代码如下:
@Override
public Result queryHotBlog(Integer current) {
// 根据用户查询
Page<Blog> page = query()
.orderByDesc("liked")
.page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
// 查询用户
records.forEach(blog -> {
this.queryBlogUser(blog);
this.isBlogLiked(blog);
});
return Result.ok(records);
}
private void queryBlogUser(Blog blog) {
Long userId = blog.getUserId();
User user = userService.getById(userId);
blog.setName(user.getNickName());
blog.setIcon(user.getIcon());
}
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);
}
3.4 逻辑解析
代码逻辑:如果已经看懂了前两个功能的实现,那么分页功能的逻辑也就明白了。