面临敏感词过滤的问题,最简单的方案就是对要检测的文本,遍历全部敏感词,逐个检测输入的文本是否包含指定的敏感词。前端
很明显上面这种实现方法的检测时间会随着敏感词库数量的增长而线性增长。系统会所以面临性能和CPU消耗的问题。vue
在计算理论中,肯定有限状态自动机或肯定有限自动机(deterministic finite automaton, DFA)是一个能实现状态转移的自动机,是表示有限个状态及在这些状态间转移和动做等行为的数学模型。python
对于一个给定的属于该自动机的状态和一个属于该自动机字母表的字符,它都能根据事先给定的转移函数转移到下一个状态(这个状态能够是先前那个状态)。git
肯定有限状态自动机是由github
所组成的5-元组。所以一个DFA能够写成这样的形式:。正则表达式
DFA算法特征是:有一个有限状态集合和一些从一个状态通向另外一个状态的边,每条边上标记有一个符号,其中一个状态是初态,某些状态是终态。但不一样于不肯定的有限自动机,DFA中不会有从同一状态出发的两条边标志有相同的符号。 算法
简单点说就是,它是是经过event和当前的state获得下一个state,即event+state=nextstate。理解为系统中有多个节点,经过传递进入的event,来肯定走哪一个路由至另外一个节点,而节点是有限的。数组
以王八蛋和王八羔子两个敏感词来进行描述,首先构建敏感词库,该词库名称为SensitiveMap,这两个词的二叉树构造为:浏览器
把每一个敏感词字符串拆散成字符,再存储到HashMap(其余语言可用字典实现hashmap)中,能够这样保存:数据结构
{ "王": { "isend": False, "八": { "isend": False, "蛋": { "isend": True, }, "羔": { "isend": False, "子": { "isend": True, } }, } } }
将每一个词的第一个字符做为key,vlue则是另外一个HashMap,value对应的HashMap的key为第二个字符,若是还有第三个字符,则存储到以第二个字符为key的value中,固然这个value仍是一个HashMap,以此类推下去,直到最后一个字符,固然最后一个字符对应的value也是HashMap,只不过这个HashMap只须要存储一个结束标志就好了。
以上面例子构造出来的SensitiveMap为敏感词库进行示意,假设这里输入的关键字为:王八很差,流程图以下:
使用HashMap存储的好处:HashMap在理想状况下能够以O(1)的时间复杂度进行查询,因此在遍历待检测字符串的过程当中,能够以O(1)的时间复杂度检索出当前字符是否在敏感词库中,大大提高效率。
JavaScript有两种方式建立一个正则表达式:第一种方式是直接经过字面量 /{正则表达式}/{flags}
写出来,第二种方式是经过new RegExp('正则表达式')
建立一个RegExp对象。
注意:这两种方法的正则表达式写法是一致的。区别仅仅是字面量的参数不使用引号,而构造函数的参数使用引号。
>var re1 = /ABC\-001/; >re1 /ABC\-001/ >var re2 = new RegExp('ABC\\-001'); >re2 /ABC\-001/ >var re = /^\d{3}\-\d{3,8}$/; >re.test('010-3945') true >re.test('010-3945x') false
new RegExp(pattern [, flags]) RegExp(pattern [, flags])
参数 pattern 是一个字符串,指定了正则表达式的模式或其余正则表达式。
参数 flags 是一个可选的字符串,包含属性 "g"、"i" 和 "m"等,分别用于指定全局匹配、区分大小写的匹配和多行匹配。ECMAScript 标准化以前,不支持 m 属性。若是 pattern 是正则表达式,而不是字符串,则必须省略该参数。
// flags能够为以下值的任意组合 g:全局匹配;找到全部匹配,而不是在第一个匹配后中止 i:忽略大小写 m:多行; 将开始和结束字符(^和$)视为在多行上工做(也就是,分别匹配每一行的开始和结束(由 \n 或 \r 分割),而不仅是只匹配整个输入字符串的最开始和最末尾处。 u:Unicode; 将模式视为Unicode序列点的序列 y:粘性匹配; 仅匹配目标字符串中此正则表达式的lastIndex属性指示的索引(而且不尝试从任何后续的索引匹配)。
一个新的 RegExp 对象,具备指定的模式和标志。若是参数 pattern 是正则表达式而不是字符串,那么 RegExp() 构造函数将用与指定的 RegExp 相同的模式和标志建立一个新的 RegExp 对象。
若是不用 new 运算符,而将 RegExp() 做为函数调用,那么它的行为与用 new 运算符调用时同样,只是当 pattern 是正则表达式时,它只返回 pattern,而再也不建立一个新的 RegExp 对象。
SyntaxError - 若是 pattern 不是合法的正则表达式,或 attributes 含有 "g"、"i" 和 "m" 以外的字符,抛出该异常。
TypeError - 若是 pattern 是 RegExp 对象,但没有省略 attributes 参数,抛出该异常。
test()方法用于检测一个字符串是否匹配某个模式,测试当前正则是否能匹配目标字符串。
let RegExpObject = new RegExp(partern, flags) RegExpObject.test('要检测的字符串')
若是要检测的字符串 string 中含有与 RegExpObject 匹配的文本,则返回 true ,不然返回 false。测试效果以下:
>>var str = "我是大佬"; >>var patt1 = new RegExp("大佬", 'g'); >>var result = patt1.test(str); true
由上例能够看出,可使用RegExp来检查敏感字段。
exec()方法用于检索字符串中的正则表达式的匹配,在目标字符串中执行一次正则匹配操做。。
RegExpObject.exec(string)
string是要检索的字符串,返回一个数组,其中存放匹配的结果。若是未找到匹配,则返回值为 null。
exec() 方法的功能很是强大,它是一个通用的方法,并且使用起来也比 test() 方法以及支持正则表达式的 String 对象的方法更为复杂。
compile
()
方法被用于在脚本执行过程当中(从新)编译正则表达式。与RegExp
构造函数基本同样。
该特性已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在将来的某个时间中止支持,略。
在github上获取到1w+敏感词库:https://github.com/observerss/textfilter
因为该词库是一行行填写在里面不方便处理,使用python转换为列表格式:
with open('keywords', 'r') as f: word_list = [] obj = f.readlines() for i in obj: i = i.strip("\n") word_list.append(i) print(word_list) f.close() with open('keywords.txt', 'w') as wf: wf.write(str(word_list))
选择在vue项目中建立文件:./src/assets/js/sensitiveWord.js
export function makeSensitiveMap() { var result = {}; var count = sensitiveWordList.length; // 依次取字 for (var i = 0; i < count; ++i) { var map = result; var word = sensitiveWordList[i]; // 依次获取字 for (var j = 0; j < word.length; ++j) { var ch = word.charAt(j); // charAt() 方法可返回指定位置的字符。 // 判断是否存在 if (typeof(map[ch]) != "undefined") { map = map[ch]; if (map["empty"]) { break; } } else { if (map["empty"]) { delete map["empty"]; } map[ch] = {"empty":true}; map = map[ch]; } } } return result; }
export function checkSensitiveWord (sensitiveMap, sentence) { let result = []; let count = sentence.length; let stack = []; let point = sensitiveMap; for (var i=0; i<count; i++) { var ch = sentence.charAt(i); var item = point[ch]; if (typeof(item) == "undefined") { i = i - stack.length; stack = []; point = sensitiveMap; } else if (item["empty"]) { stack.push(ch); result.push(stack.join("")); stack = []; point = sensitiveMap; } else { stack.push(ch); point = item; } } return result; }
export const sensitiveWordList = [各类敏感词内容]
在vue项目 /src/main.js 添加以下内容:
import VeeValidate, {Validator} from 'vee-validate'; import veeMessage from 'vee-validate/dist/locale/zh_CN'; import {sensitiveWordList, makeSensitiveMap, checkSensitiveWord} from './assets/js/sensitiveWord'; // 添加表单验证 Vue.use(VeeValidate, { classes: true, classNames: { valid: 'is-valid', invalid: 'is-invalid' } }); Validator.localize('zh_CN', veeMessage); // 自定义敏感词过滤 const sensitiveWordRule = { getMessage: (field, args) => field + '敏感字段', validate: (value, args) => { let sensitiveMap = makeSensitiveMap(sensitiveWordList); console.log('sensitivemap', sensitiveMap); if (checkSensitiveWord(sensitiveMap, value).length > 0) { console.log(checkSensitiveWord(sensitiveMap, value)) return false; } else { console.log(checkSensitiveWord(sensitiveMap, value)) return true; } } }; Validator.extend('sensitiveWordFilter', sensitiveWordRule);
使用自定义验证规则:
<div class="form-group"> <div class="row align-items-center"> <label class="col-sm-3">*标题</label> <div class="col-sm-9"> <input type="text" class="form-control" id="title" v-model="queryInfo.title" v-validate="'required|sensitiveWordFilter'" name="title" data-vv-as="标题"> </div> </div> </div> <div class="form-group"> <div class="row "> <label class="col-sm-3">*内容</label> <div class="col-sm-9"> <textarea rows="10" class="form-control" id="content" v-model="queryInfo.content.text" v-validate="'required|sensitiveWordFilter'" name="content" data-vv-as="内容"></textarea> </div> </div> </div>
显示效果以下所示:
在发帖、发评论等场景,查询到敏感词时替换为”*“。
<template> <div style="padding-bottom: 30px;"> <textarea class="form-control" placeholder="课堂交流,欢迎你们" v-model="newPosts.content.text"></textarea> <button type="button" class="btn btn-primary btn-sm pull-right" @click="addPosts"> 发送</button> </div> <div style="padding-bottom: 35px;"> <textarea class="form-control" placeholder="输入笔记" v-model="newNote.remark"></textarea> <button type="button" class="btn btn-primary btn-sm pull-right" @click="addNote"> 记笔记</button> </div> </template> <script> import {sensitiveWordList, makeSensitiveMap, checkSensitiveWord} from '../../assets/js/sensitiveWord'; export default { methods: { wordFilter: function (content) { // 过滤出敏感词用*替换 console.log(content); let inputContent = content; let sensitiveMap = makeSensitiveMap(sensitiveWordList); let needUpdateWordList = checkSensitiveWord(sensitiveMap, inputContent); console.log(needUpdateWordList); for (var i=0; i < needUpdateWordList.length; i++) { // 建立正则表达式 let r = new RegExp(needUpdateWordList[i], 'ig'); inputContent =inputContent.replace(r, "*") } console.log(inputContent); return inputContent }, addPosts: function () { var self = this; // 检查更新发送的内容 this.newPosts.content.text = this.$options.methods.wordFilter(this.newPosts.content.text); // 调用methods中的另外一个方法 this.$httpPost(this.$http, '接口信息', this.newPosts, "", function(ret){}); }, addNote: function () { var self = this; // 检查更新发送的内容 this.newNote.remark = this.$options.methods.wordFilter(this.newNote.remark); // 调用methods中的另外一个方法 this.$httpPost(this.$http, '接口信息', this.newNote, "", function(ret){}); }, } } </script>