Springboot实战——黑马点评之探店及关注
黑马点评——达人探店及关注推送1 探店业务实现
1.1 探店笔记发布
1)笔记blog字段属性
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918095500224-1983450570.png
除此之外,在"搜索博客"接口实现中会涉及到向前端展示用户的部分信息,例如用户头像icon、用户昵称name、用户是否点赞该博客islike(用于对点赞按钮高亮作实现),在设计实体类时利用springboot注解@TableField(exist = false)来标记这些前端展示字段并不属于数据库库表中的真实字段
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918095917864-476507471.png
2)上传博客接口实现
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918100241440-571758514.png
上传图片接口单独实现
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918101328515-153663555.png
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918102005741-954362563.png
3)索引博客接口实现
[*]对特定博客id索引
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918102249684-436576219.png
对检索到的博客发表用户作信息索引
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918104254579-403724693.png
[*]采取分页对点赞靠前的博客索引(分页查找)
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918104723665-352152045.png
1.2 笔记点赞实现
1)最简朴素现逻辑
将该博客的点赞数+1 即实行
update tb_blog set liked=liked+1 where id=?
2)针对重复点赞制止逻辑完善
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918105445247-1990691051.png
为了制止同一用户对同一博客重复点赞,采取Redis中的set集合(利用set集合元素的唯一性)来存储对某一博客点赞用户id的记录。
[*]向set中存入记录值下令SADD
[*]向set中检索某集合中是否存在某记录SISMEMBER
[*]向set中删除某记录SREMOVE
联合以上数据布局,将点赞逻辑完善:
[*]点赞前核验对应商铺Redis集合中是否存在当前登录用户id
[*]假如当前登录用户存在则将点赞数+1且将用户id存入Redis中(首次/再次点赞)
[*]假如不存在则点赞数-1且将用户id移除(取消点赞)
两种查询blog的业务也要做isLike判断来返回给前端,进而实现点赞按钮的高亮判断:
[*]核验,假如存在则返回isLike=true,点赞按钮高亮
[*]假如不存在则返回isLike=false,点赞按钮不亮
3)特定博客的点赞排行榜显示
在检察用户博客时应该在对应位置将点赞的用户信息(头像及昵称)同步显示出来,简朴的实现逻辑是按用户点赞时间从早到晚来排行显示,即越早点赞的用户显示越在前排。
所以该功能对应的数据布局需要满意:
[*]检索服从高
[*]元素具有唯一性
[*]元素可排序,而且是按加入的时间戳排序
考虑Redis中的三种集合数据布局:
[*]list:链表,可以按加入集合的先后排序,但元素不具有唯一性,检索时需要遍历整个链表
[*]set:集合,不能将元素排序,但元素具有唯一性,检索时底层利用哈希,检索速度较快
[*]sortedSet:可排序集合,value中除放入元素本身外,排序原则按照第二字段score大小来排序,检索速度同set
综上所述,应该选择sortedSet来实现点赞列表缓存
[*]向sortedSet中插入点赞记录实时间戳score:ZADD
[*]在sortedSet中查找某条点赞记录是否存在:ZSCORE,该下令本意是返回对应value的score,假如没有score也就是该value不存在,则返回null
[*]从sortedSet中排序score在a-b(集合元素的下标为a-b)范围的value:ZRANGE
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918145454110-1564035199.png
所以,将ZSet缓存逻辑替换了上述的Set缓存逻辑,也就是更改增删改的Redis下令语句即可实现优化了。
so,利用sortedSet优化后的点赞排行榜实现逻辑变成:
利用range(key,begin,end)即可返回与key对应的value集合中从下标为begin到end的按时间戳排好序的记录,提取其中的userid,即可以查询到user列表返回前端了。
BUT,这么实现有个问题:
返回的用户列表为想要返回的(begin,end)列表的倒序
原因在于:从Redis中查找返回的Zset中的idsList是正确次序的,但是当通过listByIds去数据库中查找对应用户信息时利用了
select icon,nick_name from tb_user where id IN(a,b,c,...)
其中(a,b,c...)原本是正确的次序,但查找的信息结果却是倒序的了
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918153544592-645131141.png
此处若将sql语句修改为
select icon,nick_name from tb_user where id IN(a,b,c,...) ORDER BY FIELD(id,a,b,c,...)
即 指定返回的次序按id指定的次序
学习一段代码:
// 从set集合中提取出用户ids
List<Long> ids = rangeLikeSet.stream().map(Long::valueOf).collect(Collectors.toList());
// 【优化点】:解决mysql语句中in list返回的记录中默认按id的从小到大顺序的问题
String idStr = StrUtil.join(",",ids);
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());2 关注及推送业务实现
关注与被关注是多-多的关系,借用中心表单来记录
2.1 建立中心表单来记录
1)用户-被关注用户表
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240918162525283-798185507.png
实现关注功能时,需要考虑前端服务器的关注按钮高亮判断
需要实现两个接口:
[*]“isFollow接口”用来检索关注关系是否存在,假如存在则返回true,该接口通过返回值来判断按钮是否高亮
[*]“follow接口”用来实现关注大概取关,除了传入用户id外还需要传入isFollow的返回布尔值来判断是关注照旧取关
2)用户间的共同关注列表
检索共同关注即检索用户关注列表中的交集,这就需要Redis缓存set数据布局来实现。
在实现关注功能时,同时将关注关系存入数据库和Redis-set中,求共同关注时利用Intersect方法来求用户Id交集
3)关注推送实现
几种推送方式对比
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919144341821-117433713.png
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919144712822-1019482234.png
接口实现需求:
(先来实现最基本的推送功能)
[*]推送缓存数据布局:利用Redis的sortedSet
【粉丝端的博客内容应该是可以支持按时间戳排序且支持分页查询的。】
考虑两种可以实现的数据布局:链表以及排序集
链表list:头插法或尾插法均可以实现按发布时间排序,分页查询时传入current、maxsize分别计算出偏移量以及每页返回的记录数,找到起始的下标即可实现分页查询了。
问题:链表分页查询每次都要从链表起始位置计算,假如期间有新博客插入,将会计算有误,一些已被查询返回的博客将被再次查到,如下图所示
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919155923257-2027161812.png
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919160547271-778166522.png
排序集sortedSet:将时间戳作为score值插入即可实现排序,分页查询类似上述的查询方法
优于链表的理由:分页查询时可以依据上一次查询到的最后一个下标,继续下一轮的分页查询,不会因为新插入的数据改变查询次序
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919160013137-582235719.png
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919162034776-1410176311.png
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919162531186-1559655375.png
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919162442128-347201022.png
[*]推送博客:用户发布博客成功同时将博客序列号id存入所有关注该用户的粉丝sortedSet中,即可实现“推送”
[*]粉丝欣赏博客:粉丝从缓存中分页查询“推送”过来的博客信息
滚动分页实现:
https://img2024.cnblogs.com/blog/3420577/202409/3420577-20240919162632413-798418082.png
基于上述分页查询的利弊分析得到:
sortedSet中的按score范围查询的方法ZREVRANGEBYSCORE,需要两个可变参数:
1 上一次查询出来记录的最后一条score值;
2 上一次查询出来最小时间戳score值雷同的记录总数offSet
这两个指标可以由每次分页查询结果返回给前端
利用到sortedSet中的reversescorerange,该方法接收五个参数:
[*]关键字key
[*]score的最大范围maxId,假如按score从大到小分列次序返回的话,该参数意味着 此次查询从小于即是上一次查询的最小score 的记录开始查询,即maxId从上一次查询结果中返回
[*]score的最小范围minId,默认给一个最小边界值,假如是时间戳的表示,就给score默认为0
[*]查询偏移量offSet,意思是从maxId往下查的记录应该从小于即是maxId的第几个记录开始查,尤其是当上一次查询的记录中有多个score雷同的最小记录,就要统计该记录的数目,以防下一次查询时多次查到该值
[*]查询记录数count,每次查询出的最大记录数目,一样平常是给定的常量
综上所述,每次查询返回给前端用于下一次查询的参数:
1)maxId
2)offSet
实现接口需要明确以下几个问题:
Q:分页查询是从哪里查?
A:从博主向粉丝的推送序列sortedSet中查,sortedSet的关键字是粉丝用户id,内容是(博客标识id,发布时间戳score),分页查询出的结果是由内容构成的元组序列。
Q:查询的结果要怎么向需要返回的结果转换
A:该接口返回的结果是博客本身、minTime、offSet,这三项内容可以封装到一个通用实体类ScrollResult中;博客序列由博客标识id到数据库中批量查找返回(前提是要提取出元组序列中的标识id);minTime直接在遍历元组序列时迭代赋值score即可获得;offSet初始值赋为1,遍历到score=minTime时则加1。
注意两点:
[*]在获得待返回博客序列时注意将用户信息以及点赞判断两个方法加入。
[*]从数据库中取出的Id序列为(a,b,c,...)的博客次序有误,需要手动加ORDER BY(a,b,c,...)来限制次序
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]