直接在业务逻辑中调用自定义脱敏函数是起点,但必须结合字段名、类型或结构体标签识别敏感字段,且函数应为窄接口、只处理已确认语义的数据,不作格式推断或修复,脱敏须覆盖JSON序列化、日志输出、HTTP响应流和配置加载四环节。

直接在业务逻辑里调用自定义脱敏函数是最简单、最可控的起点,但必须配合类型识别和字段上下文,否则极易误脱或漏脱。
为什么不能直接用 strings.ReplaceAll 或全局正则
常见错误是写个 redactPhone 函数,然后对整个日志字符串或 HTTP body 调用它。结果:"order_id": "123456789" 里的 123456789 被当成手机号替换成 "*** **** ***";"created_at": "2026-06-23T08:42:00Z" 中的数字也被扫进去。Go 没有运行时类型信息,纯字符串操作无法区分“这是身份证号”还是“这是时间戳”。
真正该被脱敏的,是明确知道语义的字段值,比如结构体里的 User.Phone、LogEntry.Password,而不是任意一段含数字的文本。
- 敏感字段必须靠字段名(如
"phone")、类型(如string且含@)、或结构体标签(如redact:"phone")来识别 - 脱敏函数只应接收已确认为敏感的数据,不负责“猜”
- 所有输入都应做长度和格式校验:空字符串、非标准手机号(如 11 位但开头不是 1)、邮箱无
@等,直接返回原值或报错,不强行掩码
redactPhone、redactEmail 这类函数该怎么写
它们不是通用工具,而是针对特定语义的窄接口。例如 redactPhone 只处理“确认为手机号”的字符串,不做格式推断。
示例:
func redactPhone(s string) string {
if len(s) != 11 || s[0] != '1' {
return s // 不符合基本手机号特征,不脱敏
}
return s[:3] + "****" + s[7:]
}
<p>func redactEmail(s string) string {
at := strings.LastIndex(s, "@")
if at <= 0 || at == len(s)-1 {
return s
}
user := s[:at]
if len(user) <= 2 {
return "*@" + s[at+1:]
}
return user[:1] + "***" + user[len(user)-1:] + s[at:]
}- 不接受
interface{}或any,强制传string,避免反射开销和类型误判 - 不尝试修复输入(如自动补 1、删空格),失败就原样返回——脱敏不能改变数据契约
- 不带 context 或配置参数,保持纯函数特性;开关逻辑(如是否启用脱敏)应由上层决定后传入
怎么把脱敏函数嵌入到结构体字段中
关键不是“在哪调用”,而是“谁来触发”。最稳妥的是在结构体序列化前统一注入,而不是每个 handler 里手动调 redactPhone(u.Phone)。
推荐两种方式:
- 用
json.Marshaler接口,在MarshalJSON方法内调用脱敏函数,并通过闭包捕获开关状态:redactEnabled := isRedactEnabled(),再对aux.Phone = redactPhone(u.Phone) - 用结构体标签 + 反射封装一层
Redact()方法,仅在日志构造、调试输出等少数场景调用;高频接口避免实时反射,可配合go:generate预生成
注意:json:",omitempty" 和脱敏值冲突——如果 redactPhone("") 返回空串,该字段会从 JSON 中消失。要么改用占位符(如 "*** **** ***"),要么去掉 omitempty。
容易被忽略的三个执行点
脱敏不是只发生在 JSON 响应里。以下三处不处理,等于白做:
-
fmt.Printf和slog.Info:struct tag 和json.Marshaler完全无效,必须实现fmt.Formatter或slog.Valuer - HTTP 响应流:Gin/Echo 的
ResponseWriter是单向写,中间件无法读取已写出的 body;必须包装 writer 并在Write([]byte)中流式解析 JSON token,不能等全量字节收完再处理 - 配置加载:Viper 解析 YAML/TOML 时,需用
mapstructure.DecodeHookFunc在字段赋值阶段拦截,比如匹配 key path 含"password"就替换为"[REDACTED]"
所有脱敏动作必须发生在数据离开 Go 运行时内存之前——不是“要不要显示”,而是“能不能让敏感值进入输出缓冲区”。

















