go-sanitize 不适合字段级脱敏,仅支持字符串替换;推荐用 gofieldmask 或自定义 marshaljson,结合 struct tag(如 mask:"phone")实现按字段语义动态掩码,避免误用 json:"-" 删除字段。

如何用 go-sanitize 对结构体字段做脱敏格式化
直接结论:go-sanitize 不适合字段级格式化脱敏,它只做简单字符串替换,无法感知结构体字段语义。真正可用的是 gofieldmask 或手写 json.Marshaler 接口,但更推荐用 structtag + 自定义 marshal 方案。
常见错误是把脱敏当成“隐藏字段”,结果用了 json:"-" 导致整个字段丢弃,而实际需求往往是保留字段名、只模糊值(如手机号变 138****1234)。
-
json:"-"是删字段,不是脱敏;要脱敏必须走序列化控制 - 字段标签里加自定义 tag,比如
san:"phone",再在MarshalJSON里识别处理 - 避免在
Getters里做脱敏——会影响下游所有逻辑,且无法控制输出场景(日志/HTTP/API)
gofieldmask 怎么按规则动态脱敏指定字段
这个库本质是运行时反射 + tag 解析,支持按正则或白名单匹配字段名,并注入脱敏函数。它不改原始数据,只影响序列化输出,适合 HTTP handler 层统一拦截。
典型使用场景:API 返回前对 User 结构体的 Phone、IDCard 字段做掩码,但数据库层、日志打印仍用明文。
- 安装:
go get github.com/alexyu85/gofieldmask - 给字段加 tag:
Phone string `json:"phone" mask:"phone"` - 注册脱敏函数:
fieldmask.RegisterMaskFunc("phone", func(v interface{}) interface{} { return maskPhone(v.(string)) }) - 调用:
masked, _ := fieldmask.Mask(user, "phone,id_card"),返回新对象,原对象不变
自己写 MarshalJSON 脱敏时容易踩的三个坑
手动实现 json.Marshaler 看似灵活,但极易出错。最常被忽略的是嵌套结构体和指针字段的 nil 判断。
- 没处理
*string类型:直接v.(*string)会 panic,得先判nil再解引用 - 忽略嵌套结构体:如果
User.Profile是另一个 struct,它的字段不会自动继承外层脱敏逻辑,必须递归处理或显式调用其MarshalJSON - 循环引用没防护:比如
User.Manager指回同类型,不加 visited map 会导致栈溢出 - 别在
MarshalJSON里改原始字段值——这是副作用,破坏不可变性,后续其他序列化会拿到脏数据
为什么不用中间件统一脱敏,而要在结构体层控制
HTTP 中间件(比如 Gin 的 gin.Context.JSON hook)看似省事,但它无法区分同一结构体在不同接口中的脱敏粒度。比如用户列表接口只需掩码手机号,但用户详情页需展示完整邮箱——中间件做不到字段级条件开关。
真正可控的方式,是把脱敏规则绑定到字段本身,靠 tag 或类型系统表达意图,而不是靠请求路径或 header 判断。
- 用自定义类型替代基础类型:
type Phone string,然后为它实现MarshalJSON,比 tag 更安全 - 字段 tag 里带参数:
san:"email,keep_domain",解析后决定是否保留@xxx.com - 别依赖全局配置开关——不同微服务对同一字段的脱敏策略可能不同,硬编码策略会耦合
脱敏不是加个过滤器就完事,关键在规则怎么和数据模型对齐。字段含义变了,脱敏方式就得跟着变,这点很容易被当成“配置问题”忽略掉。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











