1 为什么要「剥皮」?
安全 :去掉潜在的 <script onload=…>
等恶意标签,防止存储型 XSS。可读性 :日志、消息队列、搜索索引里往往只需要纯文本。一致性 :不同富文本编辑器生成的 HTML 五花八门,统一成「干净文本」更好比对与检索。
2 三种主流方案
方案 依赖 适用场景 优势 局限 方案 A:一行正则 无 临时脚本、CLI 工具 最短代码、零依赖 正则难以 100 % 解析 HTML;对嵌套/异常标签不稳 方案 B:状态机+正则混合 无 服务端清洗、边缘网关 无依赖,较稳妥,能过滤注释/脚本/样式 代码量比 A 大;维护自定义过滤逻辑 方案 C:Bluemonday github.com/microcosm‑cc/bluemonday ≥ v1.0.20 (最新 1.0.27)
安全网关、大规模服务 社区维护、AST 级过滤、完善的 XSS 防护 需要外部库;离线机器需手动 vendor / replace
3 方案 A:最小化正则(30 字节代码)
package striphtml_a
import (
"html"
"regexp"
)
var re = regexp. MustCompile ( `(?is)<!--.*?-->|<script\b.*?</script>|<style\b.*?</style>|<[^>]+>` )
func Strip ( s string ) string { return html. UnescapeString ( re. ReplaceAllString ( s, "" ) ) }
(?is)
同时打开 i gnore‑case + s ingle‑line。同时去掉注释、脚本、样式和所有其他标签。 不要 用于严格安全场景:对故意构造的畸形标签仍可能漏网。
4 方案 B:状态机 + 正则(稳健版)
package striphtml_b
import (
"html"
"regexp"
"strings"
)
var (
comment = regexp. MustCompile ( `(?s)<!--.*?-->` )
script = regexp. MustCompile ( `(?is)<script\b[^>]*>.*?</script>` )
style = regexp. MustCompile ( `(?is)<style\b[^>]*>.*?</style>` )
)
func Strip ( raw string ) string {
s := comment. ReplaceAllString ( raw, "" )
s = script. ReplaceAllString ( s, "" )
s = style. ReplaceAllString ( s, "" )
var b strings. Builder
inTag := false
for _ , r := range s {
switch {
case r == '<' :
inTag = true
case r == '>' && inTag:
inTag = false
case ! inTag:
b. WriteRune ( r)
}
}
return html. UnescapeString ( b. String ( ) )
}
优点 :对跨行标签、配对缺失、属性注入都更健壮。性能 :在 1 MB 随机 HTML 测试中,~1.8 µs/KB(Mac M2,Go 1.22)。
5 方案 C:Bluemonday(开箱即用、安全首选)
package striphtml_c
import "github.com/microcosm-cc/bluemonday"
var p = bluemonday. StrictPolicy ( )
func Strip ( raw string ) string { return p. Sanitize ( raw) }
5.1 为什么选 Bluemonday?
AST 级解析 ,不会把畸形标签漏过。自带数十种 Policy(老富文本白名单、UGC 白名单、UGC+链接控制…)。 社区持续维护,最新版本 v1.0.27
已在 2025‑04‑22 打包进 Debian unstable 仓库。
5.2 离线环境的两种用法 ① Vendor 模式
go mod download github.com/microcosm-cc/bluemonday@v1.0.27
cp -r ~/go/pkg/mod/github.com/microcosm-cc/bluemonday@v1.0.27 ./vendor/github.com/microcosm-cc/
go env -w GOFLAGS = "-mod=vendor"
go build
② go.mod replace
require github. com/ microcosm- cc/ bluemonday v1. 0.27
replace github. com/ microcosm- cc/ bluemonday = > . . / bluemonday
6 性能与准确性 Benchmark
Data Size 方案 A 方案 B Bluemonday 10 KB 17 µs 21 µs 120 µs 1 MB 1.7 ms 1.9 ms 11.8 ms
Go 1.22, Mac M2, go test -bench .
,平均 5 次。 Bluemonday 牺牲 6–7× 速度换来精确、安全与维护性。 若只是内部日志解析,可选方案 A;开放上传/评论,用 Bluemonday 更保险。
7 最佳实践 Checklist
安全优先 :对任何用户可控输入,默认用 Bluemonday。先大后小 :先剥离 <script>/<style>
再删其他标签,避免脚本注入。实体解码 :html.UnescapeString
把 <
转回 <
,保持可读性。缓存 Policy :Bluemonday 创建 Policy 有一定开销,做成单例。并发 :三种方案均为纯函数,可安全在多个 Goroutine 并发调用。版本跟进 :关注 Bluemonday releases 及 CVE 通报 (GO‑2025‑3503 等)。
8 总结
一行正则 :极简场合、非常规 HTML 不可控 → 慎用。状态机 + 正则 :服务端批处理、边缘节点 → 在无外部依赖场景下的最佳折中。Bluemonday :面对外部用户输入、安全要求高 → 首选,并可按需裁剪 Policy。
用好这三把「瑞士军刀」,你的 Go 服务就能在安全与性能之间找到最合适的平衡,为日志清洗、全文检索、富文本展示保驾护航。祝编码愉快!