重点1 使用 Set 数据结构实现一人只能点赞一次
选择set的原因
(1)不重复特性:Set 集合中的元素具有唯一性,这与 “一个用户只能对一篇博客点赞一次” 的业务需求高度契合。当用户进行点赞操作时,将用户 ID 作为元素添加到 Set 集合中,如果该用户再次尝试点赞,由于 Set 不允许重复元素,就可以直接判断该用户已经点赞过。
(2)高性能:Set 集合内部基于 Hash 表实现,插入、删除和查找操作的时间复杂度都是 O (1),能够快速处理点赞和取消点赞的操作。
(3)灵活性:一个用户可以对多篇博客进行点赞,使用 Set 集合可以方便地为每篇博客维护一个独立的点赞用户集合,实现一对多的关系。
重点2 使用 SortedSet 数据结构实现点赞排行榜
虽然 Set 数据结构可以实现一人一次点赞的功能,但在实际应用中,很多场景需要对点赞用户进行排序,例如显示最新点赞的用户列表。Set 集合是无序的,无法满足这个需求;而 List 虽然有序,但不具备元素唯一性,且查找效率较低。Sorted Set 则完美地解决了这些问题,它不仅保证元素的唯一性,还可以根据元素的分数进行排序,并且查找效率高。
重点3 实现先点赞的排在前面的需求
问题
在使用 Redis 的 Sorted Set 存储博客点赞信息时,我们通常会将用户点赞的时间戳作为分数存储在 Sorted Set 中。由于 Sorted Set 默认是按照分数从小到大进行排序的,所以后点赞的用户分数更高,会排在前面。但在某些业务场景下,我们希望先点赞的用户排在前面,这就需要对查询结果的排序进行调整。
解决方案思路
为了实现先点赞的用户排在前面,我们需要在从 MySQL 中查询点赞用户信息时,按照 Redis 中 Sorted Set 里用户 ID 的顺序来排列结果。而 MySQL 的 order by field
函数可以帮助我们实现这一需求,它允许我们指定一个字段以及该字段值的排序顺序。
field()
是 MySQL 中的一个函数,它接受一个字段名和一系列的值作为参数。field(id, 5, 1)
表示根据 id
字段的值在 (5, 1)
这个列表中的位置来排序。也就是说,id
为 5
的记录会排在 id
为 1
的记录前面。
相较于Set集合,SortedList有以下不同之处:
对于Set集合我们可以使用 isMember方法判断用户是否存在,对于SortedList我们可以使用ZSCORE方法判断用户是否存在
Set集合没有提供范围查询,无法获排行榜前几名的数据,SortedList可以使用ZRANGE方法实现范围查询
实现流程
(1)查询博客时判断是否点赞
当用户请求查看某篇博客详情或者浏览热门博客列表时。
具体步骤:
- 服务类从
ThreadLocal
中获取当前登录用户的 ID。这是因为在用户登录时,系统会将用户信息存储在ThreadLocal
中,方便在后续操作中获取用户 ID。 - 根据博客的 ID 和预定义的
BLOG_LIKED_KEY
构建 Redis 的键名。例如,若BLOG_LIKED_KEY
为blog:liked:
,博客 ID 为123
,则键名为blog:liked:123
。 - 使用
StringRedisTemplate
的opsForSet().isMember
方法检查当前用户的 ID 是否存在于该 Redis 的 Set 集合中。如果存在,说明用户已经点赞该博客;如果不存在,则未点赞。 - 将判断结果存储在博客对象的
isLike
属性中,这样在返回博客信息给前端时,前端可以根据该属性决定是否显示已点赞的样式。
(2) 点赞操作
当用户在博客详情页点击点赞按钮时。
具体步骤:
- 服务类从
ThreadLocal
中获取当前登录用户的 ID,并构建对应的 Redis 键名。 - 使用
StringRedisTemplate
的opsForSet().isMember
方法判断用户是否已经点赞该博客。 - 如果用户未点赞:
(3) 取消点赞操作
具体步骤: