在对象数组中填充字段 MongoDB 和 Mongoose

问题描述

我正在为我的网站构建评论系统。我已经为这篇文章创建了这个架构,它的评论是:

let articleSchema = mongoose.Schema({

  language: {
    type: String,default: "english"
  },slug: {
    type: String,required: true
  },image: {
    type: String,title: {
    type: String,tags: {
    type: [String],author: {
    type: mongoose.Schema.Types.ObjectId,ref: "User",date: {
    type: Date,default: Date.Now()
  },description: {
    type: String,required: false
  },body: {
    type: String,translation: [
    {
      language: {
        type: String,required: true
      },title: {
        type: String,tags: {
        type: [String],description: {
        type: String,required: false
      },body: {
        type: String,required: true
      }
    }
  ],comments: [{
    author: {
      type: mongoose.Schema.Types.ObjectId,ref: "User"
    },date: {
      type: Date,default: Date.Now()
    },body: {
      type: String,required: true
    }
  }]
});

现在我想做的是填充每个评论作者字段。

假设这是带有评论文章

{
  "comments": [
   {
    "author": "5f71d3b575f9f800943903af","date": "some date here","body": "comment body"
   }
  ]
}

现在我填充完所有这些后想要得到的是:

{
  "comments": [
   {
    "author": {
      "username": "Username","avatar": "fox"
    },"body": "comment body"
   }
  ]
}

我该怎么做?

这是我当前的代码


router.get('/article/:id',async (req,res) => {
  const { id: articleId } = req.params;

  const lang = req.i18n.language;
  const langName = new Intl.displayNames(['en'],{ type: "language" }).of(lang).toLowerCase();

  try {

    console.log(langName);

    if (req.i18n.language === "en") {
      var article = await Article.findById(articleId)
        .populate([
          {path:'comments.author',select:'+photo +username'},{path:'author',select:'+photo +username'}
        ])
        .select("title image tags author date description body comments");
    } else {

      var article = await Article
        .aggregate([
          {
            $match: {
              "_id": ObjectId(articleId)
            }
          },{
            $unwind: "$translation"
          },{
            $match: {
              "translation.language": langName
            }
          },{
            $group: {
              _id: "$_id",image: { $first: "$image" },title: { $first: "$translation.title" },tags: { $first: "$translation.tags" },author: { $first: "$author" },date: { $first: "$date" },description: { $first: "$translation.description" },body: { $first: "$translation.body" },comments: { $first: "$comments" }
            }
          },{
            $lookup: {
              from: "users",localField: "author",foreignField: "_id",as: "author"
            }
          },{
            $unwind: {
              path: "$author"
            }
          },{ ///////////////////// Here I need to populate the comment author. How can I do it?
            $lookup: {
              from: "users",localField: "comments.author",as: "comments.author"
            }
          },{
            $unwind: {
              path: "$comments.author"
            }
          },{
            $project: {
              image: 1,title: 1,tags: 1,date: 1,description: 1,body: 1,// comments: 1,"comments.date": 1,"comments.body": 1,"comments.author.username": 1,"comments.author.avatar": 1,"author.username": 1,"author.avatar": 1
            }
          }
        ],function(err,result) {
          console.log(err)
          return result;
        });
      
      console.log(article[0].comments[0]);
      console.log(article);
    }

    if (!article) throw new Error();
  } catch {
    return res.status(404).render('errors/404');
  }


  res.render('articles/article',{
    article: article[0]
  });
});

解决方法

所以经过一些挖掘,我想出了这个解决方案:

router.get('/article/:id',async (req,res) => {
  const { id: articleId } = req.params;

  const lang = req.i18n.language;
  const langName = new Intl.DisplayNames(['en'],{ type: "language" }).of(lang).toLowerCase();

  try {

    if (req.i18n.language === "en") {
      var article = await Article.findById(articleId)
        .populate([
          {path:'comments.author',select:'+photo +username'},{path:'author',select:'+photo +username'}
        ])
        .select("title image tags author date description body comments");
    } else {

      var article = await Article
        .aggregate([
          {
            $match: {
              "_id": ObjectId(articleId)
            }
          },{
            $unwind: "$translation"
          },{
            $match: {
              "translation.language": langName
            }
          },{
            $lookup: {
              from: "users",localField: "author",foreignField: "_id",as: "author"
            }
          },{
            $unwind: {
              path: "$author"
            }
          },{
            $unwind: {
              path: "$comments"
            }
          },localField: "comments.author",as: "comments.author"
            }
          },{
            $group: {
              _id: "$_id",root: { $mergeObjects: '$$ROOT' },image: { $first: "$image" },title: { $first: "$translation.title" },tags: { $first: "$translation.tags" },author: { $first: "$author" },date: { $first: "$date" },description: { $first: "$translation.description" },body: { $first: "$translation.body" },comments: { $push: "$comments" }
            }
          },{
            $project: {
              image: 1,title: 1,tags: 1,date: 1,description: 1,body: 1,"comments.date": 1,"comments.body": 1,"comments.author.username": 1,"comments.author.avatar": 1,"author.username": 1,"author.avatar": 1
            }
          }
        ]);
      
    }

    if (!article) throw new Error();
  } catch {
    return res.status(404).render('errors/404');
  }


  res.render('articles/article',{
    article: article[0]
  });
});