关注和取关

在探店图文详情页面,可关注发布笔记的作者
image.png
需求:基于该表数据结构,实现两个接口:

  • 关注和取关接口
  • 判断是否关注接口

关注是User之间的关系,用tb_follow表示
image.png

  1. @Service
  2. public class FollowServiceImpl extends ServiceImpl<FollowMapper, Follow> implements IFollowService {
  3. @Override
  4. public Result follow(Long followUserId, Boolean isFollow) {
  5. //0.获取登录用户
  6. Long userId = UserHolder.getUser().getId();
  7. //1.判断是关注还是取关
  8. if (isFollow) {
  9. //2.关注,新增数据
  10. Follow follow = new Follow();
  11. follow.setUserId(userId);
  12. follow.setFollowUserId(followUserId);
  13. save(follow);
  14. }else{
  15. //3.取关,删除数据
  16. remove(new QueryWrapper<Follow>().eq("user_id", userId).eq("follow_user_id",followUserId));
  17. }
  18. return Result.ok();
  19. }
  20. @Override
  21. public Result isFollow(Long followUserId) {
  22. //0.获取登录用户
  23. Long userId = UserHolder.getUser().getId();
  24. //1.查询是否关注
  25. Integer count = query().eq("user_id", userId).eq("follow_user_id", followUserId).count();
  26. return Result.ok(count > 0);
  27. }
  28. }

共同关注

image.png

第一步:实现用户主页

实现点击头像后跳转的个人主页的两个功能(因为跳转后先是停留在笔记页面,所以与共同关注逻辑无关):

  1. 根据id查询到该用户信息(上半部分)
  2. 根据id查询到该用户的博客笔记(下半部分) ```java // UserController 根据id查询用户 @GetMapping(“/{id}”) public Result queryUserById(@PathVariable(“id”) Long userId){ // 查询详情 User user = userService.getById(userId); if (user == null) {
    1. return Result.ok();
    } UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class); // 返回 return Result.ok(userDTO); }

// BlogController @GetMapping(“/of/user”) public Result queryBlogByUserId( @RequestParam(value = “current”, defaultValue = “1”) Integer current, @RequestParam(“id”) Long id) { // 根据用户查询 Page page = blogService.query() .eq(“user_id”, id).page(new Page<>(current, SystemConstants.MAX_PAGE_SIZE)); // 获取当前页数据 List records = page.getRecords(); return Result.ok(records); }

  1. <a name="draNl"></a>
  2. ### 第二步:实现共同关注
  3. 需求:利用Redis实现共同关注(Set求交集->SINTER s1 s2)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1123881/1651675554083-106032bd-b3d3-4ea6-9f82-f75fa1b78ad9.png#clientId=u93af1c1c-3880-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=428&id=u4afdf32e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=856&originWidth=1939&originalType=binary&ratio=1&rotation=0&showTitle=false&size=361397&status=done&style=none&taskId=ud1b7182f-a8a9-4504-9b98-105af320dde&title=&width=969.5)
  4. ```java
  5. @Override
  6. public Result followCommons(Long id) {
  7. //1.获取当前用户
  8. Long userId = UserHolder.getUser().getId();
  9. String key = "follows:" + userId;
  10. //2.求交集
  11. String key2 = "follows:" + id;
  12. Set<String> intersect = stringRedisTemplate.opsForSet().intersect(key, key2);
  13. if(intersect == null || intersect.isEmpty()){
  14. return Result.ok(Collections.emptyList());
  15. }
  16. List<Long> ids = intersect.stream().map(Long::valueOf).collect(Collectors.toList());
  17. List<UserDTO> users = userService.listByIds(ids)
  18. .stream()
  19. .map(user -> BeanUtil.copyProperties(user, UserDTO.class))
  20. .collect(Collectors.toList());
  21. return Result.ok(users);
  22. }

关注推送

Feed流实现方案分析

关注推送也叫Feed流,直译为投喂。为用户持续提供“沉浸式”体验,无限下拉刷新获取新的信息。
Feed流产品两种常见模式:

  • Timeline:不做内容筛选,简单按照内容发布时间排序,常用于好友或关注。例如朋友圈
  • 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息吸引用户。

本例中的个人页面,基于关注的好友做Feed流,因此采用Timeline模式,该模式实现方案有三种:

  1. 拉模式:读扩散
  2. 推模式:写扩散
  3. 推拉结合

image.png

image.png

推送到粉丝收件箱

需求:

  1. 修改新增探店笔记业务,保存到数据库同时,推送到粉丝收件箱
  2. 收件箱满足根据时间戳排序,必须用Redis数据结构实现
  3. 查询收件箱时,可实现分页查询

Feed流分页问题
Feed流中数据会不断更新,角标也在变化,不能使用传统的分页模式。
Feed流滚动分页
image.png
BlogServiceImpl:

  1. @Override
  2. public Result saveBlog(Blog blog) {
  3. //1.获取登录用户
  4. UserDTO user = UserHolder.getUser();
  5. blog.setUserId(user.getId());
  6. //2.保存探店博文
  7. boolean isSuccess = save(blog);
  8. if (!isSuccess) {
  9. return Result.fail("新增笔记失败!");
  10. }
  11. //3.查询笔记作者的所有粉丝
  12. List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
  13. //4.推送笔记id给所有粉丝
  14. for (Follow follow : follows) {
  15. //4.1获取粉丝id
  16. Long userId = follow.getUserId();
  17. //4.2推送箱
  18. String key = "feed:" + userId;
  19. stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
  20. }
  21. //5.返回id
  22. return Result.ok(blog.getId());
  23. }

滚动分页查询收件箱思路

image.png
image.png

实现滚动分页查询

分页实体类:

  1. package com.hmdp.dto;
  2. import lombok.Data;
  3. import java.util.List;
  4. @Data
  5. public class ScrollResult {
  6. private List<?> list;
  7. private Long minTime;
  8. private Integer offset;
  9. }

BlogController:

  1. @GetMapping("/of/follow")
  2. public Result queryBlogOfFollow(
  3. @RequestParam("lastId") Long max, @RequestParam(value = "offset", defaultValue = "0") Integer offset){
  4. return blogService.queryBlogOfFollow(max, offset);
  5. }

BlogServiceImpl:

  1. @Override
  2. public Result queryBlogOfFollow(Long max, Integer offset) {
  3. // 1.获取当前用户
  4. Long userId = UserHolder.getUser().getId();
  5. // 2.查询收件箱 ZREVRANGEBYSCORE key Max Min LIMIT offset count
  6. String key = FEED_KEY + userId;
  7. Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
  8. .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
  9. // 3.非空判断
  10. if (typedTuples == null || typedTuples.isEmpty()) {
  11. return Result.ok();
  12. }
  13. // 4.解析数据:blogId、minTime(时间戳)、offset
  14. List<Long> ids = new ArrayList<>(typedTuples.size());
  15. long minTime = 0; // 2
  16. int os = 1; // 2
  17. for (ZSetOperations.TypedTuple<String> tuple : typedTuples) { // 5 4 4 2 2
  18. // 4.1.获取id
  19. ids.add(Long.valueOf(tuple.getValue()));
  20. // 4.2.获取分数(时间戳)
  21. long time = tuple.getScore().longValue();
  22. if(time == minTime){
  23. os++;
  24. }else{
  25. minTime = time;
  26. os = 1;
  27. }
  28. }
  29. // 5.根据id查询blog
  30. String idStr = StrUtil.join(",", ids);
  31. List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idStr + ")").list();
  32. for (Blog blog : blogs) {
  33. // 5.1.查询blog有关的用户
  34. queryBlogUser(blog);
  35. // 5.2.查询blog是否被点赞
  36. isBlogLiked(blog);
  37. }
  38. // 6.封装并返回
  39. ScrollResult r = new ScrollResult();
  40. r.setList(blogs);
  41. r.setOffset(os);
  42. r.setMinTime(minTime);
  43. return Result.ok(r);
  44. }

实现成功测试:image.png