如何通过slug化地区值在MongoDB中匹配地址文档?
问题描述
我定义了如下Mongoose地址Schema:
const adresseSchema = mongoose.Schema({ adresseComplete: { type: String, required: true, trim: true, }, ville: { type: String, required: false, }, departement: { type: String, required: false, }, region: { type: String, required: true, }, pays: { type: String, required: true, }, });
示例文档:
"adresse": { "_id": "642156ad876e0c4328e193e7", "ville": "Paris", "departement": "Département de Paris", "region": "Île-de-France", "pays": "France", },
前端会传入slug化的地区值(比如ile-de-france),对应的slug化函数如下:
const slugify = (str) => { const map = { '-': ' |_', a: 'á|à|ã|â|ä|À|Á|Ã|Â|Ä', e: 'é|è|ê|ë|É|È|Ê|Ë', i: 'í|ì|î|ï|Í|Ì|Î|Ï', o: 'ó|ò|ô|õ|ö|Ó|Ò|Ô|Õ|Ö', u: 'ú|ù|û|ü|Ú|Ù|Û|Ü', c: 'ç|Ç', n: 'ñ|Ñ', }; for (const pattern in map) str = str.replace(new RegExp(map[pattern], 'g'), pattern); return str.replace(/[^a-z\-0-9\s]/gi, '').toLowerCase(); };
需要在MongoDB中查询所有匹配该slug化地区值的地址,但无法使用$where(MongoDB SaaS集群限制)。
解决方案
方法一:预存slug字段(最优方案)
直接在Schema中添加regionSlug字段,在创建或更新文档时自动生成并存储对应的slug值,查询时直接匹配该字段,性能最高。
步骤1:修改Schema
const adresseSchema = mongoose.Schema({ adresseComplete: { type: String, required: true, trim: true, }, ville: { type: String, required: false, }, departement: { type: String, required: false, }, region: { type: String, required: true, }, regionSlug: { // 新增slug字段 type: String, required: true, index: true, // 添加索引提升查询性能 }, pays: { type: String, required: true, }, });
步骤2:添加预保存钩子自动生成slug
adresseSchema.pre('save', function(next) { // 如果region字段有修改,重新生成slug if (this.isModified('region')) { this.regionSlug = slugify(this.region); } next(); }); // 支持更新操作的钩子 adresseSchema.pre('findOneAndUpdate', function(next) { const update = this.getUpdate(); if (update.region) { update.regionSlug = slugify(update.region); } next(); });
步骤3:查询匹配
const targetSlug = 'ile-de-france'; const addresses = await AdresseModel.find({ regionSlug: targetSlug });
方法二:使用聚合管道实时处理(无Schema修改)
如果无法修改现有Schema,可以用MongoDB聚合管道复制前端的slug逻辑,实时处理region字段后匹配。
查询代码
const targetSlug = 'ile-de-france'; const addresses = await AdresseModel.aggregate([ { $addFields: { // 复制前端slugify的处理逻辑 processedRegion: { $toLowerCase: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: { $replaceAll: { input: "$region", find: /[áàãâäÀÁÃÂÄ]/g, replacement: "a" } }, find: /[éèêëÉÈÊË]/g, replacement: "e" } }, find: /[íìîïÍÌÎÏ]/g, replacement: "i" } }, find: /[óòôõöÓÒÔÕÖ]/g, replacement: "o" } }, find: /[úùûüÚÙÛÜ]/g, replacement: "u" } }, find: /[çÇ]/g, replacement: "c" } }, find: /[ñÑ]/g, replacement: "n" } }, find: /[ |_]/g, replacement: "-" } }, find: /[^a-z\-0-9\s]/gi, replacement: "" } } } } }, { $match: { processedRegion: targetSlug } }, // 可选:移除临时字段,返回原文档结构 { $project: { processedRegion: 0 } } ]);
注意:这种方法需要对每个文档做实时处理,数据量大时性能会受影响,建议仅在无法修改Schema时使用,且尽量给
region字段添加索引。
方法三:构建正则表达式匹配(简易场景)
如果地区的slug转换规则相对简单,可以构建反向正则,匹配原始region字段。
示例代码
const targetSlug = 'ile-de-france'; // 反向处理slug,生成匹配原始region的正则 const regexStr = targetSlug .replace(/-/g, '[ |_]?') .replace(/a/g, '[aáàãâäÀÁÃÂÄ]') .replace(/e/g, '[eéèêëÉÈÊË]') .replace(/i/g, '[iíìîïÍÌÎÏ]') .replace(/o/g, '[oóòôõöÓÒÔÕÖ]') .replace(/u/g, '[uúùûüÚÙÛÜ]') .replace(/c/g, '[cçÇ]') .replace(/n/g, '[nñÑ]'); const regionRegex = new RegExp(`^${regexStr}$`, 'i'); const addresses = await AdresseModel.find({ region: regionRegex });
注意:这种方法可能存在匹配不准确的情况(比如特殊字符处理不全),且正则查询无法利用普通索引,性能不如预存slug字段。
内容的提问来源于stack exchange,提问作者Ricou
相关产品推荐
相关产品推荐

