怎样在PHP项目中实现敏感词过滤?

wen java案例 2

本文目录导读:

怎样在PHP项目中实现敏感词过滤?

  1. Trie树(前缀树)方案(推荐)
  2. DFA算法(确定有限自动机)
  3. 正则表达式方案(简单但性能差)
  4. 使用第三方库(推荐生产环境)
  5. Redis+布隆过滤器(大规模高并发)
  6. 实践建议
  7. 推荐方案

在PHP项目中实现敏感词过滤,通常有以下几种常见方案,按推荐程度排序:

Trie树(前缀树)方案(推荐)

这是性能最好的方案,适合大规模敏感词库:

<?php
class SensitiveWordFilter {
    private $trieTree = [];
    /**
     * 构建敏感词树
     */
    public function loadWords(array $words) {
        foreach ($words as $word) {
            $this->addWord($word);
        }
    }
    /**
     * 添加单个敏感词到Trie树
     */
    private function addWord($word) {
        $node = &$this->trieTree;
        $len = mb_strlen($word, 'UTF-8');
        for ($i = 0; $i < $len; $i++) {
            $char = mb_substr($word, $i, 1, 'UTF-8');
            if (!isset($node[$char])) {
                $node[$char] = [];
            }
            $node = &$node[$char];
        }
        // 标记敏感词结束
        $node['is_end'] = true;
    }
    /**
     * 过滤敏感词(替换为指定字符)
     */
    public function filter($text, $replaceChar = '*') {
        $result = '';
        $len = mb_strlen($text, 'UTF-8');
        $i = 0;
        while ($i < $len) {
            $node = $this->trieTree;
            $matched = false;
            $endPos = $i;
            for ($j = $i; $j < $len; $j++) {
                $char = mb_substr($text, $j, 1, 'UTF-8');
                if (!isset($node[$char])) {
                    break;
                }
                $node = $node[$char];
                if (isset($node['is_end'])) {
                    $endPos = $j;
                    $matched = true;
                }
            }
            if ($matched) {
                // 替换敏感词
                $wordLen = $endPos - $i + 1;
                $result .= str_repeat($replaceChar, $wordLen);
                $i = $endPos + 1;
            } else {
                // 保留原字符
                $result .= mb_substr($text, $i, 1, 'UTF-8');
                $i++;
            }
        }
        return $result;
    }
    /**
     * 检查是否包含敏感词
     */
    public function hasSensitiveWord($text) {
        $len = mb_strlen($text, 'UTF-8');
        for ($i = 0; $i < $len; $i++) {
            $node = $this->trieTree;
            for ($j = $i; $j < $len; $j++) {
                $char = mb_substr($text, $j, 1, 'UTF-8');
                if (!isset($node[$char])) {
                    break;
                }
                $node = $node[$char];
                if (isset($node['is_end'])) {
                    return true;
                }
            }
        }
        return false;
    }
}
// 使用示例
$filter = new SensitiveWordFilter();
$filter->loadWords(['敏感词1', '敏感词2', '测试']);
$text = '这是一段包含敏感词1和测试的文本';
$result = $filter->filter($text, '*');
echo $result; // 输出: 这是一段包含***和**的文本
if ($filter->hasSensitiveWord($text)) {
    echo '包含敏感词';
}

DFA算法(确定有限自动机)

与Trie树类似,但有更灵活的匹配规则:

<?php
class DFAFilter {
    private $wordTree = [];
    public function loadWords(array $words) {
        foreach ($words as $word) {
            $this->addWord($word);
        }
    }
    private function addWord($word) {
        $len = mb_strlen($word, 'UTF-8');
        $node = &$this->wordTree;
        for ($i = 0; $i < $len; $i++) {
            $char = mb_substr($word, $i, 1, 'UTF-8');
            if (!isset($node[$char])) {
                $node[$char] = [
                    'is_end' => false,
                    'child' => []
                ];
            }
            if ($i == $len - 1) {
                $node[$char]['is_end'] = true;
            }
            $node = &$node[$char]['child'];
        }
    }
    public function filter($text, $replace = '***') {
        $result = '';
        $textLen = mb_strlen($text, 'UTF-8');
        for ($start = 0; $start < $textLen; $start++) {
            $node = $this->wordTree;
            $matchLen = 0;
            $tempResult = '';
            for ($i = $start; $i < $textLen; $i++) {
                $char = mb_substr($text, $i, 1, 'UTF-8');
                if (!isset($node[$char])) {
                    break;
                }
                $tempResult .= $char;
                $matchLen++;
                if ($node[$char]['is_end']) {
                    $result .= $replace;
                    $start = $i;
                    $matchLen = 0;
                    break;
                }
                $node = &$node[$char]['child'];
            }
            if ($matchLen == 0) {
                $result .= mb_substr($text, $start, 1, 'UTF-8');
            }
        }
        return $result;
    }
}

正则表达式方案(简单但性能差)

<?php
class RegexFilter {
    private $pattern = '';
    public function loadWords(array $words) {
        // 转义特殊字符
        $escaped = array_map(function($word) {
            return preg_quote($word, '/');
        }, $words);
        $this->pattern = '/(' . implode('|', $escaped) . ')/u';
    }
    public function filter($text, $replace = '*') {
        return preg_replace($this->pattern, $replace, $text);
    }
    public function hasSensitiveWord($text) {
        return preg_match($this->pattern, $text) === 1;
    }
}
// 使用示例
$filter = new RegexFilter();
$filter->loadWords(['敏感词1', '敏感词2']);
$result = $filter->filter('这是一段包含敏感词1的文本', '***');

使用第三方库(推荐生产环境)

COS-AC自动机库

composer require lustre/multibyte-cjk-regular
use Multibyte\CJKRegular;
$filter = new CJKRegular();
$filter->addDictionary(['敏感词1', '敏感词2']);
$filter->addFromFile('/path/to/words.txt');
$result = $filter->replace('这是敏感词1文本', '***');

Redis+布隆过滤器(大规模高并发)

<?php
class BloomFilter {
    private $redis;
    private $key = 'sensitive_words_bloom';
    public function __construct($redis) {
        $this->redis = $redis;
    }
    public function addWord($word) {
        $positions = $this->getHashPositions($word);
        foreach ($positions as $position) {
            $this->redis->setBit($this->key, $position, 1);
        }
    }
    public function exists($word) {
        $positions = $this->getHashPositions($word);
        foreach ($positions as $position) {
            if ($this->redis->getBit($this->key, $position) == 0) {
                return false;
            }
        }
        return true; // 有误判率
    }
    private function getHashPositions($str) {
        // 多个哈希函数
        $positions = [];
        $positions[] = crc32($str) % 100000;
        $positions[] = md5($str) % 100000;
        $positions[] = sha1($str) % 100000;
        return $positions;
    }
}

实践建议

敏感词库管理

// 从文件加载
$words = file('sensitive_words.txt', FILE_IGNORE_NEW_LINES);
// 或者从数据库加载
$words = $db->query("SELECT word FROM sensitive_words")->fetchAll(PDO::FETCH_COLUMN);

性能优化

// 使用单例模式
class FilterManager {
    private static $instance;
    private $filter;
    private function __construct() {
        $this->filter = new SensitiveWordFilter();
        $this->filter->loadWords($this->getWords());
    }
    public static function getInstance() {
        if (!self::$instance) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

完整过滤器类

<?php
class SensitiveFilter {
    use SingletonTrait; // 假设有单例trait
    private $filter;
    private $cacheTime = 3600; // 缓存1小时
    public function __construct() {
        $this->filter = new DFAFilter();
        $this->loadWords();
    }
    private function loadWords() {
        // 优先从缓存加载
        $words = Cache::get('sensitive_words');
        if (!$words) {
            $words = $this->fetchWordsFromDB();
            Cache::set('sensitive_words', $words, $this->cacheTime);
        }
        $this->filter->loadWords($words);
    }
    public function filter($text) {
        return $this->filter->filter($text);
    }
    public function hasSensitive($text) {
        return $this->filter->hasSensitiveWord($text);
    }
}

推荐方案

  • 小型项目:Trie树方案(自己实现)
  • 中型项目:第三方库如 lustre/multibyte-cjk-regular
  • 大型项目:Redis + Trie树 + Bloom Filter 组合方案
  • 实时处理:AC自动机(Aho-Corasick)

选择方案时要考虑:敏感词库大小、文本长度、并发量、允许的误判率等因素。

抱歉,评论功能暂时关闭!