敏感词过滤——字符串includes/indexOf+DFA算法-HowieCong
一、背景
- 快速检测并处理用户输入中的非法内容,同时平衡性能与准确性
二、前端过滤
(1)流程
-
创建文件存储敏感词:创建一个敏感词.js 文件,在这个文件中,敏感词存储为一个数组
-
import引入敏感词库
-
添加输入监听事件并过滤
-
给用户要输入的元素(例如input框)添加input事件监听器
-
在事件处理函数中进行敏感词过滤
-
(2)方法
-
通过includes(或indexOf)来逐个检查是否包含敏感词
-
优点: 简单易懂,实现方便,能快速在前端给用户反馈
-
局限性: 敏感词词库很大,性能肯定会有影响,每次都要遍历整个数组,对于复杂的敏感词可能无法有效的过滤
-
// 定义函数checkWithIncludes,用于检查输入的文本(inputText)中是否包含给定敏感词数组(sensitiveWords)中的敏感词
function checkWithIncludes(sensitiveWords, inputText) {
// 开始遍历敏感词数组,目的是逐个检查每个敏感词是否存在于输入文本中
for (let i = 0; i < sensitiveWords.length; i++) {
// 获取当前正在遍历的敏感词
const sensitiveWord = sensitiveWords[i];
// 使用JavaScript字符串的includes方法来检查输入文本(inputText)中是否包含当前的敏感词(sensitiveWord)
// includes方法会返回一个布尔值,如果找到了对应的敏感词则返回true,否则返回false
if (inputText.includes(sensitiveWord)) {
// 如果发现输入文本中包含当前敏感词,说明包含敏感词的情况出现了,直接返回true,表示输入文本包含敏感词
return true; // 表示包含敏感词
}
}
// 如果遍历完整个敏感词数组后,都没有发现输入文本中包含其中任何一个敏感词,
// 则返回false,表示输入文本未包含敏感词
return false; // 表示未包含敏感词
}
-
通过编写合适的正则表达式去匹配敏感词
-
优点: 能匹配多个敏感词
-
局限性: 编写复杂的正则表达式比较困难,特别对于敏感词数量多和文本长的情况,构建和维护成本也会增加
-
-
引入第三方库**
js - sensitive - words**- 优点:采用了高效的算法,性能更好;可以对于一些敏感词变形的情况处理
四、DFA算法原理
- DFA核心原理:
-
每个状态(树节点)表示一个字符,通过状态转移匹配输入字符
-
敏感词路径的终点标记为终止状态(如
isEnd: true)
-
五、构建DFA算法流程
- 构建一个DFA算法的自动机函数,将敏感词集合转换为树形结构,实现单次输入遍历即可完成所有敏感词检测
-
实现步骤:
-
预处理敏感词库:构建树形结构,合并相同前缀(例如 "测试" 和 "测abc" 共享前缀 "测")
-
状态转移匹配:遍历输入字符串,维护当前状态指针,若转移失败则重置指针
-
终止状态检测:若到达终止状态,立即标记敏感词位置,实现高效拦截
-
-
版本1
// 1. 构建DFA树
const buildDFA = (words) => {
const root = {};
for (const word of words) {
let node = root;
for (const char of word) {
if (!node[char]) node[char] = {};
node = node[char];
}
node.isEnd = true; // 标记终止状态
}
return root;
};
// 2. 过滤算法
const filterText = (text, dfaRoot) => {
let currentNode = dfaRoot;
let matchStartIndex = 0;
const sensitiveWords = [];
for (let i = 0; i < text.length; i++) {
const char = text[i];
if (currentNode[char]) {
currentNode = currentNode[char];
if (currentNode.isEnd) {
// 记录敏感词位置或替换为*
sensitiveWords.push(text.slice(matchStartIndex, i + 1));
currentNode = dfaRoot; // 重置指针
}
} else {
i = matchStartIndex; // 回退到起点+1继续检测
matchStartIndex++;
currentNode = dfaRoot;
}
}
return sensitiveWords;
};
- 版本2
// 定义函数buildDFA,它接受一个敏感词数组作为参数,用于构建确定有限状态自动机(DFA)的结构
export function buildDFA(sensitiveWords) {
// 创建一个空对象作为DFA的根节点,后续所有敏感词的状态转移都会基于这个根节点展开
let root = {};
// 遍历敏感词数组中的每一个敏感词
for (let i = 0; i < sensitiveWords.length; i++) {
// 获取当前遍历到的敏感词
let sensitiveWord = sensitiveWords[i];
// 将当前节点初始化为根节点,从根节点开始构建当前敏感词对应的状态转移路径
let node = root;
// 遍历当前敏感词的每一个字符,构建状态转移路径
for (let j = 0; j < sensitiveWord.length; j++) {
// 获取当前敏感词中的当前字符
let char = sensitiveWord[j];
// 如果当前节点(在当前状态下)不存在对应字符的转移路径(即下一个状态节点不存在),则创建一个新的空对象作为下一个状态节点
if (!node[char]) {
node[char] = {};
}
// 将当前节点更新为下一个状态节点,以便继续构建后续字符的状态转移路径
node = node[char];
}
// 在当前敏感词对应的最后一个状态节点上标记为结束节点,表示当匹配到这个状态时,意味着完整匹配到了一个敏感词
node['isEnd'] = true;
}
// 返回构建好的DFA的根节点,通过这个根节点可以开始对输入的文本进行敏感词匹配操作
return root;
}
-
在组件引入并使用DFA自动机过滤
-
优势:
-
对于大量敏感词和长文本的情况,性能更优,时间复杂度相对较低
-
能够快速地判断,利用状态转移减少了不必要地遍历操作
-
-
时间复杂度:从
O(n*m)优化至O(n),仅需一次输入遍历 -
实测效果:过滤耗时从 56ms 降至 8ms,性能提升 7 倍,适用于海量敏感词场景
-
扩展性优势:
-
支持动态添加/删除敏感词(无需重建整个树)
-
可处理重叠词(如同时过滤 "abc" 和 "bcde")
-
六、后端过滤(基于node.js+express)
-
流程:
-
创建文件存储敏感词:创建一个 敏感词.js 文件,在这个文件中,敏感词存储为一个数组
-
添加路由来处理敏感词过滤,通过简单遍历检查
-
发送请求到后端进行过滤,通过uni-request或vue axios发送请求到后端进行过滤
-
-
优势:
-
更加安全可靠,用户难以绕过
-
可以结合更复杂的算法和数据库存储敏感词来提高过滤效率
-
-
注意事项: 注意跨域的问题,在生产环境需要正确配置服务器的CORS策略
七、进阶优化方向
-
结构优化:用
Map替代对象存储子节点,提升字符查找效率 -
多级缓存:对高频敏感词路径缓存,减少树深度遍历
-
异步处理:对长文本分段过滤,避免阻塞主线程
-
语义处理:结合 NLP 处理变体词(如拼音、形近字)
❓其他
1. 疑问与作者HowieCong声明
-
如有疑问、出错的知识,请及时点击下方链接添加作者HowieCong的其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
-
若想让作者更新哪些方面的技术文章或补充更多知识在这篇文章,请及时点击下方链接添加里面其中一种联系方式或发送邮件到下方邮箱告知作者HowieCong
-
声明:作者HowieCong目前只是一个前端开发小菜鸟,写文章的初衷只是全面提高自身能力和见识;如果对此篇文章喜欢或能帮助到你,麻烦给作者HowieCong点个关注/给这篇文章点个赞/收藏这篇文章/在评论区留下你的想法吧,欢迎大家来交流!