| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- package pii
- 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:])
- }
|