好友关注 :
关注和取关 :
关键是User之间的关系 , 是博主和粉丝之间的联系 , 数据库中有一张表 , tb_follow表来表示
- 关注 : 在表中添加字段 ,
- 取关 : 将表中字段删除
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public Result follow(Long followUserId, Boolean isFollow) {
// TODO 1.获取登录用户
Long userId = UserHolder.getUser().getId();
String key = "follows:" + userId;
// TODO 1.判断是关注还是取关
if (isFollow){
// TODO 2.关注 , 新增数据
Follow follow = new Follow();
follow.setUserId(userId);
follow.setFollowUserId(followUserId);
boolean save = save(follow);
// TODO 判断是否保存成功
if (save){
// TODO 保存成功 , 将关注用户的id保存在redis中
// sadd userId fallowUserId
stringRedisTemplate.opsForSet().add(key,followUserId.toString());
}
}else{
// TODO 3.取关 , 删除
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 isFollow(Long followUserId, Boolean isFollow) {
// TODO 获取登录用户
Long userId = UserHolder.getUser().getId();
// TODO 查询是否关注 ,
Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
return Result.ok(count > 0) ;
}
共同关注:
使用redis中的set , 可以查询两个缓存数据中的交集
使用的是intersect方法 , 可以查询两个key之间value的交集
@Override
public Result followCommons(Long id) {
// TODO 1.获取当前用户
// TODO 2.获取目标用户
Long userId = UserHolder.getUser().getId();
String key1 = "follows:" + userId;
// TODO 3.求交集
String key2 = "follows:" + id;
Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key1, key2);
if (intersect == null || intersect.isEmpty()){
// 无交集 , 返回
return Result.ok(Collections.emptyList());
}
// TODO 4.解析出id
List<Long> ids = intersect
.stream()
.map(Long::valueOf)
.collect(Collectors.toList());
// TODO 5.查询用户
List<UserDTO> users = userService.listByIds(ids)
.stream()
.map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());
return Result.ok(users);
}
关注推送 :
TimeLine模式 :
- 拉模式 : 读扩散 , 只有用户在读的时候 , 才会去拉取对应的信息
- 优点 :
- 节省内存空间 ;
- 缺点 : 每次读都需要进行拉取 , 进行排序 ,
- 推模式 : 写扩散 , 用户在发消息的时候 , 将这些消息 直接发送到粉丝的收件箱中 ,
- 优点 :
- 节省时间 ,
- 缺点 : 占用空间大
- 推拉结合模式 :
基于推模式实现关注推送功能 :
需求 :
- 修改新增探店笔记的业务 , 在保存blog到数据库的同时 , 推送到粉丝的收件箱
- 收件箱满足可以根据时间戳进行排序 , 必须使用Redis的数据结构实现
- 查询收件箱数据的时候 , 可以实现分页查询
修改新增探店笔记业务:
@Override
public Result saveBlog(Blog blog) {
// TODO 获取登录用户
UserDTO user = UserHolder.getUser();
blog.setUserId(user.getId());
// TODO 保存探店博文
boolean isSuccess = save(blog);
if (!isSuccess) {
return Result.fail("新增笔记失败");
}
// TODO 查询笔记作者的所有粉丝
List<Follow> follow_user_id = followService.query().eq("follow_user_id", user.getId()).list();
for (Follow follow : follow_user_id) {
// TODO 4.1 获取粉丝id
Long userId = follow.getUserId();
// TODO 4.2 推送到粉丝的收件箱 , 以feed加粉丝的id作为收件箱
String key = "feed:" + userId;
stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
}
// TODO 返回id
return Result.ok(blog.getId());
}
Feed流的滚动分页查询 :
逻辑实现 :
每次查询之后 , 记录当前查询的角标 , 下次查询从这个角标开始进行查询
参数总共有四个 :
max : 分数的最大值 : 设置为当前时间戳即可(第一次查询) | 上一次查询的最小值(不是第一次查询)
min : 分数的最小值 : 设置为 0 即可
offset : 0 (第一次查询) | 在上一次的结果中 , 与最小值一样的元素的个数 ()
count : 每次查询的数据量 : 固定的值
代码实现 :
//TODO 分页查询收件箱
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
// TODO 1. 获取当前用户的id
Long userId = UserHolder.getUser().getId();
// TODO 2.查询收件箱
String key = FEED_KEY + userId;
Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
.reverseRangeByScoreWithScores(key, 0, max, offset, 3);
// TODO 3.非空判断
if (typedTuples.isEmpty() || typedTuples == null) {
return Result.ok();
}
// TODO 4.解析数据 , BlogId , minTime(时间戳) , offset
List<Long> ids = new ArrayList<>(typedTuples.size());
long minTime = 0;
int os = 1;
for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
// TODO 4.1 获取id
ids.add(Long.valueOf(typedTuple.getValue()));
// TODO 4.2 获取分数(时间戳)
long time = typedTuple.getScore().longValue();
if (time == minTime){ //当有重复的时间戳时 , 就++
os++;
}else{ //不相等 , 说明出现了最新的最小值 , 将os重置为1即可
minTime = time;
os = 1;
}
}
// TODO 4.根据id查询blog
String idStr = StrUtil.join(",", ids);
List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
// TODO 每一个博客都需要查询点赞的用户 , 和是否被当前用户点赞
for (Blog blog : blogs) {
// TODO 2.查询blog有关的用户
queryBlogUser(blog);
// TODO 3. 查询blog是否被点赞
isBLogLiked(blog);
}
// TODO 封装并返回
ScrollResult scrollResult = new ScrollResult();
scrollResult.setList(blogs);
scrollResult.setOffset(os);
scrollResult.setMinTime(minTime);
return Result.ok(scrollResult);
}