Feed流(关注推送)

介绍

关注推送也叫做Feed流,直译为投喂。为用户持续的提供"沉浸式"的体验,通过无限下拉刷新获取新的信息。

Feed流产品有两种常见模式:

  • Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如朋友圈
    • 优点:信息全面,不会有缺失。并且实现也相对简单
    • 缺点:信息噪音较多,用户不一定感兴趣,内容获取效率低
  • 智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户
    • 优点:投喂用户感兴趣信息,用户粘度很高,容易沉迷
    • 缺点:如果算法不精准,可能起到反作用

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

  1. 拉模式(读扩散)
  2. 推模式(写扩散)
  3. 推拉结合(读写混合):兼具推和拉两种模式的优点。

三种模式的详情以及对比可看:实战篇-07.好友关注-Feed流实现方案分析_哔哩哔哩_bilibili

本文是通过推模式来实现关注推送功能

推送实现

java 复制代码
@Override
public Result saveBlog(Blog blog) {
    // 获取登录用户
    UserDTO user = UserHolder.getUser();
    blog.setUserId(user.getId());
    // 保存探店博文
    boolean isSuccess = save(blog);
    if(!isSuccess) {
        return Result.fail("新增笔记失败!");
    }
    // 查询笔记作者的所有粉丝
    List<Follow> follows = followService.query().eq("follow_user_id", user.getId()).list();
    // 推送笔记id给所有粉丝
    for (Follow follow : follows) {
        // 获取粉丝id
        Long userId = follow.getUserId();
        // 推送
        String key = "feed:" + userId;
        stringRedisTemplate.opsForZSet().add(key, blog.getId().toString(), System.currentTimeMillis());
    }
    // 返回id
    return Result.ok(blog.getId());
}

逻辑并不复杂

滚动分页实现

分页问题

Feed流中的数据会不断更新,所以数据的角标也在变化,因此不能采用传统的分页模式,而是采用滚动分页的方式

详见:实战篇-09.好友关注-滚动分页查询收件箱的思路_哔哩哔哩_bilibili

代码实现

java 复制代码
@Override
public Result queryBlogOfFollow(Long max, Integer offset) {
    // 获取当前用户
    Long userId = UserHolder.getUser().getId();
    // 查询收件箱
    String key = RedisConstants.FEED_KEY + userId;
    Set<ZSetOperations.TypedTuple<String>> typedTuples = stringRedisTemplate.opsForZSet()
            .reverseRangeByScoreWithScores(key, 0, max, offset, 2);
    if (typedTuples == null || typedTuples.isEmpty())
        return Result.ok();
    // 解析数据
    List<Long> ids = new ArrayList<>(typedTuples.size());
    long minTime = 0;
    int os=1;
    for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
        // 获取笔记id
        ids.add(Long.valueOf(typedTuple.getValue()));
        // 获取分数
        long time = typedTuple.getScore().longValue();
        if(time == minTime)
            os++;
        else {
            os=1;
            minTime = time;
        }
    }
    // 根据id查询笔记
    List<Blog> blogs = query().in("id", ids).last("ORDER BY FIELD(id," + StrUtil.join(",", ids) + ")").list();
    blogs.forEach(blog -> {
        // 查询用户
        queryBlogUser(blog);
        // 查询blog是否被点赞
        isBlogLiked(blog);
    });
    // 封装并返回
    ScrollResult scrollResult = new ScrollResult();
    scrollResult.setList(blogs);
    scrollResult.setMinTime(minTime);
    scrollResult.setOffset(os);
    return Result.ok(scrollResult);
}

请求参数

max:上一次查询的最小时间戳

offset:偏移量

返回值里scrollResult会把当前查询后的max和offset返回给前端,下一次查询的参数就是通过这次的返回值得到的

相关推荐
guslegend6 分钟前
脚手架原理与本地调试:从 bin 软链接到 npm link
前端·npm·node.js·脚手架·前端工程化
上位机妹子28 分钟前
EF Core 两种继承映射:每层次一张表(TPH)与每类型一张表(TPT)
数据库·c#·.netcore·ef core
小马同学-34 分钟前
Redis消息队列与客户端编程
数据库·redis
夜之眷属37 分钟前
Core dump 崩溃排查:JVM 宕机后,那份 core 文件怎么用 gdb 还原现场
java·运维·服务器·jvm
海码事务所39 分钟前
Google Play 新个人开发者账号上架指南:12 人连续 14 天封闭测试怎么做?
前端
波加曼大王40 分钟前
# vLLM不要迷信PagedAttention神话,聊聊线上藏着的内部碎片陷阱
java·架构
田威AI43 分钟前
图片内文字翻译的规格:输入输出、保真、自动化、时间与费用
前端·计算机视觉
SL_staff44 分钟前
目标健康度自检清单:开发者视角下的目标-计划-任务链路断点诊断
java·开源·github
for_ever_love__1 小时前
MySQL 高频面试题 30 问:索引、事务、锁、日志、主从与分库分表(MySQL 8.0 版)
java·数据库·spring boot·mysql·spring cloud·面试·总结
神仙别闹1 小时前
基于C++实现的贪吃蛇游戏平台
java·c++·游戏