主要实现三个功能
- 1.关注和取消关注
- 2.共同关注
- 3.Feed流实现的博客推送
关注和取关
根据传来的参数,为true还是false来判断操作是关注,还是取关
/**
* 关注,取关
* @param followUserId
* @param isFollow
* @return
*/
@Override
public Result follow(Long followUserId, Boolean isFollow) {
Long userId = UserHolder.getUser().getId();
//关注
if(isFollow) {
Follow follow=new Follow();
follow.setFollowUserId(followUserId);
follow.setUserId(userId);
follow.setCreateTime(LocalDateTime.now());
save(follow);
} else {
//取关
remove(new QueryWrapper<Follow>().eq("user_id",userId).eq("follow_user_id",followUserId));
}
return Result.ok();
}
共同关注
利用Redis的Set集合中求交集的方法来实现。
那么关注之后数据也要在缓存中添加和删除
/**
* 关注,取关
* @param followUserId
* @param isFollow
* @return
*/
@Override
public Result follow(Long followUserId, Boolean isFollow) {
Long userId = UserHolder.getUser().getId();
//关注
if(isFollow) {
Follow follow=new Follow();
follow.setFollowUserId(followUserId);
follow.setUserId(userId);
follow.setCreateTime(LocalDateTime.now());
boolean success= save(follow);
if(success){
stringRedisTemplate.opsForSet().add(RedisConstants.FOLLOW_KEY+userId,followUserId.toString());
}
} else {
//取关
boolean isSuccess = remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id", followUserId));
if(isSuccess) {
stringRedisTemplate.opsForSet().remove(RedisConstants.FOLLOW_KEY+userId,followUserId.toString());
}
}
return Result.ok();
}
/**
* 共同关注
* @param id
* @return
*/
@Override
public Result followCommons(Long id) {
Long userId = UserHolder.getUser().getId();
String key1=RedisConstants.FOLLOW_KEY+userId;
String key2=RedisConstants.FOLLOW_KEY+id;
//求交集
Set<String> collect = stringRedisTemplate.opsForSet().intersect(key1, key2);
if(collect==null || collect.isEmpty()) {
return Result.ok(Collections.emptyList());
}
//解析出ID集合
List<Long> ids = collect.stream().map(Long::valueOf).collect(Collectors.toList());
List<UserDTO> collect1 = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
return Result.ok(collect1);
}
实现关注推送功能
- 就是展示你关注的博主,他们的动态信息。
关于推送Feed流
什么是Feed流
- Feed:Feed流中的每一条信息都是Feed,比如朋友圈的每一条信息。
- Feed流:持续更新并呈现给用户的信息流,通过无限下拉刷新获取新的信息。B站的动态页面就是Feed流。
Feed流的实现有两种模式:
- TimeLine
- 智能排序
实现Feed流的三种方式
1.拉模式(读扩散):up发的动态会存在发件箱,粉丝想要去查看自己所有关注up的动态信息,会在收件箱拉去关注up的发件箱的信息。不看的时候收件箱会清空。缺点是当你关注的up比较多的时候不好搞
2.推模式(写扩散):up发动态会自己推到粉丝的收件箱,粉丝不用主动拉去信息。缺点是粉丝过多的时候不好搞
3.推拉结合:小up粉丝少,可以直接推模式,把信息推给所有粉丝。大V粉丝多,对于活跃的粉丝推模式,僵尸粉采用拉模式。
所以当我们发博客时候,推送一下:
/**
* 发布博客
* @param blog
* @return
*/
@Override
public Result saveBlog(Blog blog) {
// 1.获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// 2.保存探店博文
boolean save = save(blog);
if(!save){
return Result.fail("发布博客笔记失败惹");
}
//sql:select * from tb_follow where follow_user_id = {userid}
// 3.得到粉丝ids
List<Long> follow_user_id = followService.query().eq("follow_user_id", user.getId())
.list()
.stream()
.map(follow_user -> follow_user.getUserId()).collect(Collectors.toList());
//4.推送博客笔记id给所有粉丝
follow_user_id.forEach(ids-> {
String key=RedisConstants.FEED_KEY+ids;
stringRedisTemplate.opsForZSet().add(key,blog.getId().toString(), System.currentTimeMillis());
});
// 5.返回id
return Result.ok(blog.getId());
}
然后我们去查询收件箱笔记时候,要做一个滚动分页查询:
因为up发动态到我们的收件箱,我们查看动态的时候,是按照最近发布的时间顺序,来展示的。
所以传统的角标来固定分页查询的信息不行啦。
滚动分页,每一次滚动到顶部或者底部会刷新,做一次查询。
我们利用ZSet的Score来做查询
-
查看收件箱 ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
根据score 从高向低开始查询(max,min是范围), offset:符合范围的第几个开始查,0的话就是小于等于max的第一个就开始,count:查询几个
举个例子,score为:6 5 4 3 2 1
第一次:max:6 min:0 offset:0 ,count:2 会查询到 6 ,5
第二次:max:5 min:0 offset:1 ,count:2 会查询到 4 ,3
若是 score为:6 5 5 4 3 2 1
第一次:max:6 min:0 offset:0 ,count:3 会查询到 6 ,5,5
第二次:max:5 min:0 offset:1 ,count:3 会查询到 5,4,3(不是想要的4,3,2)
所以,我们的难点在于
- 1.下一次查询,要得到上一次查询的score作为这次查询的max。
- 2.上一次查询时,有几个重复的score,offset为几。
/**
* 收件箱的笔记滚动分页
* @param max
* @param offset
* @return
*/
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// 1.获取当前用户
Long userId = UserHolder.getUser().getId();
// 2.查看收件箱 ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
// 根据score 从高向低开始查询(max,min是范围), offset:符合范围的第几个开始查,0的话就是小于等于max的第一个就开始,count:查询几个
String key=RedisConstants.FEED_KEY+userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 3);
if(typedTuples == null || typedTuples.isEmpty()) {
return Result.ok();
}
// 3.把信息解析一下,博客id,score(时间戳),offset
//我们要拿到一个数据,就是最小分数有几个重复的, 5 4 4 3 2 1
List<Long> ids = new ArrayList<>(typedTuples.size());
long MinScore = 0;
int count = 1;
for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
//获取id
String value = typedTuple.getValue();
ids.add(Long.valueOf(value));
//获取score
long score = typedTuple.getScore().longValue();
if(score == MinScore) {
count++;
} else {
MinScore = score;
count = 1;
}
}
// 4.根据博客id查询博客
List<Blog> blogs = blogMapper.queryBlogByIds(ids);
for (Blog blog : blogs) {
//4.1博客关联的用户
queryBlogUser(blog);
//4.2博客是否点赞了
isBlogLiked(blog);
}
// 5.封装返回
ScrollResult scrollResult=new ScrollResult();
scrollResult.setList(blogs);
scrollResult.setMinTime(MinScore);
scrollResult.setOffset(count);
return Result.ok(scrollResult);
}