循环以生成独特的 url 友好路线

loop to generate a unique url-frinedly route

我在mariadb中有一个table这个结构,

CREATE TABLE `items` (
    `id` char(36), `route` varchar(255), `value` text,  
    PRIMARY KEY (`id`),
    UNIQUE KEY `route` (`route`)
)

我使用 route 列来获取用户友好的 url,例如 http://www.examle.com/this-is-the-route-of-an-item

当用户创建新项目时,除了省略空格和非法字符外,我想 "catch" 为新项目选择的路由正在使用的情况,并生成有效路由。

例如,如果 route-of-an-item 已经在使用中,我会回退到 route-of-an-item-a,或 route-of-an-item-b,等等

天真的解决方案可能是在循环中查询数据库,例如(某种伪代码):

var additionalChars = "";
while (db.query("select count * from `items` where `route`='" + route + "-" + additionalChars + "'"))
    additionalChars = nextAdditionalChars(additionalChars);

finalRoute = route + '-' + additionalChars;

由于这涉及多次查询数据库,我想到了另一个解决方案。

var additionalChars = "";
var usedRoutes = db.query("select `route` from `items` where `route` like '" + route + "%'");
while(usedRoutes.contains(route + '-' + additionalChars))
     additionalChars = nextAdditionalChars(additionalChars);

finalRoute = route + '-' + additionalChars;

有没有更好的方法来解决这类问题?

我认为第二种解决方案的效果更好吗?

如果我使用第二种解决方案,我是否应该在路由字段中添加全文索引?

您可以按路线降序对查询进行排序,并且只检索和检查一项。在您的伪代码中,它看起来像:

var additionalChars = "";
var usedRoutes = db.query("select `route` from `items` where `route` like '" + route + "%' order by `route` desc limit 1");
if(usedRoutes.route is already in use)
     additionalChars = nextAdditionalChars(additionalChars);

finalRoute = route + '-' + additionalChars;

好的,所以在咨询了同事之后我最终使用了解决方案 2,这是代码(node.js),以防有人遇到这个问题:

数据库访问

var route = req.body.route || 'noname';    
route = route.replace(/[\s_]/g, '-').toLowerCase().replace(/[^0-9a-z\u0591-\u05F4\u0621-\u064A\-_\s]/g, "").replace(/_+/g, ' ').trim().replace(/[\s_]+/g, '-');

var isArabic = (/[\u0621-\u064A]/g).test(route),
    isHebrew = (/[\u0591-\u05F4]/g).test(route),
    lang = isArabic ? 'ar' : (isHebrew ? 'he' : 'en');

Items.findAll({ where: { route: { $like: route + '%' } }, attributes: ['route'] })
    .then((items) => {
        var routes = _.keyBy(items, 'route'),
            prefix = '';

        while (routes[route + (prefix ? '-' + prefix : '')])
            prefix = charactersCount(prefix, lang);

        Items.create({ route: route + (prefix ? '-' + prefix : ''), value: req.body.value })
        .then(function(item){ 
            res.send({ item: _.pick(item, ['id', 'route', 'author_id', 'created_at']) })
        })
        .catch(function(){ res.sendStatus(500)});
    })
    .catch(function(){ res.sendStatus(500) });

生成额外的字符

var chars = {
    ar: { val: "اﻻبتثجحخدذرزسشصضطظعغفقكلمنهةوىي", len: 0 },
    he: { val: "אבגדהוזחטיכלמנסעפצקרשת", len: 0 },
    en: { val: "abcdefghijklmnopqrstuvwxyz", len: 0 }
};
_.forEach(chars, (c) => { c.len = c.val.length });

function charactersCount (current, lang) => {
    if (!current) return chars[lang].val[0];
    lang = lang || 'en';
    var curr = current.split(''),
        len = curr.length,
        pointer = len,
        lastIndex;

    while ((lastIndex = chars[lang].val.indexOf(curr[--pointer]) + 1) >= chars[lang].len) curr[pointer] = chars[lang].val[0];
    if (pointer < 0) { curr.unshift(''); pointer++; }
    curr[pointer] = chars[lang].val[lastIndex];

    return curr.join('');
}

所以我最终得到一个 select 查询和一个插入查询,并防止节点端发生冲突。

并且不需要全文索引,因为 % 仅出现在 like 运算符的末尾