package helper import ( "fwh-toolbox-server/domain" "fwh-toolbox-server/dto" ) // sanitizeStrong 尝试对已知的强类型做源头脱敏(优化 A)。 // // 返回: // - 脱敏后的数据副本 + true:命中强类型,已完成脱敏。 // - nil + false:未命中强类型,调用方应改走流式脱敏(优化 B)。 // // 设计约束:所有脱敏均作用于输入数据的副本,绝不修改上游对象(这些对象 // 可能来自 Mongo 缓存或被其他调用方共享)。规则与掩码函数复用 pii_rules.go, // 确保 A 与 B 的行为一致。 func sanitizeStrong(data any) (any, bool) { switch v := data.(type) { case *dto.SiteInfoDTO: if v == nil { return v, true } cp := *v sanitizeSiteInfoDTO(&cp) return &cp, true case dto.SiteInfoDTO: sanitizeSiteInfoDTO(&v) return v, true case *dto.ResPage[dto.SiteInfoDTO]: if v == nil { return v, true } cp := *v cp.Data = make([]dto.SiteInfoDTO, len(v.Data)) copy(cp.Data, v.Data) for i := range cp.Data { sanitizeSiteInfoDTO(&cp.Data[i]) } return &cp, true case *domain.SimCardInfo: if v == nil { return v, true } cp := *v sanitizeDomainSimCardInfo(&cp) return &cp, true case *dto.SimCardInfo: if v == nil { return v, true } cp := *v sanitizeDTOSimCardInfo(&cp) return &cp, true case *dto.ResPage[dto.SimCardInfo]: if v == nil { return v, true } cp := *v cp.Data = make([]dto.SimCardInfo, len(v.Data)) copy(cp.Data, v.Data) for i := range cp.Data { sanitizeDTOSimCardInfo(&cp.Data[i]) } return &cp, true case *dto.ResPage[domain.SimCardInfo]: if v == nil { return v, true } cp := *v cp.Data = make([]domain.SimCardInfo, len(v.Data)) copy(cp.Data, v.Data) for i := range cp.Data { sanitizeDomainSimCardInfo(&cp.Data[i]) } return &cp, true case *domain.SimCardMonthlyData: if v == nil { return v, true } cp := *v sanitizeSimCardMonthlyData(&cp) return &cp, true default: return nil, false } } // maskStrPtr 对 *string 字段就地脱敏(作用于副本的指针,需重新分配以免污染原值)。 func maskStrPtr(p **string, kind MaskKind) { if p == nil || *p == nil { return } masked := applyMaskString(kind, **p) *p = &masked } func maskStr(s *string, kind MaskKind) { if s == nil { return } *s = applyMaskString(kind, *s) } // sanitizeSiteInfoDTO 对站点信息做字段级脱敏。 // 保留:city / province / country(行政区)、iccId(业务标识)、sn、各类版本与状态字段。 func sanitizeSiteInfoDTO(s *dto.SiteInfoDTO) { // 姓名 maskStrPtr(&s.UserName, MaskName) maskStrPtr(&s.FirstName, MaskName) maskStrPtr(&s.LastName, MaskName) maskStrPtr(&s.AccountName, MaskName) maskStrPtr(&s.FinancierName, MaskName) // 手机号 —— 完全脱敏 maskStrPtr(&s.ContactNumber, MaskFull) maskStrPtr(&s.FinancierContractNumber, MaskFull) // 完整地址串 —— 完全脱敏 maskStrPtr(&s.Address, MaskFull) maskStrPtr(&s.DeviceAddress, MaskFull) maskStrPtr(&s.AddressSecondLast, MaskFull) // 经纬度 —— 完全脱敏(string 类型) s.Longitude = maskFull() s.Latitude = maskFull() // 账号 maskStrPtr(&s.Account, MaskAccount) maskStrPtr(&s.InstallerAccount, MaskAccount) // 设备/网络标识 —— 完全脱敏(保留 ICCID) maskStrPtr(&s.Mac, MaskFull) maskStrPtr(&s.DeviceIp, MaskFull) maskStrPtr(&s.GatewayIp, MaskFull) s.IMEI = maskFull() // s.ICCID 保留 } // sanitizeDomainSimCardInfo 对 domain.SimCardInfo 脱敏(保留 GICCID)。 func sanitizeDomainSimCardInfo(s *domain.SimCardInfo) { s.IMEI = maskFull() s.IMSI = maskFull() s.MSISDN = maskFull() // s.GICCID 保留 } // sanitizeDTOSimCardInfo 对 dto.SimCardInfo 脱敏(保留 GICCID、SN 列表)。 func sanitizeDTOSimCardInfo(s *dto.SimCardInfo) { s.IMEI = maskFull() s.IMSI = maskFull() s.MSISDN = maskFull() // s.GICCID / s.SN 保留 } // sanitizeSimCardMonthlyData 月用量数据本身不含 IMEI/IMSI/MSISDN,仅含 GICCID(simId) 与用量, // 无需脱敏;保留以显式声明该类型已被审阅。 func sanitizeSimCardMonthlyData(_ *domain.SimCardMonthlyData) {}