基于确定有穷自动机算法(DFA)的敏感词过滤 Hyperf 组件,高效、灵活,支持协程环境。支持表情符号处理、变形文本检测和动态白名单管理。
- 🚀 高性能DFA算法:基于确定有穷自动机,检测效率高
- 🏗️ 模块化架构:采用单责任原则和策略模式,代码结构清晰,易于维护和扩展
- 🎯 智能白名单系统:支持动态白名单管理,避免误判
- 🔧 灵活配置:支持多种表情符号处理策略和变形文本检测
- 💾 智能缓存:三级缓存机制,大幅提升加载速度
- 🔍 前缀索引加速:针对中等长度文本的匹配优化
- 🌐 协程友好:完全支持 Hyperf 协程环境
- 📊 详细位置信息:支持返回敏感词的精确位置和长度
- 🛡️ 异常安全:敏感词处理异常不会影响业务流程
- 🔍 模糊匹配:支持绕过技术检测,如表情符号分隔、特殊字符插入等
composer require lovevivi/hyperf-sensitive-wordsphp bin/hyperf.php vendor:publish lovevivi/hyperf-sensitive-words配置文件位于 config/autoload/sensitive_words.php:
return [
// 用户自定义敏感词库路径,留空则使用默认词库
'word_path' => '',
// 词库合并模式:override-覆盖模式,append-追加模式
'merge_mode' => 'append',
// 是否开启中间件自动过滤
'middleware_enable' => false,
// 替换字符
'replace_char' => '*',
// 是否重复替换字符
'repeat_char' => true,
// 处理哪些HTTP请求参数
'http_params' => ['content', 'text', 'message'],
// 是否启用词库缓存
'enable_cache' => true,
// 缓存过期时间(秒),默认86400秒(1天)
'cache_expire' => 86400,
// 缓存文件存放路径,留空则按优先级决定
'cache_path' => '',
// 是否在应用启动时预热词库
'preload' => false,
// 是否启用前缀索引加速
'enable_prefix_index' => true,
// 表情符号处理策略 (ignore, remove, replace, include)
'emoji_strategy' => 'remove',
// 表情符号替换占位符(当emoji_strategy为replace时有效)
'emoji_placeholder' => '[表情]',
// 是否启用变形文本检测(如拼音、特殊字符分隔等)
'detect_variant_text' => true,
// 变形文本映射表路径(自定义映射表,留空使用内置映射)
'variant_map_path' => '',
// 白名单词库 - 新增功能
// 这里的词语即使命中敏感词规则,也不会被处理
'whitelist' => [
'assessment', // 示例: 防止 "ass" 被错误匹配
'helloween', // 示例: 防止 "hell" 被错误匹配
'中华人民共和国', // 示例: 整个词是合法的
// 在这里添加您需要的白名单词
],
];<?php
namespace App\Service;
use SensitiveWords\SensitiveWordsManager;
class ContentService
{
/**
* @var SensitiveWordsManager
*/
protected $sensitiveWordsManager;
public function __construct(SensitiveWordsManager $sensitiveWordsManager)
{
$this->sensitiveWordsManager = $sensitiveWordsManager;
}
public function filterContent(string $content): string
{
// 检查内容是否包含敏感词
if (!$this->sensitiveWordsManager->check($content)) {
// 替换敏感词
$content = $this->sensitiveWordsManager->replace($content);
// 或者获取所有敏感词
$badWords = $this->sensitiveWordsManager->getBadWords($content);
}
return $content;
}
}
### 1.1. 模糊匹配功能 **[新增]**
```php
<?php
namespace App\Service;
use SensitiveWords\SensitiveWordsManager;
class AdvancedContentService
{
/**
* @var SensitiveWordsManager
*/
protected $sensitiveWordsManager;
public function __construct(SensitiveWordsManager $sensitiveWordsManager)
{
$this->sensitiveWordsManager = $sensitiveWordsManager;
}
public function smartFilterContent(string $content): array
{
// 常规检测:快速,但无法识别绕过技术
$normalCheck = $this->sensitiveWordsManager->check($content);
$normalBadWords = $this->sensitiveWordsManager->getBadWords($content);
// 模糊检测:能发现各种绕过技术
$fuzzyCheck = $this->sensitiveWordsManager->check($content, true);
$fuzzyBadWords = $this->sensitiveWordsManager->getBadWords($content, 0, true);
return [
'normal_detection' => [
'has_sensitive' => $normalCheck,
'bad_words' => $normalBadWords
],
'fuzzy_detection' => [
'has_sensitive' => $fuzzyCheck,
'bad_words' => $fuzzyBadWords
],
'bypass_detected' => $fuzzyCheck && !$normalCheck, // 是否检测到绕过技术
];
}
public function handleBypassAttempts(string $content): string
{
// 示例绕过文本:'a法😊b轮😜c功d' (试图绕过"法轮功")
// 常规检测无法发现绕过
$normalDetected = $this->sensitiveWordsManager->getBadWords($content);
// 结果:[] (空数组)
// 模糊检测能发现绕过
$fuzzyDetected = $this->sensitiveWordsManager->getBadWords($content, 0, true);
// 结果:['法轮功'] (找到了原始敏感词)
if (!empty($fuzzyDetected)) {
// 发现绕过尝试,记录日志或采取其他措施
logger()->warning('检测到敏感词绕过尝试', [
'content' => $content,
'detected_words' => $fuzzyDetected
]);
return '内容包含不当信息';
}
return $content;
}
}<?php
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\RequestMapping;
use SensitiveWords\Annotation\SensitiveCheck;
/**
* @Controller
*/
class ContentController
{
/**
* @RequestMapping(path="/sensitive/aspect-test", methods={"GET", "POST"})
*/
public function test(RequestInterface $request)
{
// 从请求中获取内容
$content = $request->input('string', '');
// 调用助手方法,它将被切片处理
$filteredContent = $this->filterContent($content);
// 使用过滤后的内容构建响应
return [
'code' => 0,
'message' => '成功',
'data' => [
'original_content' => $content,
'processed_content' => $filteredContent
]
];
}
/**
* 助手方法:过滤敏感词
*
* @SensitiveCheck(param="content", replace=true, replaceChar="#")
*/
protected function filterContent(string $content): string
{
// 切片会在方法执行前处理$content参数
// 这里直接返回(可能已经被过滤的)内容
return $content;
}
}方式一:使用全局中间件,在 config/autoload/middlewares.php 中添加:
return [
'http' => [
SensitiveWords\Middleware\SensitiveWordsMiddleware::class,
],
];方式二:按需使用中间件:注解方式注入中间件
use Hyperf\HttpServer\Annotation\Middleware;
use SensitiveWords\Middleware\SensitiveWordsMiddleware;
/**
* @Middleware(SensitiveWordsMiddleware::class)
*/
class SensitiveMiddlewareTestController同时在配置文件中设置 middleware_enable 为 true 可以启用中间件自动过滤功能。
// 预热词库(在应用启动时调用,提高首次访问性能)
$container->get(SensitiveWordsManager::class)->warmup();
// 清除词库缓存
$container->get(SensitiveWordsManager::class)->clearCache();
// 设置词库(覆盖现有词库)- 方法已重命名
$container->get(SensitiveWordsManager::class)->setWordLibrary(['新词1', '新词2']);
// 增量添加敏感词
$container->get(SensitiveWordsManager::class)->addWords(['新词1', '新词2']);$manager = $container->get(SensitiveWordsManager::class);
// 添加白名单词语
$manager->addWhitelistWords(['正常词汇', '合法内容']);
// 删除白名单词语
$manager->removeWhitelistWords(['不再需要的白名单']);
// 设置白名单(覆盖现有白名单)
$manager->setWhitelistWords(['新白名单1', '新白名单2']);
// 获取当前所有白名单词语
$whitelistWords = $manager->getWhitelistWords();
// 检查词语是否在白名单中
$isWhitelisted = $manager->isWhitelisted('某个词语');
// 清空所有白名单
$manager->clearWhitelist();$manager = $container->get(SensitiveWordsManager::class);
// 获取当前词库中的所有敏感词
$allSensitiveWords = $manager->getAllSensitiveWords();
// 示例输出:['敏感词1', '敏感词2', '违禁词', ...]
var_dump($allSensitiveWords);
// 可以用于词库管理、统计分析等场景
$wordCount = count($allSensitiveWords);
echo "当前词库包含 {$wordCount} 个敏感词";// 设置表情符号处理策略
$manager->setEmojiStrategy('remove'); // 可选:ignore, remove, replace, include
// 如果选择replace策略,可以自定义替换占位符
$manager->setEmojiStrategy('replace', '[表情符号]');
// 启用或禁用变形文本检测
$manager->enableVariantTextDetection(true);
// 设置自定义变形文本映射表
$manager->setVariantMapPath('/path/to/custom/variant_map.php');// 获取敏感词的详细位置信息
$badWordsDetails = $manager->getBadWords($content, 0, true); // 第三个参数为true时返回详细信息
// 返回格式:
// [
// ['word' => '敏感词', 'offset' => 5, 'len' => 3],
// ['word' => '违禁词', 'offset' => 12, 'len' => 3]
// ]模糊匹配能够检测各种绕过技术,有效防止恶意用户通过插入字符、表情符号等方式规避敏感词检测:
- 表情符号分隔:
法😊轮😜功→ 检测到法轮功 - 特殊字符插入:
法.轮.功、法-轮-功→ 检测到法轮功 - 字母数字分隔:
法a轮b功c、法1轮2功3→ 检测到法轮功 - 空格分隔:
法 轮 功→ 检测到法轮功 - 混合分隔:
a法😊b轮😜c功d→ 检测到法轮功
$bypassText = 'a法😊b轮😜c功d相关内容';
// 常规检测:无法识别绕过
$normalWords = $manager->getBadWords($bypassText, 0, false);
// 结果:[] (空数组)
// 模糊检测:能识别绕过
$fuzzyWords = $manager->getBadWords($bypassText, 0, true);
// 结果:['法轮功'] (找到原始敏感词)- 常规检测:速度快,适合大量文本的实时处理
- 模糊检测:稍慢但更准确,适合关键内容的深度检测
- 智能缓存:模糊检测结果会被缓存,重复内容检测速度显著提升
check(string $content, bool $useFuzzyMatch = false): bool- 检查内容是否包含敏感词,支持模糊匹配检测绕过技术 [增强]replace(string $content, string $replaceChar = '*', bool $repeat = true): string- 替换内容中的敏感词,优化格式保持 [优化]getBadWords(string $content, int $wordNum = 0, bool $useFuzzyMatch = false): array- 获取内容中的敏感词列表,支持模糊匹配和详细位置信息 [增强]mark(string $content, string $sTag = '<span style="color:red">', string $eTag = '</span>'): string- 标记内容中的敏感词,优化格式保持 [优化]
setWordLibrary(array $words = []): bool- 设置词库(覆盖现有词库)[方法重命名]addWords(array $words): bool- 增量添加敏感词getAllSensitiveWords(): array- 获取当前词库中的所有敏感词warmup(): bool- 预热词库,提高首次访问性能clearCache(): bool- 清除词库缓存,在词库更新后调用
addWhitelistWords(array $words): bool- 添加白名单词语removeWhitelistWords(array $words): bool- 删除白名单词语setWhitelistWords(array $words): bool- 设置白名单词语(覆盖现有白名单)getWhitelistWords(): array- 获取当前所有白名单词语isWhitelisted(string $word): bool- 检查词语是否在白名单中clearWhitelist(): bool- 清空所有白名单词语
setEmojiStrategy(string $strategy, string $placeholder = '[表情]'): bool- 设置表情符号处理策略enableVariantTextDetection(bool $enable = true): bool- 启用或禁用变形文本检测setVariantMapPath(string $path): bool- 设置变形文本映射表路径
组件采用模块化设计,将原本的单一类拆分为多个专职类,遵循单责任原则和策略模式:
SensitiveHelper (主类)
├── FilterManager (过滤器管理器)
│ ├── BoundaryFilter (边界检测过滤器)
│ ├── LengthFilter (长度过滤器)
│ └── ContextFilter (上下文过滤器)
└── FuzzyMatcher (模糊匹配器)
FilterManager - 过滤器管理器
- 统一管理所有过滤策略
- 支持动态添加和移除过滤器
- 通过依赖注入提供给主类使用
BoundaryFilter - 边界检测过滤器
- 专门处理单字符边界检测逻辑
- 防止正常词汇中的单字符被误判
- 支持中英文混合文本的边界识别
LengthFilter - 长度过滤器
- 处理英文短词在中文语境中的过滤
- 避免短英文单词在中文文本中的误匹配
- 智能判断语境相关性
ContextFilter - 上下文过滤器
- 专业术语保护(技术、医学、农业等)
- 商业新闻内容的上下文分析
- 品牌和专有名词的智能识别
FuzzyMatcher - 模糊匹配器
- 独立处理所有模糊匹配逻辑
- 管理模糊匹配的缓存机制
- 支持各种绕过技术的检测
- 单责任原则:每个类只负责一个特定功能
- 策略模式:过滤器可以独立开发和测试
- 依赖注入:组件之间松耦合,便于单元测试
- 向后兼容:公共API保持不变,现有代码无需修改
- 易于扩展:新的过滤策略只需实现FilterStrategyInterface
白名单系统支持两种过滤模式:
直接将词语加入白名单,该词语将不会被检测为敏感词:
$manager->addWhitelistWords(['assessment', 'helloween']);
// "assessment" 和 "helloween" 不会被检测为敏感词当敏感词是更长白名单词语的一部分时,也会被过滤:
// 敏感词库包含:['ass', 'hell']
// 白名单包含:['assessment', 'helloween']
$text = 'This is an assessment of the helloween party.';
// "ass" 和 "hell" 不会被检测,因为它们是白名单词语的一部分支持运行时动态添加、删除、查询白名单:
// 运行时添加
$manager->addWhitelistWords(['新的合法词汇']);
// 检查是否在白名单中
if ($manager->isWhitelisted('某个词')) {
// 处理逻辑
}
// 获取所有白名单
$allWhitelist = $manager->getWhitelistWords();组件支持多种表情符号处理策略:
ignore: 忽略表情符号(默认)remove: 移除所有表情符号后再检测replace: 将表情符号替换为占位符再检测include: 将表情符号作为正常字符处理
能够识别常见的变形规避手段:
- 全角/半角转换(如"Apple"→"Apple")
- 特殊符号分隔(如"敏.感.词"→"敏感词")
- 数字与汉字混用(如"全①国"→"全1国"/"全一国")
- 中英文混合(如"wo爱中国"→"我爱中国")
- 支持词库缓存,大幅提升加载速度
- 三级缓存路径:用户配置、框架缓存目录、系统临时目录
- 支持自动预热,避免首次请求的性能损耗
- 白名单也会被缓存,确保性能一致性
针对中等长度文本的匹配算法优化:
- 按首字符索引敏感词,预先过滤不可能匹配的词
- 减少大型词库的检查次数,提高检测效率
支持返回敏感词的精确位置信息:
$details = $manager->getBadWords($content, 0, true);
// 返回:[['word' => '敏感词', 'offset' => 5, 'len' => 3], ...]- 基于DFA算法的高效词库树结构
- 前缀索引加速,减少检测过程中的比较次数
- 词库文件懒加载与缓存机制,避免频繁IO操作
- 支持协程环境下的高并发处理
- 灵活的降级策略,保证组件在各种环境下可用
- 异常安全,敏感词处理异常不会影响业务流程
- 智能白名单缓存,避免重复计算
- 单责任原则:将原本1749行的SensitiveHelper类拆分为多个专职类
- 策略模式实现:创建FilterStrategyInterface和具体过滤器类
BoundaryFilter: 单字符边界检测逻辑LengthFilter: 英文短词在中文语境的智能过滤ContextFilter: 专业术语、商业新闻和品牌保护逻辑
- 依赖注入设计:通过FilterManager统一管理所有过滤策略
- 模糊匹配器独立:FuzzyMatcher类专门处理模糊匹配和缓存
- 边界检测优化:单字符敏感词只在独立出现时才被检测
- 上下文语义分析:根据语境自动过滤正常用词
- 专业术语保护:自动识别技术、科研、农业等专业领域术语
- 商业内容保护:电商、企业、媒体等商业词汇智能保护
- 误检率降低:在真实新闻文本测试中,误检率从87.5%降至0%
- 缓存管理优化:模糊匹配缓存通过专门的FuzzyMatcher管理
- 初始化修复:解决addWords和getBadWord方法的词库自动加载问题
- 测试完善:52个测试用例全部通过,代码稳定性显著提升
- 可维护性:模块化设计便于独立开发和测试各个功能
- 可扩展性:新的过滤策略可以轻松通过实现接口来添加
- 向后兼容:所有公共方法保持完全兼容,现有代码无需修改
- 新增
check()方法的$useFuzzyMatch参数,支持绕过技术检测 - 新增
getBadWords()方法的$useFuzzyMatch参数,支持模糊匹配获取敏感词 - 新增
getFuzzyBadWords()底层方法,专门处理模糊匹配逻辑 - 支持检测表情符号分隔、特殊字符插入、字母数字分隔等绕过技术
- 智能缓存机制:模糊检测结果自动缓存,避免重复计算
- 修复
replace()和mark()方法在变形文本检测开启时移除空格的问题 - 通过临时禁用变形文本检测,完美保持原始文本格式
- 英文敏感词替换现在能正确保留空格:
'This is bad word'→'This is *** word'
getBadWords()方法现在支持模糊匹配模式- 新增性能优化的缓存清理机制
- 保持完全向后兼容,现有代码无需修改
- 新增
testGetBadWordsWithFuzzyMatch()测试用例 - 新增模糊匹配性能对比测试
- 修复格式保持相关的测试用例
- 总计新增 4 个测试方法,确保功能稳定性
- 模糊检测通过优化算法和缓存机制,性能大幅提升
- 重复内容检测提速高达 472%
- 敏感词按长度排序,优化匹配效率
- 新增动态白名单管理API
- 支持上下文白名单过滤
- 白名单与缓存系统集成
- 配置文件支持白名单预设
updateWordLibrary()→setWordLibrary()- 更准确地反映方法功能
getBadWords()方法支持返回详细位置信息replace()和mark()方法基于精确位置进行操作- 匹配算法一致性优化,确保前缀索引与DFA主循环行为统一
- 新增
getAllSensitiveWords()方法,支持获取当前词库中的所有敏感词
- 新增
SensitiveWordsManagerTest.php- 管理器功能全面测试 - 新增
WhitelistManagementTest.php- 白名单功能专项测试 - 52个测试用例,307个断言,确保功能稳定性
- 重复代码优化,提取公共逻辑
- 中文注释完善,提高代码可读性
- 异常处理增强,提高系统健壮性
MIT
本项目包含全面的单元测试,确保所有功能的正确性和稳定性。
tests/
├── Cases/
│ ├── SensitiveHelperTest.php # SensitiveHelper 核心功能测试
│ ├── SensitiveWordsManagerTest.php # SensitiveWordsManager 管理器测试
│ └── WhitelistManagementTest.php # 白名单功能专项测试
└── AbstractTestCase.php # 测试基类
- 白名单功能测试(上下文过滤、直接白名单等)
- addWords 增量添加功能测试
- getBadWord 详细模式测试(返回位置信息)
- replace 和 mark 方法测试(各种匹配模式)
- 变形文本处理测试
- 边界情况和异常处理测试
- getAllSensitiveWords 功能测试 - 新增
- 基础功能测试:check、replace、getBadWords、mark
- 词库管理测试:setWordLibrary、addWords、getAllSensitiveWords
- 系统功能测试:warmup、clearCache
- 配置管理测试:setEmojiStrategy、enableVariantTextDetection、setVariantMapPath
- 复杂场景测试:多功能组合使用
- 性能测试:大词库和大文本处理
- 边界情况测试:空内容、特殊字符等
- 模糊匹配测试:testGetBadWordsWithFuzzyMatch - 新增
- 动态白名单管理:addWhitelistWords、removeWhitelistWords
- 白名单设置:setWhitelistWords、clearWhitelist
- 白名单查询:getWhitelistWords、isWhitelisted
- 白名单与敏感词检测的交互测试
- 预处理对白名单的影响测试
# 运行所有测试
composer test
# 或者使用 PHPUnit 直接运行
./vendor/bin/phpunit
# 运行特定测试文件
./vendor/bin/phpunit tests/Cases/SensitiveWordsManagerTest.php
# 运行特定测试方法
./vendor/bin/phpunit tests/Cases/SensitiveWordsManagerTest.php::testGetAllSensitiveWords
# 显示详细输出
./vendor/bin/phpunit --verbose
# 生成代码覆盖率报告(需要安装 xdebug)
./vendor/bin/phpunit --coverage-html coverage/- 总测试数: 52个测试 (包含架构重构后的所有功能测试)
- 总断言数: 313个断言
- 测试覆盖: 核心功能100%覆盖,包括模糊匹配和模块化架构
- 测试类型: 单元测试、集成测试、性能测试、模糊匹配专项测试
- 架构测试: 过滤器策略、依赖注入、缓存管理等模块化功能全面测试
如果你需要添加新的测试用例,请遵循以下规范:
- 测试文件命名: 使用
*Test.php后缀 - 测试方法命名: 使用
test*前缀,方法名要清晰描述测试内容 - 测试分组: 按功能模块分组,相关测试放在同一个测试类中
- 断言使用: 使用具体的断言方法,如
assertStringContainsString而不是通用的assertTrue - 测试数据: 使用有意义的测试数据,包含中英文、特殊字符等各种情况
项目配置了完整的测试流程,每次代码提交都会自动运行全部测试,确保代码质量。