推荐用 github.com/bobusumisu/aho-corasick 构建自动机实现敏感词匹配,时间复杂度从 o(n×m) 降至 o(n+k),需预处理词库、复用实例、避免 body 读取后未重置,并用 rwmutex 保障热更新并发安全。

用 aho-corasick 实现高性能敏感词匹配
Go 原生没有内置的多模式字符串匹配库,直接用 strings.Contains 遍历所有词效率极低,尤其词库超千条时响应明显卡顿。推荐用 github.com/BobuSumisu/aho-corasick(轻量、无依赖、支持 UTF-8),它把敏感词构建成自动机,单次扫描完成全部匹配,时间复杂度从 O(n×m) 降到 O(n+k),n 是文本长度,k 是命中词数。
实操建议:
- 初始化时一次性构建
ac.AhoCorasick实例,复用而非每次新建;词库变更需重建实例 - 敏感词需预处理:统一转小写(若不区分大小写)、去首尾空格、过滤空字符串,否则影响匹配准确性
- 匹配返回的是
[]ac.Match,每个含Start、End和Keyword字段,便于做替换或拦截 - 避免在 HTTP handler 中同步加载词库文件——应提前读入内存并初始化 AC 自动机
在 Gin 中间件里做请求体敏感词拦截
Gin 默认不解析 request body(除非显式调用 c.ShouldBindJSON 等),所以不能只靠 query 或 form 过滤,必须手动读取原始 body 再检测。常见错误是直接对 c.Request.Body 调用 io.ReadAll 后没重置,导致后续绑定失败(body 已 EOF)。
实操建议:
- 用
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))恢复 body 流,确保下游仍可正常绑定 - 只对
Content-Type: application/json或application/x-www-form-urlencoded类型做检测,跳过文件上传等二进制请求 - 检测到命中词后,立即调用
c.AbortWithStatusJSON(400, map[string]string{"error": "包含敏感词"})并 return,防止继续执行 - 不要在中间件里做替换(如 * 号遮蔽),拦截比清洗更安全——避免语义歧义或绕过(例如“草”被替成“*”,但“caoo”可能漏判)
ReplaceAllString 不适合敏感词脱敏的三个原因
有人用 regexp.MustCompile(`(?i)` + keyword).ReplaceAllString(text, "***") 做替换,这在实际业务中问题很大:
- 正则编译开销大,每词一编译 → 应预编译所有词为一个
|(word1|word2|...)的 regexp,但词量超 500 就易触发 regexp 包 panic - 无法处理重叠匹配(如词库含“苹果”和“果酱”,文本“苹果酱”会被错切)
- 大小写混排场景失效(如“ShiMa”匹配不到“石马”,而 AC 自动机可设 ignore-case 模式)
真要脱敏,应在 AC 匹配后,按 Match.Start/Match.End 坐标逐段替换,确保位置精准、不污染上下文。
词库热更新与并发安全陷阱
敏感词常需动态增删,但 aho-corasick 实例不可并发写。常见错误是直接在 goroutine 里重建 AC 实例并赋值给全局变量,引发读写竞争。
实操建议:
- 用
sync.RWMutex保护 AC 实例指针,读操作(匹配)用RLock,写操作(重建)用Lock - 新实例构建完成后再原子替换指针,旧实例自然被 GC,无需手动清理
- 词文件监听推荐用
fsnotify,但注意:文件写入可能分多次触发事件,应加 debounce(如 100ms 内只处理最后一次) - 测试热更新时,务必用
go tool pprof观察 GC 频率——频繁重建 AC 实例会导致内存抖动
AC 自动机本身不保存状态,但热更新时的锁粒度和替换时机,才是压测下出问题最多的环节。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











