Beego中敏感词过滤须用ahocorasick.Trie流式匹配并按rune索引精准替换:先调ctx.Input.RequestBody安全读取,预加载词库,再用bytes.Buffer单次拼接;禁用strings.ReplaceAll,避免UTF-8边界错误;过滤器应置于BeforeExec以支持路由级开关和session依赖。

Beego 中如何在请求体里做敏感词过滤
不能把 strings.ReplaceAll 直接塞进 Beego 的 filter 里——它不处理 UTF-8 rune 边界,中文一换行就漏词,emoji 会被切碎,还可能 panic。真正的做法是:先读 ctx.Input.RequestBody,再用 ahocorasick.Trie 做流式匹配,最后按 rune 索引精准替换。
关键操作点:
- 必须显式调用
ctx.Input.RequestBody,否则后续 controller 拿不到原始 body - 敏感词库要预加载进
ahocorasick.NewTrie(),别在 filter 里动态插入(重建开销大) - 匹配后别用
strings.Replace全局扫一遍——又变 O(n×m),而要用bytes.Buffer+ 匹配结果做单次拼接 - 中文/emoji 替换时,
match.Start和match.End是字节偏移,得转成[]rune再算位置,否则text[i:j]会越界或乱码
Beego Filter 里怎么安全读取并重写 request body
Beego 的 ctx.Request.Body 是一次性读取流,读完就 EOF。直接 io.ReadAll 后不重置,controller 会收空 body。必须手动包装回 io.NopCloser。
典型错误写法:body, _ := io.ReadAll(ctx.Request.Body) → 后续 controller 的 c.ShouldBindJSON() 拿不到数据
正确流程:
立即学习“go语言免费学习笔记(深入)”;
- 调用
ctx.Input.RequestBody(Beego 封装好的安全读法) - 若需自己读,必须:
body, _ := io.ReadAll(ctx.Request.Body); ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body)) - 过滤完新 body 后,同样要设回
ctx.Request.Body,否则 controller 解析失败 - 如果用了 JSON 解析,记得对
map[string]interface{}或 struct 的 string 字段逐个过筛,不能只扫原始字节流(字段名、数字会被误判)
Beego 过滤器该插在 BeforeRouter 还是 BeforeExec
选 BeforeExec。虽然 BeforeRouter 更早,但它发生在路由查找前,此时你连 handler 都不知道是谁,没法判断要不要过滤——比如 /api/v1/comment 要过滤,但 /api/v1/user 不需要。
BeforeExec 的优势:
- 路由已匹配,能拿到
ctx.Input.ControllerName和ctx.Input.MethodName,可做细粒度开关 - session 已初始化(
BeforeStatic之后才可用),适合依赖登录态的审核逻辑 - 避免对静态资源(
/static/)、健康检查(/healthz)误触发过滤 - Beego 2.x 推荐用
beego.BeeApp.FilterTypeBeforeExec,不是旧版的BeforeRouter
为什么用 ahocorasick 而不用 strings.Contains 或正则
因为性能差一个数量级,且无法支持重叠匹配。“王八”和“王八羔子”必须同时命中,正则写不出来,strings.Contains 要扫 N 遍。
真实瓶颈不在匹配算法本身,而在:
- I/O 缓冲区太小:默认
bufio.Scanner行缓冲上限 64KB,超长评论直接 panic;改用bufio.NewReader(f).ReadSize = 1 - 没做
sync.Pool复用bytes.Buffer和 JSON 解析器,高频接口 GC 压力飙升 - 敏感词含 emoji 时没校验
utf8.ValidString(),导致自动机建树失败却无报错 - 热更新词库时没加锁,多个 goroutine 并发写
trie的children map[rune]*Node,直接 panic: assignment to entry in nil map
最易被忽略的一点:Beego 的 filter 不会自动透传修改后的 body 给 controller 的绑定逻辑,你得确保 controller 用的是 ctx.Input.RequestBody,而不是直接读 ctx.Request.Body——后者可能还是原始未过滤版本。


















