敏感词过滤关键在准确、不漏、稳定、可维护;优先用str_replace处理常规场景,正则仅补缺且须转义,词表需热更新,过滤后必须验证并确保utf-8编码安全写入。

直接用字符串变量做敏感词过滤,关键不在“怎么替”,而在“替得准、不漏、不崩、可维护”。生产环境里,一次没赋值、一个空数组、一次编码错位,都可能让过滤形同虚设。
先用 str_replace 做稳第一道防线
90% 的常规场景(词表固定、无谐音/变体、词量几千以内),str_replace 是最安全、最快、最可控的选择。它不依赖正则引擎,不会因边界符写错导致误杀(比如把“和谐号”拦掉),也不怕 Unicode 零宽字符干扰匹配逻辑。
- 必须显式赋值:
$content = str_replace($badwords, $replace, $content);—— 忘了这行,等于没过滤 - 过滤前清理词表:
$badwords = array_filter($badwords, 'strlen');,避免空字符串触发 PHP Warning 并跳过整轮替换 - 确保输入是字符串:
if (!is_string($content)) { throw new InvalidArgumentException('内容必须为字符串'); }
正则只补缺,不主战
当需要处理大小写混排(如“H3xie”)、符号穿插(如“和-谐”)、或全角/半角混用时,才引入正则。但绝不能直接拼接用户词进 preg_replace,必须 escape:
- 逐个转义敏感词:
$escaped_words = array_map(fn($w) => preg_quote($w, '/'), $badwords); - 构造模式时加字边界:
$pattern = '/(' . implode('|', $escaped_words) . ')/u';——/u支持 UTF-8,防止子串误伤 - 慎用
g修饰符;若需保留原始大小写结构,改用回调函数做动态掩码
词表必须脱离代码硬编码
线上改一个词就要发版?不行。词表要能热更新,且不被 OPcache 锁死:
- 存为独立 PHP 文件(如
sensitive_words.php),返回纯数组:<?php return ["暴力", "诈骗", "违禁"]; ?> - 加载时加时间戳校验:
if (filemtime($wordfile) > $cached_time) { $badwords = require $wordfile; $cached_time = time(); } - 更高要求用 Redis 缓存:
$badwords = json_decode($redis->get('sensitive:words') ?: '[]', true);,更新时SET sensitive:words即刻生效
写文件前必须双重确认
过滤完不验证,就等于把筛子当滤纸用:
- 检查是否真有替换发生:
if ($content === $original) { /* 无敏感词,可跳过后续校验 */ } - 写入前确认路径可写:
if (!is_writable(dirname($file))) { throw new RuntimeException("目录不可写"); } - 强制 UTF-8 编码写入:
file_put_contents($file, mb_convert_encoding($content, 'UTF-8', 'auto'), LOCK_EX);,避免中文乱码或截断 - 文件名绝不拼接用户输入;限定后缀 + 过滤路径分隔符:
$safe_filename = preg_replace('/[\/\\\0]/', '_', $_POST['name']) . '.txt';










