应分层处理:简单文本用内存预热的AC自动机,长文本先strpos预筛再AC匹配,替换场景统一构建字符串;AC实例须在Worker启动时初始化并复用,禁用正则避免协程隔离与性能雪崩。

敏感词过滤该用同步还是异步方式?
Webman 是基于 Swoole 的常驻内存框架,请求生命周期长,不能直接套用传统 PHP 的 file_get_contents + str_replace 方式。同步加载词库并逐条匹配在高并发下会阻塞协程;而完全丢给子进程或 Redis 又增加延迟和运维复杂度。实际应按场景分层处理:
- 简单文本(如用户昵称、标题):用内存加载的 AC 自动机(
ahocorasick扩展或纯 PHP 实现),启动时预热词库,运行时零 IO - 长文本(如评论、文章正文):先做轻量级关键词命中预筛(
strpos或str_word_count快速排除),再对疑似段落走 AC 匹配 - 需要替换/打码的场景:避免用
str_replace多次遍历,改用 AC 返回所有命中位置后统一构建新字符串
AC 自动机怎么在 Webman 里安全初始化?
AC 自动机实例不能在每次请求中 new,必须复用。但直接放全局变量或静态属性,在多 Worker 进程下会各自加载一份,浪费内存且无法热更新。正确做法是:
- 在
start.php中使用Worker::$onWorkerStart回调,在每个 Worker 启动时单独构建 AC 实例,并绑定到$_SERVER['worker_id']可查的局部存储(如static $ac = []) - 词库路径统一配置在
config/autoload/sensitive.php,格式为['word1', 'word2', '违禁词'],避免硬编码 - 若需热更新,不要 reload Worker,而是监听文件变更(
inotify或轮询),触发$ac[$worker_id]->rebuild($words)(需 AC 类支持)
示例初始化片段:
Worker::$onWorkerStart = function (Worker $worker) {
$words = config('sensitive.words', []);
$ac = new Ahocorasick();
$ac->addWords($words);
$ac->build();
$_SERVER['sensitive_ac'] = $ac;
};
如何拦截 HTTP 请求里的敏感内容?
别在 Controller 里手动调用过滤逻辑——容易漏掉表单字段、JSON body、甚至 query 参数。应在中间件统一处理:
- 对于表单提交(
application/x-www-form-urlencoded):遍历$request->post()所有值,对字符串类型字段调用$ac->search($value) - 对于 JSON 请求:先
json_decode($request->rawBody(), true),再递归过滤所有 string 值(注意避免修改原始结构) - 拦截动作取决于业务:返回 400 错误(含具体违规词)、自动替换为
***、或记录日志后放行
关键细节:
- 不要过滤数组键名(可能含业务标识符)
- 二进制数据(如 base64 图片)跳过检测,否则误判率飙升
- 若开启
Content-Type: multipart/form-data,只过滤 text 字段,忽略文件流
为什么正则方案在 Webman 里不推荐?
有人习惯写 preg_replace('/(敏感|违禁|非法)/i', '***', $text),这在 Webman 下隐患明显:
- 每次调用都编译正则,协程间无法复用 PCRE cache(Swoole 的协程调度导致 cache 隔离)
- 多关键词时,
|拼接的正则性能随词量指数下降,1000 个词就可能拖慢到 50ms+ - 无法支持“前缀屏蔽”(如“腾讯”不违规,“腾讯QQ”违规)或“上下文感知”(如“苹果手机”合法,“苹果发布会”需审核)
真正需要正则的场景极少,仅限:
- 构造动态规则(如手机号、身份证号格式校验)
- 替换固定模式(如所有连续 4 个以上星号
/*{4,}/)
其余一律交给 AC 自动机,哪怕只是几十个词,也值得封装一层。
Webman 的常驻特性让敏感词加载一次、复用千次成为可能,但前提是别把它当普通 PHP 脚本用——初始化时机、协程安全、热更新路径,每一步错都会让过滤系统变成性能瓶颈或漏网之鱼。


















