基於DFA算法、RegExp對象和vee-validate實現前端敏感詞過濾

  面臨敏感詞過濾的問題,最簡單的方案就是對要檢測的文本,遍歷全部敏感詞,逐個檢測輸入的文本是否包含指定的敏感詞。前端

  很明顯上面這種實現方法的檢測時間會隨着敏感詞庫數量的增長而線性增長。系統會所以面臨性能和CPU消耗的問題。vue

1、基於DFA敏感詞算法解析

  在計算理論中,肯定有限狀態自動機肯定有限自動機(deterministic finite automaton, DFA)是一個能實現狀態轉移的自動機,是表示有限個狀態及在這些狀態間轉移和動做等行爲的數學模型python

  對於一個給定的屬於該自動機的狀態和一個屬於該自動機字母表\Sigma 的字符,它都能根據事先給定的轉移函數轉移到下一個狀態(這個狀態能夠是先前那個狀態)。git

一、DFA概念

  肯定有限狀態自動機{\mathcal {A}}是由github

  • 一個非空有限的狀態集合Q
  • 一個輸入字母表\Sigma (非空有限的字符集合)
  • 一個轉移函數\delta :Q\times \Sigma \rightarrow Q(例如:\delta \left(q,\sigma \right)=p,\left(p,q\in Q,\sigma \in \Sigma \right)
  • 一個開始狀態s\in Q
  • 一個接受狀態的集合F\subseteq Q

  所組成的5-元組。所以一個DFA能夠寫成這樣的形式:{\mathcal  {A}}=\left(Q,\Sigma ,\delta ,s,F\right)正則表達式

  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只須要存儲一個結束標誌就好了。

  上面最後就是保存了一個{"isend": True} 的HashMap,來標識這個value對應的key是敏感詞的最後一個字符。

三、基於敏感詞庫搜索算法 

  以上面例子構造出來的SensitiveMap爲敏感詞庫進行示意,假設這裏輸入的關鍵字爲:王八很差,流程圖以下:

  

  使用HashMap存儲的好處:HashMap在理想狀況下能夠以O(1)的時間複雜度進行查詢,因此在遍歷待檢測字符串的過程當中,能夠以O(1)的時間複雜度檢索出當前字符是否在敏感詞庫中,大大提高效率。

2、JavaScript RegExp 對象

  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

一、建立 RegExp 對象語法

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屬性指示的索引(而且不嘗試從任何後續的索引匹配)。

二、返回值和拋出

(1)返回值

  一個新的 RegExp 對象,具備指定的模式和標誌。若是參數 pattern 是正則表達式而不是字符串,那麼 RegExp() 構造函數將用與指定的 RegExp 相同的模式和標誌建立一個新的 RegExp 對象

  若是不用 new 運算符,而將 RegExp() 做爲函數調用,那麼它的行爲與用 new 運算符調用時同樣,只是當 pattern 是正則表達式時,它只返回 pattern,而再也不建立一個新的 RegExp 對象

(2)拋出

  SyntaxError - 若是 pattern 不是合法的正則表達式,或 attributes 含有 "g"、"i" 和 "m" 以外的字符,拋出該異常。

  TypeError - 若是 pattern 是 RegExp 對象,但沒有省略 attributes 參數,拋出該異常。

三、RegExp對象方法

(1)test()方法

  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來檢查敏感字段。

(2)exec()方法

  exec()方法用於檢索字符串中的正則表達式的匹配,在目標字符串中執行一次正則匹配操做。。

RegExpObject.exec(string)

  string是要檢索的字符串,返回一個數組,其中存放匹配的結果。若是未找到匹配,則返回值爲 null。

  exec() 方法的功能很是強大,它是一個通用的方法,並且使用起來也比 test() 方法以及支持正則表達式的 String 對象的方法更爲複雜。

(3)compile()方法

  compile() 方法被用於在腳本執行過程當中(從新)編譯正則表達式。與RegExp構造函數基本同樣。  

  該特性已經從 Web 標準中刪除,雖然一些瀏覽器目前仍然支持它,但也許會在將來的某個時間中止支持,略。

3、Vue前端項目中實現

一、獲取敏感詞列表

  在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

(1)構造敏感詞map數據結構

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;
}

(2)檢查敏感詞存在

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;
} 

(3)敏感詞列表

export const sensitiveWordList = [各類敏感詞內容]

三、使用vee-validate自定義驗證敏感詞

  在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>

  顯示效果以下所示:

  

四、在vue單頁面組件過濾並替換敏感詞

  在發帖、發評論等場景,查詢到敏感詞時替換爲」*「。

<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>    
相關文章
相關標籤/搜索