当modelObject从数据库本身获得时,modelObject.save()是否只更新现有的数据库文档?

要使用演示该问题的示例,假定我有一个由以下模式定义的用户模型:

var UserSchema = new Schema({ username: String, email: String } mongoose.model('User', UserSchema); 

我知道要使用save方法更新用户,我可以查询用户,然后保存更改,如下所示:

 User.findOne({username: usernameTofind}, function(err, user) { //ignore errors for brevity user.email = newEmail; user.save(function(err) { console.log('User email updated') }); }); 

但是,如果我尝试创build一个新的用户对象与完全相同的字段值(包括_id)是否有覆盖数据库文件的任何可能性? 我不会假设,因为从理论上讲,这意味着恶意用户可能利用不安全的API并覆盖现有文档(例如使用“创build新帐户”请求,这将不会/不能依赖于用户被authentication),但更重要的是,当我尝试使用请求工具(我使用邮差,但我敢肯定类似的curl命令就足够了)这样做,我得到一个重复的_id错误

 MongoError: insertDocument :: caused by :: 11000 E11000 duplicate key error index 

所以我只想澄清,更新现有文档的唯一方法是查询文档,修改返回的实例,然后调用该实例的save方法,或者使用静态update()。 这两者都可以通过要求authentication来保护。

如果有帮助,我上面提到了这个问题的动机,因为我想确保用户不能覆盖现有文档,如果公开暴露以下方法:

 userCtrl.create = function(req, res, next) { var user = new User(req.body); user.save(function(err) { if (err) { return next(err); } else { res.json(user); } }); }; 

快速编辑:我刚刚意识到,如果是这样的话,那么数据库如何知道查询的实例和具有完全相同的键和属性的新的用户对象之间的区别?

当modelObject从数据库本身获得时, modelObject.save()是否只更新现有的数据库文档?

是的,它确实。 有一个标志,表明文件是否是新的。 如果是新的,Mongoose会插入文件。 如果不是,那么它将更新文件。 该标志是Document#isNew

当你find一个文件:

 User.findOne({username: usernameTofind}, function(err, user) { //ignore errors for brevity console.log(user.isNew) // => will return false }); 

当你创build一个新的实例时:

 var user = new User(req.body); console.log(user.isNew) // => will return true 

所以我只想澄清,更新现有文档的唯一方法是查询文档,修改返回的实例,然后调用该实例的save方法,或者使用静态update() 。 这两者都可以通过要求authentication来保护。

您还可以使用Model#updateModel.findOneAndUpdate 等更新文档。

但是,您不能更新_id字段。 即使Mongoose没有发出正确的数据库命令,MongoDB也不会允许它。 如果你尝试它,你会得到这样的错误:

 The _id field cannot be changed from {_id: ObjectId('550d93cbaf1e9abd03bf0ad2')} to {_id: ObjectId('550d93cbaf1e9abd03bf0ad3')}. 

但假设你正在使用你的问题的最后一段代码来创build新的用户,Mongoose将发出一个insert命令,所以没有办法有人可以覆盖现有的文件。 即使它在请求体中传递了一个_id字段,MongoDB也会抛出E11000 duplicate key error index错误。

此外,您应该筛选用户可以作为有效内容传递的字段,然后再使用它们来创build用户。 例如,您可以创build一个通用函数来接收一个对象和一个允许的参数数组:

 module.exports = function(object, allowedParams) { return Object.keys(object).reduce(function(newObject, param) { if (allowedParams.indexOf(param) !== -1) newObject[param] = object[param]; return newObject; }, {}); } 

然后你只需要使用这个函数来过滤请求体:

 var allow = require('./parameter-filter'); function filter(params) { return allow(params, ["username", "email"]); } var user = new User(filter(req.body));