关注和取关
在探店图文详情页面,可关注发布笔记的作者
需求:基于该表数据结构,实现两个接口:
- 关注和取关接口
- 判断是否关注接口
关注是User之间的关系,用tb_follow表示
@Servicepublic class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {@Overridepublic Result follow(Long followUserId, Boolean isFollow) {//0.获取登录用户Long userId = UserHolder.getUser().getId();//1.判断是关注还是取关if (isFollow) {//2.关注,新增数据Follow follow = new Follow();follow.setUserId(userId);follow.setFollowUserId(followUserId);save(follow);}else{//3.取关,删除数据remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id",followUserId));}return Result.ok();}@Overridepublic Result isFollow(Long followUserId) {//0.获取登录用户Long userId = UserHolder.getUser().getId();//1.查询是否关注Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();return Result.ok(count > 0);}}
共同关注
第一步:实现用户主页
实现点击头像后跳转的个人主页的两个功能(因为跳转后先是停留在笔记页面,所以与共同关注逻辑无关):
- 根据id查询到该用户信息(上半部分)
- 根据id查询到该用户的博客笔记(下半部分)
```java
// UserController 根据id查询用户
@GetMapping(“/{id}”)
public Result queryUserById(@PathVariable(“id”) Long userId){
// 查询详情
User user = userService.getById(userId);
if (user == null) {
} UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); // 返回 return Result.ok(userDTO); }return Result.ok();
// BlogController
@GetMapping(“/of/user”)
public Result queryBlogByUserId(
@RequestParam(value = “current”, defaultValue = “1”) Integer current,
@RequestParam(“id”) Long id) {
// 根据用户查询
Page
<a name="draNl"></a>### 第二步:实现共同关注需求:利用Redis实现共同关注(Set求交集->SINTER s1 s2)<br />```java@Overridepublic 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());}List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());List<UserDTO> users = userService.listByIds(ids).stream().map(user -> BeanUtil.copyProperties(user, UserDTO.class)).collect(Collectors.toList());return Result.ok(users);}
关注推送
Feed流实现方案分析
关注推送也叫Feed流,直译为投喂。为用户持续提供“沉浸式”体验,无限下拉刷新获取新的信息。
Feed流产品两种常见模式:
- Timeline:不做内容筛选,简单按照内容发布时间排序,常用于好友或关注。例如朋友圈
- 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息吸引用户。
本例中的个人页面,基于关注的好友做Feed流,因此采用Timeline模式,该模式实现方案有三种:
- 拉模式:读扩散
- 推模式:写扩散
- 推拉结合

推送到粉丝收件箱
需求:
- 修改新增探店笔记业务,保存到数据库同时,推送到粉丝收件箱
- 收件箱满足根据时间戳排序,必须用Redis数据结构实现
- 查询收件箱时,可实现分页查询
Feed流分页问题
Feed流中数据会不断更新,角标也在变化,不能使用传统的分页模式。
Feed流滚动分页
BlogServiceImpl:
@Overridepublic Result saveBlog(Blog blog) {//1.获取登录用户UserDTO user = UserHolder.getUser();blog.setUserId(user.getId());//2.保存探店博文boolean isSuccess = save(blog);if (!isSuccess) {return Result.fail("新增笔记失败!");}//3.查询笔记作者的所有粉丝List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();//4.推送笔记id给所有粉丝for (Follow follow : follows) {//4.1获取粉丝idLong userId = follow.getUserId();//4.2推送箱String key = "feed:" + userId;stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());}//5.返回idreturn Result.ok(blog.getId());}
滚动分页查询收件箱思路
实现滚动分页查询
分页实体类:
package com.hmdp.dto;import lombok.Data;import java.util.List;@Datapublic class ScrollResult {private List<?> list;private Long minTime;private Integer offset;}
BlogController:
@GetMapping("/of/follow")public Result queryBlogOfFollow(@RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){return blogService.queryBlogOfFollow(max, offset);}
BlogServiceImpl:
@Overridepublic Result queryBlogOfFollow(Long max, Integer offset) {// 1.获取当前用户Long userId = UserHolder.getUser().getId();// 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset countString key = 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、minTime(时间戳)、offsetList<Long> ids = new ArrayList<>(typedTuples.size());long minTime = 0; // 2int os = 1; // 2for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2// 4.1.获取idids.add(Long.valueOf(tuple.getValue()));// 4.2.获取分数(时间戳)long time = tuple.getScore().longValue();if(time == minTime){os++;}else{minTime = time;os = 1;}}// 5.根据id查询blogString 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);}
实现成功测试:

