#javascript #node.js #express #mongoose
#javascript #node.js #выразить #mongoose
Вопрос:
Я использую mongoose в проекте nodejs. В нем я хочу сделать следующее: когда я сохраняю «slug» в db, я хочу проверить, существует ли slug, а затем добавить к нему счетчик перед сохранением. Итак, что-то вроде этого my-title my-title-2 my-title-3 …
Я создал приведенный ниже код промежуточного программного обеспечения express, который проверяет данный slug, выполняет проверку и добавляет к нему счетчик. Я нашел это из сообщения stackoverflow, но я думаю, что это работает только до моего-title-2. Если вы добавите еще один «my-title» slug, он перейдет в бесконечные рекурсионные вызовы. Ниже приведен код:
module.exports.generateSlug = (req, res, next) => {
// remove special chars, trim spaces, replace all spaces to dashes, lowercase everything
var slug = req.body.slug
.replace(/[^ws]/gi, "")
.trim()
.replace(/s /g, "-")
.toLowerCase();
var counter = 2;
// check if there is existing release with same slug
Release.findOne({ slug: slug }, checkSlug);
// recursive function for checking repetitively
function checkSlug(err, existingRelease) {
if (existingRelease) {
// if there is release with the same slug
if (counter == 2)
// if first round, append '-2'
slug = slug.concat("-" counter );
// increment counter on slug (eg: '-2' becomes '-3')
else slug = slug.replace(new RegExp(counter "$", "g"), counter);
Release.findOne({ slug: slug }, checkSlug); // check again with the new slug
} else {
// else the slug is set
req.body.slug = slug;
next();
}
}
};
Я думаю, что ошибка в этой строке
slug = slug.replace(new RegExp(counter "$", "g"), counter);
Комментарии:
1. Независимо от того, как вы это делаете, если только поле slug каким-то образом не определено как уникальное в базе данных (например, с УНИКАЛЬНЫМ индексом в SQL; не уверен, что такое аналог MongoDB), это может привести к условиям гонки и, как таковым, столкновениям.
Ответ №1:
Просто понял это сам. Ниже приведен код 🙂
module.exports.generateSlug = (req, res, next) => {
var slug = req.body.slug
.replace(/[^ws]/gi, "")
.trim()
.replace(/s /g, "-")
.toLowerCase();
// check if there is existing release with same slug
Release.find({ slug: new RegExp("^" slug, "i") }, (err, results) => {
if (results == null) {
next();
} else {
var count = results.length 1;
req.body.slug = `${slug}-${count}`;
next();
}
});
};
Комментарии:
1. Это приведет к ненужной выборке всех результатов из базы данных, и это не будет работать корректно, если у вас есть, например
foo
,foobar
,fooquux
в базе данных и вашreq.body.slug
isfoo
.2. Если в вашей БД у вас есть
slug
, иslug-3
тогда ваш код создаст дубликатslug-3
3. @CuongLeNgoc да, я только что это заметил. Это действительно сложно реализовать
Ответ №2:
Вот нерекурсивная переформулировка с использованием асинхронных функций.
Я повторю то, что я сказал в комментариях, хотя:
Независимо от того, как вы это делаете, если только поле slug каким-то образом не определено как уникальное в базе данных (например, с УНИКАЛЬНЫМ индексом в SQL; не уверен, что такое аналог MongoDB), это может привести к условиям гонки и, как таковым, столкновениям.
/**
* Find a free slug (or other value) by appending a counter to the given prefix and passing it
* to the given `func` function, which should return a promise with a falsy value if the slug is free.
* @param prefix Prefix for attempts.
* @param func Predicate function.
* @param maxAttempts Maximum number of attempts.
* @returns {Promise<string|*>}
*/
async function findFreeSlug(prefix, func, maxAttempts = 10) {
for (let i = 1; i < maxAttempts; i ) {
const slug = i > 1 ? `${prefix}-${i}` : prefix;
const result = await func(slug);
if (!result) {
return slug;
}
}
throw new Error(
`Could not find a free slug for ${prefix} after ${maxAttempts} attempts`,
);
}
/**
* Return a promise that returns true if a Release with the given slug exists.
* @param slug
* @returns {Promise<boolean>}
*/
async function doesReleaseWithSlugExist(slug) {
return new Promise((resolve, reject) => {
Release.findOne({ slug }, (err, release) => {
if (err) return reject(err);
resolve(!!release);
});
});
}
module.exports.generateSlug = async (req, res, next) => {
// remove special chars, trim spaces, replace all spaces to dashes, lowercase everything
const initialSlug = req.body.slug
.replace(/[^ws]/gi, "")
.trim()
.replace(/s /g, "-")
.toLowerCase();
req.body.slug = await findFreeSlug(initialSlug, doesReleaseWithSlugExist);
next();
};