使用JS与MongoDB实现支持易混淆相似字符匹配的正则搜索功能
相似字符匹配搜索的接口改造方案
现有逻辑的问题是直接拿用户输入做正则匹配,遇到易混淆字符输错时就命中不了,要实现等价匹配,核心思路是把每组外形相似的字符归为同一匹配集合,两种可直接落地的方案如下:
方案一:轻量正则改写(无需调整数据库表结构)
改动最小,直接在现有接口的正则生成环节做改造,不需要动原有表结构:
- 先梳理需要兼容的易混淆字符对,建立映射规则
- 把用户输入的搜索词逐字符拆解,每个字符替换成能覆盖所有相似字符的正则片段,再用生成的新正则去查询
可直接落地的改造代码如下:
// 定义相似字符匹配集合:原字符对应所有可等价匹配的相似字符 const similarCharMap = { '0': '[0Oo]', 'O': '[0Oo]', 'o': '[0Oo]', '1': '[1IilL]', 'I': '[1IilL]', 'i': '[1IilL]', 'l': '[1IilL]', 'L': '[1IilL]' } exports.findByName = (req, res) => { const itemName = req.params.name if (!itemName) { return Item.find({ _active: true }) .then(data => res.send(data)) .catch(err => res.status(500).send({ message: err.message || "Some error occurred while retrieving items." })) } // 逐字符处理用户输入,生成可匹配相似字符的正则 const matchedRegex = itemName.split('') .map(char => { // 先转义正则特殊字符,避免语法错误/注入问题 if (/[.*+?^${}()|[\]\\]/.test(char)) return `\\${char}` // 命中相似字符规则就返回对应匹配集合,否则返回原字符 return similarCharMap[char] || char }) .join('') const condition = { itemName: { $regex: new RegExp(matchedRegex), $options: "ix" }, _active: true } Item.find(condition) .then((data) => { res.send(data) }) .catch((err) => { res.status(500).send({ message: err.message || "Some error occurred while retrieving items." }) }) }
举个实际匹配的例子:
用户输入搜索词89O1450,生成的正则片段为89[0Oo][1IilL]45[0Oo],可以直接命中数据库里的原条目890I45O——因为条目里的0属于0/O/o的匹配集合,I属于1/I/i/l/L的匹配集合,完全匹配需求。
方案二:性能优化版(数据量大时推荐)
如果数据库内的物品条目量过万,全表正则扫描的性能会明显下降,这时候推荐用冗余字段做归一化处理:
- 给Item表增加
normalizedItemName字段,在新增/修改物品信息时,按照统一规则把原物品名转成归一化格式同步存入:- 统一规则:所有
O/o替换为0,所有I/i/l/L替换为1,其余字符保持不变 - 比如原条目
890I45O,归一化后的值为8901450
- 统一规则:所有
- 搜索时先把用户输入的搜索词做同样的归一化转换,之后直接匹配归一化字段即可,比纯正则查询快数倍。
这个方案下的接口改造代码:
// 统一的归一化转换函数 const normalizeStr = (str) => str.replace(/[Oo]/g, '0').replace(/[IiLl]/g, '1') exports.findByName = (req, res) => { const itemName = req.params.name const condition = itemName ? { // 直接匹配归一化后的字段,天然兼容所有相似字符输入 normalizedItemName: { $regex: new RegExp(normalizeStr(itemName)), $options: "ix" }, _active: true } : { _active: true } Item.find(condition) .then((data) => res.send(data)) .catch((err) => res.status(500).send({ message: err.message || "Some error occurred while retrieving items." })) }
注意事项
- 相似字符的映射规则可以按需扩展,如果后续发现
S/5、B/8这类常见易混字符,直接在映射表中补充对应规则即可 - 方案一必须保留正则特殊字符的转义逻辑,否则用户输入带正则语法的内容时,会直接报错甚至引发注入风险
- 数据量小的场景直接用方案一即可,改动量最小;数据量上来之后再迁移到方案二的冗余字段模式,不需要推翻原有逻辑
内容的提问来源于stack exchange,提问作者SeektheFreak
相关产品推荐
相关产品推荐

