黑马点评8——好友关注-SortedSet
文章目录
- 关注和取关
- 共同关注
- Feed流实现方案分析
- 推送到粉丝收件箱
- Feed流
- 基于推模式实现关注推送功能
- 滚动分页查询收件箱的思路
- 实现滚动分页查询
关注和取关
所以关注和取关就是简单的插入和删除数据库。
@Override
public Result isFollow(Long followUserId) {
// 1. 获取登录用户
Long userId = UserHolder.getUser().getId();
// 2.查询是否关注,select count(*) from tb_follow where user_id = ? and follow_user_id = ?
Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
// 3.判断
return Result.ok(count > 0);
}
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// 获取登录用户
Long userId = UserHolder.getUser().getId();
// 1. 判断到底是关注还是取关
if(isFollow){
// 2.关注,新增数据
Follow follow = new Follow();
follow.setFollowUserId(followUserId);
follow.setUserId(userId);
save(follow);
}else{
// 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?
remove(new QueryWrapper<Follow>()
.eq("user_id",userId).eq("follow_user_id", followUserId));
}
return Result.ok();
}
共同关注
这两个接口主要是点击头像,查询用户和查询用户的博客
直接上写接口就行
@GetMapping("/{id}")
public Result queryUserById(@PathVariable("id") Long userId){
// 查询详情
User user = userService.getById(userId);
if(user == null){
return Result.ok();
}
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
// 返回
return Result.ok(userDTO);
}
@GetMapping("/of/user")
public Result queryBlogByUserId(
@RequestParam(value = "current", defaultValue = "1") Integer current,
@RequestParam("id") Long id){
// 根据用户查询
Page<Blog> page = blogService.query().eq("user_id", id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE));
// 获取当前页数据
List<Blog> records = page.getRecords();
return Result.ok(records);
}
其实查询共同关注就是查询当前登录用户和查询的用户他们的关注的集合的交集
我们可以把当前用户的所有关注全部放在redis中,直接使用redis的set的求交集的功能
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// 获取登录用户
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
// 1. 判断到底是关注还是取关
if(isFollow){
// 2.关注,新增数据
Follow follow = new Follow();
follow.setFollowUserId(followUserId);
follow.setUserId(userId);
boolean isSuccess = save(follow);
if(isSuccess){
// 把关注用户的id, 放入redis的set集合,sadd userId followerUserId
stringRedisTemplate.opsForSet().add(key, followUserId.toString());
}
}else{
// 3.取关,删除 delete from tb_follow where user_id = ? and follow_user_id = ?
boolean isSuccess = remove(new QueryWrapper<Follow>()
.eq("user_id", userId).eq("follow_user_id", followUserId));
if(isSuccess){
// 把关注用户的id从Redis集合中移除
stringRedisTemplate.opsForSet().remove(key, followUserId.toString());
}
}
return Result.ok();
}
@Override
public Result followCommons(Long id) {
// 1. 获取当前用户
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
// 2. 求交集
String key2 = "follows:" + id;
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
if(intersect == null || intersect.isEmpty()){
// 没有交集
return Result.ok(Collections.emptyList());
}
// 3.解析id集合
List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
// 4. 查询用户
List<UserDTO> userDTOS = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
return Result.ok(userDTOS);
}
Feed流实现方案分析
推送到粉丝收件箱
Feed流
我们的业务是把关注的用户发布的博客展示出来,比较适合使用Timeline模式
我们就使用推模式
基于推模式实现关注推送功能
Feed流的分页问题:
分页流不能采用传统的分页模式,因为我们的数据会动态变化,比如上面的情况就会出现重复读取6的情况,
因为我们采用滚动分页
因为他的查询不依赖角标,因此他的查询不会因为角标的变化、数据的变化而改变
list支持滚动分页吗?不行,因为list只支持角标的查询,或者首尾,都不行。
SortedSet可以吗?SortedSet会按照score值排序,然后有一个排名,如果按照排名查询,那和角标查询没什么区别,但是,我们的SortedSet也支持范围查询,score值就是我们的时间戳嘛,我们把时间戳从大到小的顺序进行一个排列,每一次查询的时候,记录下最小的时间戳,然后下次查询的时候,找比这个更小的,这样就实现了滚动分页了。
我们先实现,每当有人发布笔记时,把笔记保存到SortedSet中
@Override
public Result saveBlog(Blog blog) {
// 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 保存探店笔记
boolean isSuccess = save(blog);
if(!isSuccess){
return Result.fail("新增笔记失败!");
}
// 3.查询笔记作者的所有粉丝 select * from tb_follow where follow_user_id = ?
List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
// 4,推送笔记id给所有粉丝
for (Follow follow : follows) {
// 4.1. 获取粉丝id
Long userId = follow.getUserId();
// 4.2 推送
String key = "feed:" + userId;
stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
}
// 返回id
return Result.ok(blog.getId());
}
滚动分页查询收件箱的思路
实现滚动分页查询
@GetMapping("/of/follow")
public Result queryBlogOfFollow(@RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){
return blogService.queryBlogOfFollow(max, offset);
}
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// 1. 获取当前用户id
Long userId = UserHolder.getUser().getId();
// 2. 查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
String key = RedisConstants.FEED_KEY + userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 2);
// 3. 非空判断
if(typedTuples == null || typedTuples.isEmpty()){
return Result.ok();
}
// 4. 解析数据: blogId、score(时间戳minTime), offset
List<Long> ids = new ArrayList<>(typedTuples.size());
long minTime = 0;
int os = 1;
for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
// 4.1 获取id
ids.add(Long.valueOf(typedTuple.getValue()));
// 4.2 获取分数
long time = typedTuple.getScore().longValue();
if(time == minTime){
os++;
}else{
minTime = time;
os = 1;
}
}
// 5. 根据id查询blog
String idStr = StrUtil.join(",", ids);
List<Blog> blogs = query()
.in("id", ids)
.last("ORDER BY FIELD(id," + idStr + ")").list();
for (Blog blog : blogs) {
// 5.1 查询blog有关的用户
queryBlogUser(blog);
// 5.2 查询blog是否被点赞
isBlogLiked(blog);
}
// 6. 封装并返回
ScrollResult r = new ScrollResult();
r.setList(blogs);
r.setOffset(os);
r.setMinTime(minTime);
return Result.ok(r);
}