首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 敏感詞過濾系統實作:Trie 樹 + Memcached 快取高效能方案

     編輯:PHP 相關知識

高併發場景下,敏感詞過濾需兼顧準確性 (支援中文分詞、同音字、變體) 與效能 (毫秒級響應)。本文實作一個基於 Trie 樹 (字典樹) 的過濾器,搭配 Memcached 快取熱詞庫,並提供完整類別代碼。

核心資料結構:Trie 節點

<?php
class TrieNode {
    public $children = [];  // 字元 => TrieNode
    public $isEnd = false;  // 是否為敏感詞結尾
    public $word = '';      // 完整敏感詞 (用於替換)

    public function __construct() {}
}

敏感詞過濾器主類別

<?php
class SensitiveWordFilter {
    private $root;
    private $replaceChar = '*';

    public function __construct(array $words = [], $replaceChar = '*') {
        $this->replaceChar = $replaceChar;
        $this->root = new TrieNode();
        foreach ($words as $w) $this->addWord($w);
    }

    /** 建立 Trie 樹 */
    public function addWord(string $word) {
        $node = $this->root;
        $len = mb_strlen($word, 'UTF-8');
        for ($i = 0; $i < $len; $i++) {
            $char = mb_substr($word, $i, 1, 'UTF-8');
            if (!isset($node->children[$char])) {
                $node->children[$char] = new TrieNode();
            }
            $node = $node->children[$char];
        }
        $node->isEnd = true;
        $node->word = $word;
    }

    /** 從檔案載入 (一行一詞) */
    public static function loadFromFile(string $path): self {
        $words = [];
        $handle = fopen($path, 'r');
        if ($handle) {
            while (($line = fgets($handle)) !== false) {
                $word = trim($line);
                if ($word !== '') $words[] = $word;
            }
            fclose($handle);
        }
        return new self($words);
    }

    /** 核心過濾:返回 [過濾後文字, 命中詞陣列] */
    public function filter(string $text): array {
        $hitWords = [];
        $result = '';
        $len = mb_strlen($text, 'UTF-8');
        
        for ($i = 0; $i < $len; $i++) {
            $node = $this->root;
            $matchLen = 0;
            $matchedWord = '';
            
            // 向前匹配最長敏感詞
            for ($j = $i; $j < $len; $j++) {
                $char = mb_substr($text, $j, 1, 'UTF-8');
                if (!isset($node->children[$char])) break;
                
                $node = $node->children[$char];
                $matchLen++;
                
                if ($node->isEnd) {
                    $matchedWord = $node->word;
                    // 繼續嘗試更長匹配 (最長匹配原則)
                }
            }
            
            if ($matchedWord !== '') {
                $result .= str_repeat($this->replaceChar, mb_strlen($matchedWord, 'UTF-8'));
                $hitWords[] = $matchedWord;
                $i += $matchLen - 1; // 跳過已匹配字元
            } else {
                $result .= mb_substr($text, $i, 1, 'UTF-8');
            }
        }
        
        return [$result, $hitWords];
    }

    /** 簡易檢測是否包含敏感詞 (不替換) */
    public function contains(string $text): bool {
        $len = mb_strlen($text, 'UTF-8');
        for ($i = 0; $i < $len; $i++) {
            $node = $this->root;
            for ($j = $i; $j < $len; $j++) {
                $char = mb_substr($text, $j, 1, 'UTF-8');
                if (!isset($node->children[$char])) break;
                $node = $node->children[$char];
                if ($node->isEnd) return true;
            }
        }
        return false;
    }
}

Memcached 快取整合類別

<?php
class CachedSensitiveFilter {
    private $memcached;
    private $cacheKey = 'sensitive_words_trie_v1';
    private $wordFile;
    private $filter;

    public function __construct(string $wordFile, array $memcachedConfig = []) {
        $this->wordFile = $wordFile;
        $this->memcached = new Memcached();
        $this->memcached->addServers($memcachedConfig ?: [['127.0.0.1', 11211]]);
        $this->initFilter();
    }

    private function initFilter() {
        // 嘗試從快取載入序列化的 Trie 根節點
        $data = $this->memcached->get($this->cacheKey);
        if ($data !== false && isset($data['root'])) {
            $this->filter = new SensitiveWordFilter();
            $this->filter->root = $data['root']; // 需 SensitiveWordFilter 開放 root 屬性或提供 setRoot
            return;
        }

        // 快取未命中,從檔案建立
        $this->filter = SensitiveWordFilter::loadFromFile($this->wordFile);
        
        // 序列化存入快取 (有效期 24 小時)
        $this->memcached->set($this->cacheKey, ['root' => $this->filter->root], 86400);
    }

    /** 公開過濾介面 */
    public function filter(string $text): array {
        return $this->filter->filter($text);
    }

    /** 重新載入 (詞庫更新後呼叫) */
    public function reload() {
        $this->memcached->delete($this->cacheKey);
        $this->initFilter();
    }
}

使用範例

<?php
require_once 'SensitiveWordFilter.php';
require_once 'CachedSensitiveFilter.php';

// 配置
$wordFile = __DIR__ . '/config/sensitive_words.txt'; // 一行一詞
$memConfig = [['127.0.0.1', 11211]];

// 初始化 (單例模式建議)
$filter = new CachedSensitiveFilter($wordFile, $memConfig);

// 測試
$text = "這裡有敏感詞測試和另一個違禁詞內容";
list($clean, $hits) = $filter->filter($text);

echo "原文: $text\n";
echo "過濾: $clean\n";
echo "命中: " . implode(', ', $hits) . "\n";

// 輸出範例:
// 原文: 這裡有敏感詞測試和另一個違禁詞內容
// 過濾: 這裡有****測試和另一個****內容
// 命中: 敏感詞, 違禁詞

敏感詞庫檔案範例

config/sensitive_words.txt (UTF-8 編碼,一行一詞):

敏感詞
違禁詞
測試關鍵字
另一個違禁詞
...

效能優化要點

  1. Trie 樹 O(m) 查找:m 為文本長度,單次掃描完成所有匹配,優於多次 str_replace 或正則遍歷。
  2. Memcached 快取序列化 Trie:避免每請求重建樹,首次建立後極快。
  3. 熱更新機制:詞庫異動呼叫 reload() 清除快取重建。
  4. 中文分詞前置:若需語義級過濾,可先用 Jieba/PSCWS 分詞再送入過濾器。
  5. 同音/異形詞擴展:預處理階段將同音字、全半形、Leet 語 (如 a→@) 正規化後再入樹。

生產環境部署建議

  • 使用 phpiredis 或 predis 替代 Memcached,支援更豐富資料結構。
  • 詞庫版本化管理 (Git),部署流程自動執行 reload()。
  • 監控 hit rate、平均延遲、命中詞分佈。
  • 提供管理後台:增刪詞、測試過濾、查看日誌。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved