塞尔维亚拉丁字母模糊搜索实现方案咨询(NestJS+React)
实现塞尔维亚拉丁字母同源字符搜索的基础思路
方案1:动态生成同源字符正则(无需修改存储结构)
- 步骤1:建立同源字符映射表
定义基础字符到所有变体的映射,覆盖塞尔维亚拉丁字母的特殊字符:const serbianCharMap: Record<string, string[]> = { 'c': ['c', 'č', 'ć'], 'd': ['d', 'đ'], 's': ['s', 'š'], 'z': ['z', 'ž'], }; - 步骤2:实现搜索词归一化工具函数
先转义正则特殊字符,再把每个基础字符替换为对应变体的正则字符组:// 转义正则特殊字符(避免用户输入破坏正则结构) function escapeRegExp(str: string): string { return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } // 归一化搜索词,生成匹配所有同源字符的正则表达式字符串 function normalizeSerbianSearchTerm(term: string): string { const escapedLowerTerm = escapeRegExp(term.toLowerCase()); let normalizedTerm = escapedLowerTerm; Object.entries(serbianCharMap).forEach(([baseChar, variants]) => { normalizedTerm = normalizedTerm.replace( new RegExp(baseChar, 'g'), `[${variants.join('')}]` ); }); return normalizedTerm; } - 步骤3:在NestJS服务层构建查询
结合ORM(如TypeORM)生成包含正则匹配的查询条件,以匹配多列为例:
注意:不同数据库的正则语法有差异,比如PostgreSQL用async searchEntities(searchTerm: string) { const normalizedTerm = normalizeSerbianSearchTerm(searchTerm); return this.entityRepository.find({ where: Raw(alias => `LOWER(${alias}.column1) REGEXP '${normalizedTerm}' OR LOWER(${alias}.column2) REGEXP '${normalizedTerm}'` ), }); }~*替代REGEXP,MongoDB直接用$regex操作符。
方案2:预归一化存储字段(性能更优,适合大数据量)
- 步骤1:在实体中添加归一化搜索字段
新增一个专门用于搜索的字段,存储去掉变音符号的基础字符版本:@Entity() export class YourEntity { @Column() name: string; @Column() description: string; // 归一化搜索字段,存储无变音的字符串 @Column({ name: 'search_normalized' }) searchNormalized: string; } - 步骤2:写入数据时自动生成归一化字段
在实体的BeforeInsert和BeforeUpdate钩子中处理:@BeforeInsert() @BeforeUpdate() generateSearchNormalized() { // 把所有变音字符替换为基础字符 this.searchNormalized = this.name.toLowerCase() .replace(/[čć]/g, 'c') .replace(/đ/g, 'd') .replace(/š/g, 's') .replace(/ž/g, 'z') + ' ' + // 拼接多列内容,实现多列搜索 this.description.toLowerCase() .replace(/[čć]/g, 'c') .replace(/đ/g, 'd') .replace(/š/g, 's') .replace(/ž/g, 'z'); } - 步骤3:搜索时直接匹配归一化字段
这种方式可以利用数据库索引,性能远高于正则匹配:async searchEntities(searchTerm: string) { const normalizedSearchTerm = searchTerm.toLowerCase() .replace(/[čć]/g, 'c') .replace(/đ/g, 'd') .replace(/š/g, 's') .replace(/ž/g, 'z'); return this.entityRepository.find({ where: { searchNormalized: Like(`%${normalizedSearchTerm}%`), }, }); }
方案对比
- 方案1:无需修改存储结构,实现简单,但正则匹配无法利用索引,数据量大时性能下降明显。
- 方案2:需要额外维护字段,但搜索性能优异,适合数据量较大或搜索频繁的场景。
内容的提问来源于stack exchange,提问作者StrahinjaL
相关产品推荐
相关产品推荐

