package helper import ( "strings" ) // MaskChar 是脱敏时使用的掩码字符,默认为 '*'。 // 抽为包级变量以便在需要时全局自定义(例如改为 '•' 或 '#')。 var MaskChar = "*" // MaskKind 表示某个字段应采用的脱敏方式。 type MaskKind int const ( // MaskFull 完全脱敏:整个值替换为固定长度的掩码串(不泄露长度)。 // 适用于手机号、完整地址串、经纬度、IMEI/IMSI/MAC/IP 等。 MaskFull MaskKind = iota // MaskName 姓名脱敏:保留首字符,其余以掩码填充。 MaskName // MaskEmail 邮箱脱敏:保留本地部分首字符与完整域名。 MaskEmail // MaskAccount 账号脱敏:保留前 2 后 2,中间掩码。 MaskAccount ) // piiFieldRules 是敏感字段名到脱敏方式的映射。 // 匹配时统一将 JSON key 转为小写后查表(大小写不敏感精确匹配)。 // // 明确不脱敏(保留原值)的字段(不出现在此表中即视为保留): // - iccid / giccid:SIM 卡业务主键,作为查询标识符保留 // - city / province / country:行政区字段,地址保留到城市级 // - sn / giccid 等设备/业务标识 var piiFieldRules = map[string]MaskKind{ // 手机号 —— 完全脱敏 "contactnumber": MaskFull, "financiercontractnumber": MaskFull, "msisdn": MaskFull, "phone": MaskFull, "mobile": MaskFull, "tel": MaskFull, // 姓名 —— 保留首字符 "username": MaskName, "firstname": MaskName, "lastname": MaskName, "accountname": MaskName, "financiername": MaskName, "display_name": MaskName, "displayname": MaskName, // 邮箱 "email": MaskEmail, // 完整地址串 —— 完全脱敏(行政区字段 city/province/country 不在表中,保留) "address": MaskFull, "deviceaddress": MaskFull, "addresssecondlast": MaskFull, "gpsaddress": MaskFull, // 经纬度 —— 完全脱敏 "longitude": MaskFull, "latitude": MaskFull, "lng": MaskFull, "lat": MaskFull, // 账号 —— 前2后2 "account": MaskAccount, "installeraccount": MaskAccount, // 设备/网络标识 —— 完全脱敏(iccid/giccid 保留,不在表中) "imei": MaskFull, "imsi": MaskFull, "mac": MaskFull, "deviceip": MaskFull, "gatewayip": MaskFull, } // fullMask 固定长度掩码串,用于 MaskFull,避免通过长度泄露原值信息。 const fullMaskLen = 6 // maskKindFor 返回字段名对应的脱敏方式;ok 为 false 表示该字段无需脱敏。 func maskKindFor(key string) (MaskKind, bool) { k, ok := piiFieldRules[strings.ToLower(key)] return k, ok } // applyMaskString 对字符串值按指定方式脱敏。 func applyMaskString(kind MaskKind, s string) string { switch kind { case MaskName: return maskName(s) case MaskEmail: return maskEmail(s) case MaskAccount: return maskAccount(s) case MaskFull: fallthrough default: return maskFull() } } // maskFull 返回固定长度的掩码串。 func maskFull() string { return strings.Repeat(MaskChar, fullMaskLen) } // maskName 保留首字符(按 rune),其余固定 3 个掩码字符。 // 空字符串返回空。 func maskName(s string) string { if s == "" { return s } r := []rune(s) return string(r[0]) + strings.Repeat(MaskChar, 3) } // maskEmail 保留本地部分首字符 + 掩码 + 完整域名。 // 非法邮箱(无 @)退化为完全脱敏。 func maskEmail(s string) string { if s == "" { return s } at := strings.LastIndex(s, "@") if at <= 0 || at == len(s)-1 { return maskFull() } local := s[:at] domain := s[at:] // 含 @ lr := []rune(local) return string(lr[0]) + strings.Repeat(MaskChar, 3) + domain } // maskAccount 保留前 2 后 2,中间固定掩码;长度不足 5 时完全脱敏。 func maskAccount(s string) string { r := []rune(s) if len(r) < 5 { return maskFull() } return string(r[:2]) + strings.Repeat(MaskChar, 3) + string(r[len(r)-2:]) }