package useragent import ( "encoding/base64" "encoding/json" "fmt" "strings" "unicode/utf8" ) // 限制解析开销;正常 SDK 公共参数远小于此大小。 const maxSDKCommonHeaderSize = 16 * 1024 // SDKCommonFields 接收五项 SDK 公共字段及客户端 app_version,完整 fields 中的其他字段自动忽略。 // 业务 DevID、用户 ID、渠道等继续走原有业务参数,不能被 SDK 对象覆盖。 type SDKCommonFields struct { SID string `json:"sid" bson:"sid"` DeviceID string `json:"device_id" bson:"device_id"` Device string `json:"device" bson:"device"` DeviceFingerprint string `json:"device_fingerprint" bson:"device_fingerprint"` FPVersion string `json:"fp_version" bson:"fp_version"` AppVersion string `json:"app_version" bson:"app_version"` // 客户端版本随下单快照保存,支付回调不使用当前版本补值。 } // ParseSDKCommonHeader 缺少请求头返回 nil;格式错误交给调用方记录,不阻断业务请求。 // 不修剪、截断或推算字段值,SDK 指纹生成失败时也保留其空值。 func ParseSDKCommonHeader(header string) (*SDKCommonFields, error) { header = strings.TrimSpace(header) if header == "" { return nil, nil } if len(header) > maxSDKCommonHeaderSize { return nil, fmt.Errorf("SDK 公共参数请求头超过 %d 字节", maxSDKCommonHeaderSize) } data, err := base64.StdEncoding.DecodeString(header) if err != nil { return nil, fmt.Errorf("SDK 公共参数 Base64 解码失败: %w", err) } if !utf8.Valid(data) { return nil, fmt.Errorf("SDK 公共参数不是有效的 UTF-8") } var fields *SDKCommonFields if err := json.Unmarshal(data, &fields); err != nil { return nil, fmt.Errorf("SDK 公共参数 JSON 解析失败: %w", err) } if fields == nil { return nil, fmt.Errorf("SDK 公共参数必须是 JSON 对象,不能为 null") } return fields, nil } // Clone 下单时保存独立快照,避免请求对象后续变化影响支付事件。 func (fields *SDKCommonFields) Clone() *SDKCommonFields { if fields == nil { return nil } snapshot := *fields return &snapshot } // MissingFields 仅用于缺失观测;合法空值仍原样上报,不作为业务失败。 func (fields *SDKCommonFields) MissingFields() []string { if fields == nil { fields = &SDKCommonFields{} } var missing []string for _, field := range []struct{ name, value string }{ {"sid", fields.SID}, {"device_id", fields.DeviceID}, {"device", fields.Device}, {"device_fingerprint", fields.DeviceFingerprint}, {"fp_version", fields.FPVersion}, {"app_version", fields.AppVersion}, } { if strings.TrimSpace(field.value) == "" { missing = append(missing, field.name) } } return missing }