脱敏函数须先识别需脱敏字段,推荐用结构体标签(如sensitive:"true")结合反射实现通用性,但高频场景应预编译路径或代码生成;需处理指针、map、嵌套结构体及nil值,并按类型调用预编译正则规则脱敏手机号、身份证、邮箱等。

脱敏函数要先明确“哪些字段需要脱敏”
Go 里没有内置的脱敏逻辑,必须由你定义规则。常见做法是用结构体标签(如 json 或自定义标签如 sensitive)标记需脱敏字段。不靠标签硬编码字段名,否则新增字段就得改函数,维护成本高。
推荐用结构体标签 + 反射实现通用性,但要注意:反射性能略低,高频调用场景(如日志中间件)建议预编译字段路径或用代码生成。
- 支持
json:"user_name"和sensitive:"true"双标签组合,避免污染业务标签 - 若字段类型是
*string或map[string]interface{},需额外判空,否则nil解引用 panic - 嵌套结构体默认不递归脱敏,除非显式加标签——这点常被忽略,导致深层手机号没被处理
手机号、身份证、邮箱的标准脱敏模式怎么写
固定格式数据建议用正则 + 模板替换,别手写截取。比如手机号统一用 138****1234,身份证用 110101****000000,邮箱用 u***@example.com。硬编码索引容易出错(比如把 18 位身份证当 15 位处理)。
示例函数:MaskPhone(s string) 应先校验长度和数字格式,再替换中间 4 位;MaskIDCard(s string) 要区分 15/18 位,末尾 X 大小写也要保留。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
- 邮箱脱敏注意 @ 符号位置,不能简单取前 3 位——
abc.def@gmail.com的用户名部分是abc.def,不是abc - 正则建议用
regexp.MustCompile预编译,避免每次调用都 recompile - 中文姓名脱敏慎用“张*”模式——真实姓名可能单字(“王”)或三字(“欧阳修”),更稳妥是留首尾字,中间用 *,且需判断 rune 数而非 byte 数
如何安全地对 map[string]interface{} 做脱敏
API 日志或动态 JSON 数据常用 map[string]interface{},但它没有结构体标签可依赖,只能靠字段名白名单或路径匹配。硬写 if key == "phone" || key == "id_card" 不够灵活,也难扩展。
推荐方案:传入一个 map[string]func(string) string 映射,比如 map[string]func(string) string{"phone": MaskPhone, "email": MaskEmail}。这样既解耦,又支持不同字段用不同规则。
- 遇到嵌套 map(如
data["user"].(map[string]interface{}))必须递归处理,但要设深度限制(如 ≤5),防栈溢出 - value 是
float64(JSON 解析后数字默认类型)时,先fmt.Sprintf("%v", v)转成字符串再脱敏,否则正则匹配失败 - 注意
interface{}可能是nil,直接断言会 panic,务必先if v != nil
为什么不要在 defer 中做脱敏日志输出
有人习惯在函数入口打日志,然后用 defer 打脱敏后的日志,以为能“兜底”。但这是错的:如果函数中途 panic,defer 确实执行,但此时原始参数可能已被修改(比如指针字段被重置),脱敏结果失真;更糟的是,panic 时栈已损坏,反射访问结构体字段可能直接 crash。
- 脱敏必须在数据离开业务逻辑前完成,最佳时机是接收请求后、进入 handler 前(如 Gin 中间件)或写日志前一刻
- 如果必须用 defer,只用于清理资源,脱敏逻辑应放在明确可控的点,比如
log.Printf("req: %+v", MaskRequest(req)) - 敏感字段一旦进入内存,就存在被 dump 或调试器读取的风险——脱敏只是掩耳盗铃,真正要防的是日志落盘和网络传输,这点比函数写法重要得多
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










