go字段脱敏需手动实现,标准库无内置支持;自定义tag如sensitive:"true"须用小写+下划线命名,反射读取前需确保字段导出、传指针、非空tag,并按类型递归处理脱敏逻辑。

Go 语言里做字段脱敏,不能靠“标记一下就自动隐藏”,必须自己写逻辑读取 tag 并控制输出——标准库不提供脱敏语义,json 包的 json:"-" 只是跳过序列化,不是脱敏。
怎么定义脱敏用的自定义 tag
用反引号包裹、符合 key:"value" 格式即可,比如 `sensitive:"true"` 或 `mask:"phone"`。别用下划线或特殊符号当 key 名(如 sensitive_),Go 的 StructTag.Get() 对 key 是精确匹配的,大小写和拼写错一个字就返回空字符串。
- 推荐统一用小写字母 + 下划线,比如
mask、sensitive、hide - 值部分支持双引号包裹的任意字符串,例如
`mask:"id_card"`、`sensitive:"user_id"` - 避免在 value 里塞复杂结构(如
`mask:"type=phone;rule=last4"`),后续解析容易出错;真要分层,单独开一个 tag,比如`mask_type:"phone"` `mask_rule:"last4"`
反射读取 tag 时最容易 panic 的三个地方
不是所有结构体都能直接反射读 tag:字段没导出、传了值没传指针、类型不是 struct,任一条件不满足,NumField() 就返回 0 或直接 panic。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 确保字段首字母大写(导出字段),
field.PkgPath == ""才表示能被反射看到 - 必须用
reflect.ValueOf(&s).Elem(),传值(reflect.ValueOf(s))也能遍历,但嵌套结构体或指针字段会拿不到子字段 - 调用
field.Tag.Get("mask")前,先确认field.Tag != "",空字符串不等于没 tag,可能是字段根本没写反引号
脱敏逻辑不能只看 tag,还要结合字段值类型
同一个 mask:"phone",对 string 和 *string 处理方式不同;对 int64 身份证号得转成字符串再截取;对嵌套结构体得递归进到具体字段。
- 用
field.Type.Kind()判断基础类型:string、int、int64等可按规则脱敏;struct或ptr需要递归处理 - 对指针字段,先
.Elem()再判断是否为零值,否则IsNil()之前就 panic - 别在反射循环里直接修改原结构体字段值(比如把手机号替换成
***),除非你明确需要副作用;更安全的做法是构建新 map 或新结构体返回脱敏后数据
为什么不要手写正则或 strings.Split 解析 tag 值
field.Tag 是 reflect.StructTag 类型,不是普通字符串。它内部已按 RFC 规范处理引号、空格、转义,比如 `mask:"a\"b"` 里的 "a\"b",用 Get("mask") 返回的是 a"b,而手动 strings.Split 会卡在转义引号上。
- 错误示例:
strings.Split(string(field.Tag), " ")——field.Tag不能强转string,编译不过 - 更错示例:
regexp.MustCompile(`mask:"([^"]+)"`).FindStringSubmatch—— 遇到mask:"name:John Doe"这种带空格的值就失效 - 正确姿势:只用
field.Tag.Get("mask")拿原始值,后续按需用strings.Trim去引号、strings.SplitN(..., ",", 2)拆选项
真正麻烦的从来不是加 tag,而是字段类型不一致时脱敏逻辑要覆盖所有分支;比如 mask:"phone" 出现在 string、*string、[]byte、sql.NullString 上,每种都得单独判。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










