Feed实现方案

我们关注博主之后,当用户发布了动态我们应该把这些数据推送给粉丝,关注推送也叫作Feed(投喂)流,通过无限下拉刷新获取新的信息

Feed实现的两种模式

Timeline: 对发布的信息不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注(朋友圈等)

智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容,推送用户感兴趣的信息来吸引用户

  • 优点: 投喂用户感兴趣的信息,用户粘度很高,容易沉迷
  • 缺点: 如果算法精准可能会起到反作用即给用户推了不想看的内容

个人页面一般就是基于关注的好友来做Feed流,因此采用Timeline方式需要拿到我们关注用户的发布的内容然后按照时间排序即可

在这里插入图片描述

模式也叫读扩散,每个用户都有自己的发件箱和收件箱

在这里插入图片描述

推模式也叫写扩散,推模式没有发件箱

  • 优点时效快: 系统主动将博主发布的动态推送到其粉丝的收件箱中,这样用户每次读取信息时就不需要临时拉取
  • 缺点: 内存压力大,假设一个大V发了一个动态此时就会写很多份数据发到他的粉丝收件箱中

在这里插入图片描述

推拉结合也叫读写混合,兼具推和拉两种模式的优点,只要大v才有发件箱

在这里插入图片描述

推送笔记ID到粉丝收件箱

需求: 修改新增探店笔记业务,在保存Blog对象数据库的同时推送笔记到粉丝的收件箱

传统的分页模式: 查询数据时要求数据库中的数据角标必须固定,否则可能会出现数据重复读取的情况

在这里插入图片描述

Feed流的滚动分页: 记录每次查询操作最后一条数据,下次查询时会从上次读取的最后一条数据之后开始读取数据

在这里插入图片描述

第一步: 修改BlogController中保存笔记方法,当博主发布完探店笔记后还要将发布笔记的Id推送到所有粉丝的收件箱中,score值是当前的时间戳(默认升序)

// 在RedsiConstants声明一个常量作为用户收件箱的前缀
public static final String FEED_KEY = "feed:";
@Resource
private IFollowService followService;
@Override
public Result saveBlog(Blog blog) {
    // 获取登录用户
    UserDTO user = UserHolder.getUser();
    blog.setUserId(user.getId());
    // 保存探店博文
    boolean isSuccess = save(blog);
    if(!isSuccess){
        return Result.fail("新增笔记失败");
    }
    // 条件构造器
    LambdaQueryWrapper<Follow> queryWrapper = new LambdaQueryWrapper<>();
    // 从follow表中查找博主的所有粉丝select * from follow where follow_user_id = user_id
    queryWrapper.eq(Follow::getFollowUserId, user.getId());
    List<Follow> follows = followService.list(queryWrapper);
    for (Follow follow : follows) {
        // 获取粉丝Id
        Long userId = follow.getUserId();
        // 推送发布笔记的Id到每个粉丝的收件箱(score值是当前时间戳),每个粉丝都有一个自己的收件箱
        String key = FEED_KEY + userId;
        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
    }
    // 返回笔记的id
    return Result.ok(blog.getId());
}

查询推送笔记

需求: 在个人主页的关注栏中查询并展示推送的Blog信息

ZREVRANGEBYSCORE key 最大值范围 最小值范围 WITHSCORES LIMIT offSet(偏移量) 查询到的个数: 获取指定的score范围内的元素并按照降序排序

在这里插入图片描述

第一步: 业务中不一定只对Blog进行分页查询,可以使用泛型做一个通用的分页查询

@Data
public class ScrollResult {
    // 封装查询到的数据
    private List<?> list;
    // 记录本次查询的最小时间戳,作为下一次查询的最大值(起始值)
    private Long minTime;
    // 记录偏移量
    private Integer offset;
}

第二步: 在BlogController创建对应的分页查询方法, 具体实现逻辑BlogServiceImpl中完成

@GetMapping("/of/follow")
// 由于第一次查询的时候没有传递offset参数,可以设置默认值为0
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. 查询该用户的收件箱获取该用户对应的SortedSet集合中所有的笔记Id及score值看是否有关注的博主发了笔记
    String key = FEED_KEY + userId;
    Set<ZSetOperations.TypedTuple<String>> typeTuples = stringRedisTemplate.opsForZSet()
            .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
    //3. 非空判断
    if (typeTuples == null || typeTuples.isEmpty()){
        return Result.ok(Collections.emptyList());
    }
    //4. 获取SortedSet集合中所有的笔记Id及score值,List集合的大小和Set集合一致,可以略微提高效率避免长度重置
    ArrayList<Long> ids = new ArrayList<>(typeTuples.size());
    // 保存最小的时间戳
    long minTime = 0;
    // 记录最小时间戳的个数即偏移量
    int os = 1;
    for (ZSetOperations.TypedTuple<String> typeTuple : typeTuples) {
        //4.1 获取推送的Blog的Id
        String id = typeTuple.getValue();
        ids.add(Long.valueOf(id));
        //4.2 将笔记对应的score(时间戳)转换为long类型
        long time = typeTuple.getScore().longValue();
        if (time == minTime){// 如果当前的时间戳等于最小时间戳则最小时间戳个数+1
            os++;
        }else {// 如果当前时间戳不等于最小时间戳,则把当前时间作为最小时间戳同时把最小时间戳的个数重置为1
            minTime = time;
            os = 1;
        }
    }
    // 解决MySQL的in语句自动按照id大小排序问题,手动指定排序方式为传入的ids集合中的顺序
    String idsStr = StrUtil.join(",");
    //5. 根据推送的ids集合查询所有的blog
    List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + idsStr + ")").list()
    for (Blog blog : blogs) {
        //5.1 查询发布该blog的用户信息
        queryBlogUser(blog);
        //5.2 查询当前用户是否给该blog点过赞
        isBlogLiked(blog);
    }
    //6. 封装结果返回
    ScrollResult scrollResult = new ScrollResult();
    scrollResult.setList(blogs);
    scrollResult.setOffset(os);
    scrollResult.setMinTime(minTime);
    return Result.ok(scrollResult);
}

原文地址:https://blog.csdn.net/qq_57005976/article/details/134623418

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_4415.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注