实现Feed功能

Posted Natcret

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现Feed功能相关的知识,希望对你有一定的参考价值。

分类

Feed流的分类有很多种,但最常见的分类有两种:

  1. Timeline:按发布的时间顺序排序,先发布的先看到,后发布的排列在最顶端,类似于微信朋友圈,微博等。这也是一种最常见的形式。产品如果选择Timeline类型,那么就是认为Feed流中的Feed不多,但是每个Feed都很重要,都需要用户看到
  2. Rank:按某个非时间的因子排序,一般是按照用户的喜好度排序,用户最喜欢的排在最前面,次喜欢的排在后面。这种一般假定用户可能看到的Feed非常多,而用户花费在这里的时间有限,那么就为用户选择出用户最想看的Top N结果,场景的应用场景有图片分享、新闻推荐类、商品推荐等


    实现Feed流最核心的两点:一个是存储,另一个是推送

存储
因为该项目中Feed比较简单,就类比于空间说说,因此可以使用mysql关系型数据库存储,如果对于数据结构比较复杂的Feed流就要使用NoSQL数据库,这样存储更方便与高效,比如MongoDB或者HBase。

推送

在推送方案里面的,有三种方案,分别是:

  • 拉方案:也称为读扩散,用户主动去拉取关注人的Feed内容
  • 推方案:也成为写扩散,当用户添加Feed时,会自动将Feed通知给关注的人(优选)
    使用Redis Sorted Sets(方便按时间排序Timeline)维护粉丝的Feed集合,当博主添加Feed时,主动将内容推送到粉丝的Feed集合中,这样用户可以很方便快速从集合中读取
  • 推拉结合:比如微博,大部分用户的账号关系都是几百个,但是有个别用户是1000万以上才使用。

表结构设计

CREATE TABLE `t_feeds` (
`id`  int(11) NOT NULL AUTO_INCREMENT ,
`content`  varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT \'内容\' ,
`fk_diner_id`  int(11) NULL DEFAULT NULL ,
`praise_amount`  int(11) NULL DEFAULT NULL COMMENT \'点赞数量\' ,
`comment_amount`  int(11) NULL DEFAULT NULL COMMENT \'评论数量\' ,
`fk_restaurant_id`  int(11) NULL DEFAULT NULL ,
`create_date`  datetime NULL DEFAULT NULL ,
`update_date`  datetime NULL DEFAULT NULL ,
`is_valid`  tinyint(1) NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8mb4 COLLATE=utf8mb4_general_ci
AUTO_INCREMENT=14
ROW_FORMAT=COMPACT
;

接下来编写实体类 创建 ms-feeds 模块。在application.yml中配置数据库、配置 Eureka Server 注册中心、远程服务地址(ms-oauth-server、ms-diners-server)。把好友数据集合存到redis所以需要RedisTemplate,另外RestTemplate用于远程调用其他服务。


FeedsService中添加业务逻辑

@Service
public class FeedsService {

    @Value("${service.name.ms-oauth-server}")
    private String oauthServerName;
    @Value("${service.name.ms-follow-server}")
    private String followServerName;
    @Value("${service.name.ms-diners-server}")
    private String dinersServerName;
    @Resource
    private RestTemplate restTemplate;
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private FeedsMapper feedsMapper;

    /**
     * 添加 Feed
     *
     * @param feeds       feed数据
     * @param accessToken 登录token
     */
    @Transactional(rollbackFor = Exception.class)
    public void create(Feeds feeds, String accessToken) {
        // 非空校验
        AssertUtil.isNotEmpty(feeds.getContent(), "请输入内容");
        AssertUtil.isTrue(feeds.getContent().length() > 255, "输入内容太多,请重新输入");
        // 获取登录用户信息
        SignInDinerInfo dinerInfo = loadSignInDinerInfo(accessToken);
        // Feed 关联用户信息
        feeds.setFkDinerId(dinerInfo.getId());
        // 添加 Feed
        int count = feedsMapper.save(feeds);
        AssertUtil.isTrue(count == 0, "添加失败");

        // 推送到粉丝的列表中 -- 后续这里应该采用异步消息队列解决性能问题

        // 先获取我的粉丝
        List<Integer> followers = findFollowers(dinerInfo.getId());

        // 推送 Feeds,以时间作为分数存储至 ZSet
        long now = System.currentTimeMillis();
        followers.forEach(follower -> {
            String key = RedisKeyConstant.following_feeds.getKey() + follower;
            redisTemplate.opsForZSet().add(key, feeds.getId(), now);
        });
    }

    /**
     * 获取用户粉丝
     *
     * @param dinerId 用户id
     * @return
     */
    private List<Integer> findFollowers(Integer dinerId) {
        String url = followServerName + "followers/" + dinerId;
        ResultInfo followersResultInfo = restTemplate.getForObject(url, ResultInfo.class);
        if (followersResultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(followersResultInfo.getCode(), followersResultInfo.getMessage());
        }

        List<Integer> followers = (List<Integer>) followersResultInfo.getData();
        return followers;
    }

    /**
     * 获取登录用户信息
     *
     * @param accessToken
     * @return
     */
    private SignInDinerInfo loadSignInDinerInfo(String accessToken) {
        // 是否有 accessToken
        AssertUtil.mustLogin(accessToken);
        String url = oauthServerName + "user/me?access_token={accessToken}";
        ResultInfo resultInfo = restTemplate.getForObject(url, ResultInfo.class, accessToken);
        if (resultInfo.getCode() != ApiConstant.SUCCESS_CODE) {
            throw new ParameterException(resultInfo.getCode(), resultInfo.getMessage());
        }
        // 这里的data是一个LinkedHashMap,SignInDinerInfo
        SignInDinerInfo dinerInfo = BeanUtil.fillBeanWithMap((LinkedHashMap) resultInfo.getData(),
                new SignInDinerInfo(), false);
        return dinerInfo;
    }

}

FeedsController方法、FeedsMapper、网关配置省略

以上是关于实现Feed功能的主要内容,如果未能解决你的问题,请参考以下文章

IOS开发-OC学习-常用功能代码片段整理

html PHP代码片段: - AJAX基本示例:此代码演示了使用PHP和JavaScript实现的基本AJAX功能。

Redis实战之好友关注功能

Redis实战之好友关注功能

具有相同功能的活动和片段

Notepad++编辑器——Verilog代码片段直接编译