Mongoose: 验证 ObjectId 的最佳方法

创建于 2014-03-12  ·  29评论  ·  资料来源: Automattic/mongoose

我正在插入一些 objectIds 作为引用,并且我想确保它们是有效的 objectids(现在当它们不是有效的 ids 时我会崩溃)。

我正在检查它们是否通过调用 find() 来引用有效对象。

我应该如何检查字符串的格式是否正确? 我见过一些正则表达式检查字符串是否是 24 个字符的字符串和类似的东西 - 似乎是一个 hack。 ObjectId 上是否有内部验证器? 我无法弄清楚如何使用它。 非常感谢!

最有用的评论

这对我有用:

var mongoose = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [函数:isValid]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// 真的
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// 错误的

所有29条评论

我有同样的问题。 我尝试了 mongoose.Schema.ObjectId.isValid() 和 mongoose.Types.ObjectId.isValid() 但这些属性都没有 isValid 方法。 你最终是如何解决这个问题的? 我还看到 mongodb 有一个,还有正则表达式作为另一种选择。 我宁愿不使用正则表达式也不必 require('mongodb')

这对我有用:

var mongoose = require('./node_modules/mongoose');
console.log(mongoose.Types.ObjectId.isValid);
// [函数:isValid]
console.log(mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943'));
// 真的
console.log(mongoose.Types.ObjectId.isValid('bleurgh'));
// 错误的

顺便说一句,mongodb 中的方法是 bson lib 方法,它只检查非空、12 或 24 字符的十六进制字符串 - 它是这样的正则表达式:

var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");

所以如果在那里使用它就不会太hacky

如果字符串包含 12 个字母,sValid() 总是返回 True

console.log(mongoose.Types.ObjectId.isValid("zzzzzzzzzzzz")); // 真的

因为“zzzzzzzzzzzz”在技术上是一个有效的 ObjectId - 一个对象 id 的唯一定义特征是它的 12 字节长。 见 mongodb/js-bson#112。 长度为 12 的 JS 字符串有 12 个字节,以 unicode 为模。 如果要检查长度为 24 的十六进制字符串,只需检查字符串是否匹配/^[a-fA-F0-9]{24}$/

“zzzzzzzzzzzz”不是有效的 ObjectId。 例如 Mongo shell listiong(mongodb 版本 - 3.0.2):

> ObjectId('zzzzzzzzzzzz')
2015-04-29T18:05:20.705+0300 E QUERY    Error: invalid object id: length
    at (shell):1:1
> ObjectId('zzzzzzzzzzzzzzzzzzzzzzzz')
2015-04-29T18:06:09.773+0300 E QUERY    Error: invalid object id: not hex
    at (shell):1:1
> ObjectId('ffffffffffff')
2015-04-29T18:09:17.303+0300 E QUERY    Error: invalid object id: length
    at (shell):1:1
> ObjectId('ffffffffffffffffffffffff')
ObjectId("ffffffffffffffffffffffff")

因为 mongodb shell 的 ObjectId 构造函数是这样编写的,它只接受十六进制字符串。 为了方便起见,这是 mongo shell 中的一个限制,而不是 BSON 类型的 ObjectId。 诚然,这是一个有点违反直觉的情况,因为十六进制字符串通常是 ObjectIds 的表示方式,但如果你不喜欢它,那么只需使用正则表达式/^[a-fA-F0-9]{24}$/ :)

当我们尝试对 ObjectId 本身而不是 String 执行isValid时,为什么我们会得到错误? 既然 ObjectId 是有效的 ObjectId,这不应该返回 true 吗? 这没有意义——如果.toString()是一个传递给isValid的对象,可能会调用它?

@niftylettuce欢迎在 #3365 发表评论。 现在我们只是推迟到bson 包的 ObjectId.isValid() 函数,它与人们对 mongoose 中 ObjectIds 的看法并不完全一致。 如果你得到一个 ObjectId,我会打开一个 PR 来返回 true,这似乎是完全合理的。

回到这里的一个老问题...... @atcwells解决方案mongoose.Types.ObjectId.isValid('53cb6b9b4f4ddef1ad47f943')对我来说已经足够好了,但是让我的控制器检查对象 ID 是否有效似乎仍然有点笨拙——什么时候当然,希望能够将格式错误的 ID 发送到服务器而不让它崩溃是一个非常常见的用例。

理想情况下,它会简单地在回调中的err中返回一些内容,以便我们可以正确处理它并使用我们的控制器发送正确的 HTTP 状态。

是否有一个用例在核心中这不是有用的功能? 如果没有,也许我们可以制作一个插件。 我进行了快速搜索,似乎没有任何东西可以完成这项工作 - https://github.com/CampbellSoftwareSolutions/mongoose-id-validator用于验证 ID 是否确实存在,这不是什么我们想在这里做——我们只是想确保我们不会产生未捕获的错误。

现在,在我的 Express 控制器中,每次我发出一个包含 ObjectId 的请求时,例如 GET 到https://myproject/organisations/ {id},我必须执行以下操作:

if( !mongoose.Types.ObjectId.isValid(id) ){
    return res.sendStatus(400); // They didn't send an object ID
  }

...在此之前继续做Organisation.findOne();

看起来很样板。 如果有人能指出我从哪里开始的正确方向,我很乐意写一个插件或其他东西。 看起来不像一个插件,因为它不是一个真正的架构东西......

@shankiesan ,您不需要这样做,如果 id 无效,猫鼬将拒绝查询承诺。

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

mongoose.connect('mongodb://localhost/test');
mongoose.set('debug', true);

var MyModel = mongoose.model('test', new Schema({ name: String }));

MyModel.findOne({ _id: 'invalid' }).exec().catch(error => console.error('error', error));

输出:

$ node gh-1959.js 
error { CastError: Cast to ObjectId failed for value "invalid" at path "_id"
    at MongooseError.CastError (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/error/cast.js:19:11)
    at ObjectId.cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/schema/objectid.js:147:13)
    at ObjectId.castForQuery (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/schema/objectid.js:187:15)
    at cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/cast.js:174:32)
    at Query.cast (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2563:10)
    at Query.findOne (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:1239:10)
    at /home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2163:21
    at new Promise.ES6 (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/promise.js:45:3)
    at Query.exec (/home/val/Workspace/10gen/troubleshoot-mongoose/node_modules/mongoose/lib/query.js:2156:10)
    at Object.<anonymous> (/home/val/Workspace/10gen/troubleshoot-mongoose/gh-1959.js:10:37)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
  message: 'Cast to ObjectId failed for value "invalid" at path "_id"',
  name: 'CastError',
  kind: 'ObjectId',
  value: 'invalid',
  path: '_id',
  reason: undefined }
^C
$ 

啊,我真是个布偶! 当然,处理被拒绝的承诺...... arg,我的大脑。 谢谢@vkarpov15

如果 promise 太让人头疼的话,回调也可以工作MyModel.findOne({ _id: 'invalid' }).exec(error => console.error('error', error)); :)

如果 Valeri 所说的上述 12 个字节的内容尚不清楚,请不要使用ObjectId.isValid() 。 刚刚被这个烧得很好:
ObjectId.isValid('The Flagship') === true

@atcwells如果您可以更新您高度赞成的评论以包括那一点,我认为其他人可能会欣赏它,因为我最初是根据您所说的: ObjectId.isValid('The Flagship') === true

有趣的部分是:
以下语句返回 true
mongoose.Types.ObjectId.isValid("南非")

我(目前)正在使用的是检查 promise catch 中的错误类型。 如果是“ObjectId”,我会返回 404。我会返回 404,因为对于 API/Web 服务的使用者而言,资源未找到或不存在。

参见示例:

  Widget.findByIdAndRemove(resourceId)
    .then((result) => {
      if (!result) {
        let error = new Error('Resource not found');
        error.status = 404;
        next(error);
      } else {
        res.redirect(303, '/');
      }
    })
    .catch((error) => {
      if (error.kind === 'ObjectId') {
        let error = new Error('Resource not found');
        error.status = 404;

        next(error);
      } else {
        next(error);
      }
    });

更新:
而不是将此添加到控制器中的每个路由处理程序。 我正在添加到全局捕获所有处理程序。

参见示例:

app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  if (err.kind === 'ObjectId') {
    err.status = 404;
  }

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

是否可以在模式中验证? 如果重复,不是最佳实践,但我想记录转换错误事件。

为什么 mongoose 在只处理 MongoDB 时不能实现 isValid 的正则表达式 /^[a-fA-F0-9]{24}$/ 。 这太令人困惑了。 我们只花了一个小时调试这个问题,结果发现这是一个你永远不会注意到的愚蠢的事情

我建议使用这个 npm 包https://www.npmjs.com/package/valid-objectid
它完美地工作

我为此创建了一个解决方法:

function isObjectId(value) {
  try {
      const { ObjectId } = mongoose.Types;
      const asString = value.toString(); // value is either ObjectId or string or anything
      const asObjectId = new ObjectId(asString);
      const asStringifiedObjectId = asObjectId.toString();
      return asString === asStringifiedObjectId;
    } catch (error) {
      return false;
    }
}

@mstmustisnt我认为需要尝试/捕获,如果值为“123”,则会引发错误。

如果它对任何人有帮助,我一直在对上面的ObjectId.isValid()方法进行改编。 因为在我的用例中,我希望能够通过其 ID 或其 url-slug 获取资源,例如:

GET /users/59f5e7257a92d900168ce49a ... 或 ...
GET /users/andrew-shankie

...我发现这在我的控制器中运行良好:

  const { id } = req.params;

  const query = {
    $or: [{ slug: id }],
  };

  if (mongoose.Types.ObjectId.isValid(id)) query.$or.push({ _id: id });

  User.findOne(query)
    .exec((err, user) => { ... }

在这种情况下,一个 12 字节的字符串仍然是一个有效的对象 ID,搜索它只会返回一个长度为零的数组,而不是抛出错误。 而且因为我使用的是$or查询,所以它会通过 URL slug 进行搜索(我的其他可能选项)。

可能不是最优雅的解决方案,但它对我有用。

@victorbadila是的,没错。 我只是给了个提示。 编辑了我的评论,实际上只是放置了我实际使用的代码。

validator.js有一个内置的isMongoId方法

每当我使用猫鼬时,我总是使用一些静态辅助方法来扩展它:

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;

//Helper to check if an ID is an object ID
mongoose.isObjectId = function(id) {
  return (id instanceof ObjectId);
};

//Helper to validate a string as object ID
mongoose.isValidObjectId = function(str) {
  if (typeof str !== 'string') {
    return false;
  }
  return str.match(/^[a-f\d]{24}$/i);
};

您可以将其作为数据库初始化脚本的一部分运行,因此这些方法在您的应用程序中始终可用。

如果 ObjectId.isValid(id) 为真,我们可以判断 (new ObjectId(id).toString()) 的值和id。

const mongoose = require('mongoose');
const {Types: {ObjectId}} = mongoose;
const validateObjectId = (id) => ObjectId.isValid(id) && (new ObjectId(id)).toString() === id;
此页面是否有帮助?
0 / 5 - 0 等级