Files
rootandClaude Opus 4.7 79ac89b458 Initial commit
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-11-07 10:47:18 +08:00

84 lines
2.9 KiB
Go

package useragent
import (
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"unicode/utf8"
)
// SDKCommonHeader 由 H5/Flutter 将 SDK 完整公共参数转为 UTF-8 JSON,再用标准 Base64 编码后传入。
const SDKCommonHeader = "X-SDK-Common"
// 限制解析开销;正常 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
}