使用Node + MongoDB + Redis构建简单的新闻订阅系统

16

我的目标是使用mongodb和redis在node.js中构建一个简单的新闻订阅功能。它类似于Twitter。

因此,场景非常简单,一旦用户A关注了用户B,之后用户A的新闻订阅(主页)将显示用户B的活动,比如他发布的内容。

用户模式

const UserSchema = new Schema({
  email: { type: String, unique: true, lowercase: true},
});



const followSchema = new Schema(
    {
        user: { type: Schema.Types.ObjectId, required: true, ref: 'User' },
        target: { type: Schema.Types.ObjectId, required: true, ref: 'User' },
    });

目前我的用户架构设计相当简单,当我关注另一个用户时,我只会创建“关注架构对象”。

还有另一个架构,即帖子架构。

/* This is similar like the Tweet */
var PostSchema = new Schema({
  // Own by the user
  creator: { type: Schema.Types.ObjectId, ref: 'User' }
  body: String,
});

这个架构是用于用户发布任何内容的,类似于 Twitter 的发布。

假设我已经关注了一堆用户。

{
   user: 'me',
   target: 'draco'
},

{
  user: 'me',
  target: 'donald'
},

{
  user: 'me',
  target: 'joker'
}

假设我的一个关注者发布了一些内容,我要如何将它呈现在我的最新动态中?

/* I'm following Joker */
app.post('/follow', (req, res, next) => {
   let follow = new Follow();
   follow.user = "me";
   follow.target = "joker";
   // Do i need to use redis to subscribe to him?
   follow.save();
})


/* Joker posted something */
app.post('/tweet',(req, res, next) => {
   let post = new Post();
   post.creator = "joker";
   post.body = "Hello my name is joker"
   post.save();
   // Do i need to publish it using redis so that I will get his activity?

});

这是我的尝试

app.get('/feed', function(req, res, next) {

     // Where is the redis part?
     User.findOne({ _id: req.user._id }, function(err, foundUser) {
        // this is pretty much my attempt :(
     })
})

我应该在什么时候使用Redis进行发布和订阅?以便我能够获取我的一个关注者的内容并在我的时间线上展示它。


你在查询中没有对关注者做任何操作,而是只针对当前用户。 - abdulbarik
我应该对我的关注者数组做些什么?我需要循环遍历它们吗? - sinusGob
2个回答

17

我已经建立了一个社交网络,其中包括新闻动态。这就是我实现的方法。


基本上,你有两种方法来构建新闻动态:

  1. 写入时扇出(推送)方法
  2. 读取时扇出(拉取)方法

写入时扇出

首先,您需要另一个集合:

const Newsfeed = new mongoose.model('newsfeed', {
  owner: {type: mongoose.Types.ObjectId, required: true},
  post: {type: mongoose.Types.ObjectId, required: true}
});

当用户发布内容时:

  1. 获取n个关注者
  2. 将此帖子推送(扇出)给n个关注者

当用户获得 feed 时:

  1. Newsfeed集合中获取

示例:

router.post('/tweet', async (req, res, next) => {
  let post = await Post.create({});

  let follows = await Follow.find({target: req.user.id}).exec();

  let newFeeds = follows.map(follow => {
    return {
      user: follow.user,
      post: post.id
    }
  });
  await Newsfeed.insertMany(newFeeds);
});

router.get('/feed', async (req, res, next) => {
  let feeds = await Newsfeed.find({user: req.user.id}).exec();
});

读取时的广播

当用户发布内容时:

  1. 保存

当用户获取动态时:

  1. 获取 n 个关注者
  2. 从 n 个关注者中获取帖子

示例

router.post('/tweet', async (req, res, next) {
  await Post.save({});
});

router.get('/feeds', async (req, res, next) {
  let follows = await Follow.find({user: req.user.id}.exec();

  let followings = follows.map(follow => follow.target);

  let feeds = await Post.find({user: followings}).exec();
});

实现新闻订阅不需要使用Redis或发布/订阅功能。然而,为了提高性能,你可能需要使用Redis来实现某种缓存。

如果想了解更多信息或高级技术,请查看此链接


嘿,谢谢你的帮助!我只是想知道 Redis 部分在哪里,谢谢! - sinusGob
好的,就像我之前所说的那样,你并不需要Redis或发布/订阅机制来实现基本的新闻订阅。然而,你可以使用Redis来实现缓存以提高性能。你想让我详细解释一下吗? - willie17
@willie17 如果您能澄清上图中关于Redis的部分,那将非常棒。 - Shahin Ghasemi
@willie17 每种方法的优缺点是什么? - Shahin Ghasemi
链接无法访问。 - Alexis Tyler

7

用户模式:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

const userSchema = new Schema({
     name:{type:String},
     email: { type: String, unique: true, lowercase: true},
  },{
    collection: 'User'
  });

var User = module.exports = mongoose.model('User', userSchema);

遵循模式:

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

var followSchema = new Schema(
    {
        follow_id: { type: Schema.Types.ObjectId, required: true, ref: 'User'  },
        leader_id: { type: Schema.Types.ObjectId, required: true, ref: 'User' }
    },{
        collection:'Follow'
    });

 var Follow = module.exports = mongoose.model('Follow', followSchema);

文章模式:

 var mongoose = require('mongoose');
 var Schema = mongoose.Schema;

var postSchema = new Schema({
  creator: { type: Schema.Types.ObjectId, ref: 'User' }
  body: {type: String , required:true},
  created_at :{type:Date , default:Date.now}
},{
    collection:'Post'
  });

var Post = module.exports = mongoose.model('Post', postSchema);

假设您在用户集合中有3个用户:

{ _id: ObjectID('5a2ac68d1413751391111111') ,name:'John' , email:'john@gmail.com'}

{ _id: ObjectID('5a2ac68d1413751392222222') ,name:'Morgan' , email:'morgan@yahoo.com'}

{ _id: ObjectID('5a2ac68d1413751393333333') ,name:'Emily' , email:'emily@outlook.com'}

现在 John 关注了 Morgan 和 Emily:

因此在关注集合中有两条记录

1)follow_id = John 的 IDleader_id = Morgan 的 ID

2)follow_id = John 的 IDleader_id = Emily 的 ID

{ 
  _id: ObjectID('5a2ac68d141375139999999'),
  follow_id : ObjectID('5a2ac68d1413751391111111'),
  leader_id : ObjectID('5a2ac68d1413751392222222')
},
  {
     _id: ObjectID('5a2ac68d1413751393333333'),
    follow_id : ObjectID('5a2ac68d1413751391111111'),
    leader_id : ObjectID('5a2ac68d1413751393333333')
  }

现在如果您想获取用户的关注列表:
app.get('/following/:user_id',function(req,res){
      var userid=req.params.user_id;
      Follow.find({follow_id:mongoose.mongo.ObjectID(userid)})
      .populate('leader_id')
      .exec(function(err,followings){
       if(!err && followings){ 
          return res.json({followings:followings});
       }
      });
});

获取用户的粉丝信息:
   app.get('/followers/:user_id',function(req,res){
      var userid=req.params.user_id;
      Follow.find({leader_id:mongoose.mongo.ObjectID(userid)})
      .populate('follow_id')
      .exec(function(err,followers){
       if(!err && followers){ 
          return res.json({followers:followers});
       }
      });
});

使用npm安装redis模块

在你的app.js文件中:

var redis = require('redis');
var client = redis.createClient();

当一个用户创建帖子时:
app.post('/create_post',function(req,res){

       var creator=new mongoose.mongo.ObjectID(req.body.creator);
       var postbody=req.body.body;


       async.waterfall([
          function(callback){

          // find followers of post creator

          Follow.find({leader_id:creator})
              .select({ "follow_id": 1,"leader_id":0,"_id": 0})
              .exec(function(err,followers){
                      if(!err && followers){ 
                      callback(null,followers);
                      }
              });
         },
        function(followers, callback){

          // saving the post

         var post=new Post({
            creator: creator,
            body: postbody
         });
          post.save(function(err,post){
             if(!err && post){

   // adding newly created post id to redis by key userid , value is postid

                for(var i=0;i<followers.length;i++){
                   client.sadd([followers[i].follow_id,post.id]);
                }


                 callback(null,post);
             }
         });   
      }
   ], function (err, result) {
             if(!err && result){
                return res.json({status:"success",message:"POST created"});
             }
       });
    });

现在获取用户的新闻动态:

1)首先从用户id的redis键中获取postid数组

2)循环遍历postid并从mongo中获取post

通过用户ID获取新闻动态的函数:

app.get('/newsfeed/:user_id',function(req,res){
    var userid=req.params.user_id;
    client.smembers(userid,function(err, reply) {
        if(!err && reply){
            console.log(reply);

            if(reply.length>0){
               var posts=[];

               for(var i=0;i<reply.length;i++){
                  Post.findOne({_id:new mongoose.mongo.ObjectID(reply[i])}).populate('creator').exec(function(err,post){        
                 posts.push(post);
      });
               }

              return res.json({newsfeed:posts});

            }else{
              // No News Available in NewsFeed
            }
        }
    });

});

这里我们使用Redis存储[用户ID,帖子ID数组]以供新闻订阅使用, 但如果您不想使用Redis,则只需使用以下Newsfeed模型并存储新创建的帖子的用户ID和帖子ID,然后显示它。

NewsFeed模式:

 var mongoose = require('mongoose');
 var Schema = mongoose.Schema;

var newsFeedSchema = new Schema({
  user_id: {type: Schema.Types.ObjectId, refer:'User' , required:true}
  post_id: {type: Schema.Types.ObjectId, refer:'Post' , required:true},
},{
    collection:'NewsFeed'
  });

var NewsFeed = module.exports = mongoose.model('NewsFeed', newsFeedSchema);

Redis相关的有用链接:https://www.sitepoint.com/using-redis-node-js/

Async相关的链接:https://caolan.github.io/async/docs.html#


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接