猫鼬独特的自动递增段塞

时间:2016-07-26 15:36:53

标签: node.js mongodb concurrency mongoose slug

我正在为项目中的某些模型实施slug,并且我希望根据文档的数据保持其唯一性。

var user = new User({ name: { first: 'John', last: 'Doe' });
user.save().then(function(user) {}); // user.slug === 'john-doe'

var user2 = new User({ name: { first: 'John', last: 'Doe' });
user2.save().then(function(user) {}); // user.slug === 'john-doe-2'

我可以成功实现它,我甚至可以在.pre('save')钩子上查询数据库,如下所示:

UserSchema.pre('save', function(next) {
    var user = this;
    return User.distinct('slug')
        .exec()
        .then(function(slugs) {
            // generate a unique slug like "john-doe-2",
            // checking it against slugs that are already
            // in the database, and set it to user
            next();
        });
});

唯一的问题是并发请求(不太可能,但仍然可能):

var user = new User({ name: { first: 'John', last: 'Doe' }),
    user2 = new User({ name: { first: 'John', last: 'Doe' });

Promise.all([ user.save(), user2.save() ]);
// throws validation error if slug should be unique

为此,我正在寻找能够处理验证错误(唯一索引重复)的解决方案,并尝试使用更新的参数重新保存文档(例如" john-doe-2&#34 ;)

2 个答案:

答案 0 :(得分:1)

我知道这个问题现在已经有一年了,但是我遇到了同样的问题并找到了一个解决方案,在找到第一个免费slug之前不需要客户端重试。如果您有第二个集合userslugs,请执行

var generatedSlug = 'john.doe';

var count = db.accountslugs.findOneAndUpdate({
    _id: generatedSlug
}, {
    $inc: { count: 1 }
}, {
    upsert: true,
    returnNewDocument: true
}).count;

var fullSlug = generatedSlug + ((count === 1) ? "" : "." + count);

你最终得到一个完整的slug,首先是john.doe,然后是john.doe.2,依此类推。

这种方法要快得多,因为它找到一个带有一个查询的slug。我没有使用猫鼬,因为这是这个集合必须做的唯一的事情,它似乎有点过分。

修改:请记住,您的客户应该验证generatedSlug是否已经以点后跟数字结尾,那么您将遇到问题。

答案 1 :(得分:0)

好的,所以我最终得到了一个单独的slugs模型,有点像@AmiramKorach建议的那样。然后我实现了一个模块来获取一个slug,它依赖于Mongo的11000错误(重复条目)。

function getSlug(base, counter) {
    if (typeof counter === 'undefined') counter = 0;
    var url = base;
    if (counter > 0) url += '-' + counter;
    var slug = new Slug({ url: url });
    return slug.save()
        .then(function(slug) {
            return slug.url;
        })
        .catch(function(reason) {
            if (reason.code === 11000) return getSlug(base, counter + 1);
            throw reason;
        });
}

Promise.all([
    getSlug('john-doe'),
    getSlug('john-doe'),
    getSlug('john-doe'),
    getSlug('john-doe'),
    getSlug('john-doe'),
    getSlug('john-doe'),
    getSlug('john-doe')
])
.then(function(slugs) {
    // slugs are [ 'john-doe', 'john-doe-1', 'john-doe-2',
    //'john-doe-4', 'john-doe-5', 'john-doe-6', 'john-doe-3' ]
});