From 58bea0c09a13f3d3672335ed3b1a6025a3fd91e6 Mon Sep 17 00:00:00 2001 From: ayflying Date: Wed, 23 Jul 2025 15:43:15 +0800 Subject: [PATCH] =?UTF-8?q?=E6=99=9A=E4=B8=8Aoppo=E6=94=AF=E4=BB=98?= =?UTF-8?q?=E5=9B=9E=E8=B0=83=E4=B8=8E=E7=99=BB=E5=BD=95=E5=9B=9E=E8=B0=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 3 + go.sum | 7 +++ package/pay/oppo/login.go | 97 +++++++++++++++++++++++++++++++++ package/pay/oppo/oppo.go | 112 ++++++-------------------------------- 4 files changed, 124 insertions(+), 95 deletions(-) create mode 100644 package/pay/oppo/login.go diff --git a/go.mod b/go.mod index 512fe4e..f4b040f 100644 --- a/go.mod +++ b/go.mod @@ -47,6 +47,8 @@ require ( github.com/gorilla/websocket v1.5.3 // indirect github.com/grokify/html-strip-tags-go v0.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/haxqer/gofunc v0.0.0-20210609183449-586b0cce5fe4 // indirect + github.com/haxqer/xthird v0.0.0-20210703071732-ff4f4dbb6e5d // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/magiconair/properties v1.8.9 // indirect @@ -60,6 +62,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pelletier/go-toml v1.9.3 // indirect + github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect diff --git a/go.sum b/go.sum index a6a3f74..66540c1 100644 --- a/go.sum +++ b/go.sum @@ -225,6 +225,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/haxqer/gofunc v0.0.0-20210609183449-586b0cce5fe4 h1:QQOImHnvEkzqEbozZup+76WLv+W0QhO84GTLHppMY1M= +github.com/haxqer/gofunc v0.0.0-20210609183449-586b0cce5fe4/go.mod h1:XQaEUze5QCq3D9oenfHItzPQUD+NGg+0TDt1kJhJAAc= +github.com/haxqer/xthird v0.0.0-20210703071732-ff4f4dbb6e5d h1:e2qgvCNjMrp9fNtUQr3gQnK15B0C2CIUjPWMc7jZnbo= +github.com/haxqer/xthird v0.0.0-20210703071732-ff4f4dbb6e5d/go.mod h1:9avb74vS4djGUwtueA6V01grbpskLGisSy0jxFZIBY8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -296,6 +300,8 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -457,6 +463,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= diff --git a/package/pay/oppo/login.go b/package/pay/oppo/login.go new file mode 100644 index 0000000..cd347e6 --- /dev/null +++ b/package/pay/oppo/login.go @@ -0,0 +1,97 @@ +package oppo + +import ( + "context" + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/grand" + "io" + "net/url" + "strings" +) + +const host = "https://iopen.game.heytapmobi.com" + +// oppo参数类型 +type LoginType struct { + Token string `json:"token"` + Ssoid string `json:"ssoid"` + Channel int `json:"channel"` + AdId string `json:"adId"` +} + +//登录回复 +type LoginResType struct { + ResultCode string `json:"resultCode" dc:"响应码,成功为 200"` + ResultMsg string `json:"resultMsg" dc:"响应信息"` + LoginToken string `json:"loginToken" dc:"透传的token"` + Ssoid string `json:"ssoid" dc:"透传的ssoid"` + //AppKey string `json:"appKey" dc:"秘钥key,因隐私安全规范,该字段目前已不返回信息"` + UserName string `json:"userName" dc:"用户ssoid绑定的账户昵称"` + //Email string `json:"email" dc:"因隐私安全规范,该字段目前已不返回信息"` + //MobileNumber string `json:"mobileNumber" dc:"因隐私安全规范,该字段目前已不返回信息"` + //CreateTime string `json:"createTime" dc:"因隐私安全规范,该字段目前已不返回信息"` + UserStatus string `json:"userStatus" dc:"用户状态:NORMAL 表示正常"` +} + +func (p *OppoType) FileIdInfo(ctx context.Context, oauthToken string, ssoid string) (res *LoginResType, err error) { + url := host + "/sdkopen/user/fileIdInfo" + header := p.GetHeader(oauthToken) + getHtml, err := g.Client().Header(header).Get(ctx, url, g.Map{ + "token": oauthToken, + "fileId": ssoid, + }) + getRes := getHtml.ReadAllString() + gjson.DecodeTo(getRes, &res) + g.Log().Debugf(ctx, "当前登陆请求的:%v", res) + return + +} + +func (p *OppoType) GenParam(oauthToken, oauthTimestamp, oauthNonce string) string { + // 注意:拼接的顺序不能有改变,不然会导致联运方验签失败 + params := []string{ + "oauthConsumerKey=" + url.QueryEscape(p.AppKey), + "oauthToken=" + url.QueryEscape(oauthToken), + "oauthSignatureMethod=" + url.QueryEscape("HMAC-SHA1"), + "oauthTimestamp=" + url.QueryEscape(oauthTimestamp), + "oauthNonce=" + url.QueryEscape(oauthNonce), + "oauthVersion=" + url.QueryEscape("1.0"), + } + return strings.Join(params, "&") + "&" +} + +// 生成签名 +func (p *OppoType) GenOauthSignature(param string) string { + oauthSignatureKey := p.AppSecret + "&" + mac := hmac.New(sha1.New, []byte(oauthSignatureKey)) + io.WriteString(mac, param) + signature := base64.StdEncoding.EncodeToString(mac.Sum(nil)) + return url.QueryEscape(signature) +} + +func (p *OppoType) GetHeader(oauthToken string) (headers map[string]string) { + + // 没有做过 urlEncode 的 token,由游戏客户端调用 OPPO SDK 直接获取 + //oauthToken := "TICKET_Ajnxxxxx" + oauthTimestamp := gtime.Now().TimestampStr() + oauthNonce := grand.S(5) + + // 生成请求头参数和签名 + param := p.GenParam(oauthToken, oauthTimestamp, oauthNonce) + oauthSignature := p.GenOauthSignature(param) + + // 封装请求头 + headers = map[string]string{ + "param": param, + "oauthSignature": oauthSignature, + } + + //fmt.Println("游戏服务端登录鉴权请求头为:", headers) + + return +} diff --git a/package/pay/oppo/oppo.go b/package/pay/oppo/oppo.go index da204ed..7c41bec 100644 --- a/package/pay/oppo/oppo.go +++ b/package/pay/oppo/oppo.go @@ -2,118 +2,40 @@ package oppo import ( "context" - "crypto" - "crypto/rsa" - "crypto/sha1" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "fmt" - "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" - "net/http" - "strings" + "github.com/haxqer/xthird/oppo" ) // 跟充值平台通信的加密key //const PUBLIC_KEY = `dfsdfs` type OppoType struct { + AppId string `json:"app_id"` + AppKey string `json:"app_key"` + AppSecret string `json:"app_secret"` PublicKey string `json:"public_key"` } -func New(PublicKey string) *OppoType { +func New(cfg *OppoType) *OppoType { return &OppoType{ - PublicKey: PublicKey, + AppKey: cfg.AppKey, + AppSecret: cfg.AppSecret, + PublicKey: cfg.PublicKey, } } -func (p *OppoType) Verify(ctx context.Context, data map[string]string) error { +func (p *OppoType) Verify(ctx context.Context) (err error) { + // OPPO公钥. 在官方给的 demo 中. 无需修改,改了就验证不过 + oppoPublicKey := "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmreYIkPwVovKR8rLHWlFVw7YDfm9uQOJKL89Smt6ypXGVdrAKKl0wNYc3/jecAoPi2ylChfa2iRu5gunJyNmpWZzlCNRIau55fxGW0XEu553IiprOZcaw5OuYGlf60ga8QT6qToP0/dpiL/ZbmNUO9kUhosIjEu22uFgR+5cYyQIDAQAB" + //oppoPublicKey := p.PublicKey // 解析请求参数 - for k, v := range data { - if v == "" || v == "0" { - delete(data, k) - } - } - - //data["notifyId"] = getParam(r, "notifyId") - //data["partnerOrder"] = getParam(r, "partnerOrder") - //data["productName"] = getParam(r, "productName") - //data["productDesc"] = getParam(r, "productDesc") - //data["price"] = getParam(r, "price") - //data["count"] = getParam(r, "count") - //data["attach"] = getParam(r, "attach") - //data["sign"] = getParam(r, "sign") - - // 验证签名 - result, err := p.rsaVerify(data) + bodyMap, err := oppo.ParseNotifyToBodyMap(g.RequestFromCtx(ctx).Request) if err != nil { - //http.Error(w, "Verification error: "+err.Error(), http.StatusInternalServerError) - g.Log().Errorf(ctx, "Verification error: %v", err.Error()) - return err + // 解析失败, 处理错误逻辑 + return } - if result { - // TODO::验证成功,处理后续逻辑 - //fmt.Fprint(w, "Verification successful") - //g.Log().Errorf(ctx, "Verification error: %v", err.Error()) - } else { - // TODO::验证失败,处理后续逻辑 - //http.Error(w, "Verification failed", http.StatusBadRequest) - g.Log().Error(ctx, "Verification failed") - err = gerror.New("Verification failed") - } - return nil -} - -func (p *OppoType) getParam(r *http.Request, paramName string) string { - r.ParseForm() - if value := r.FormValue(paramName); value != "" { - return strings.TrimSpace(value) - } - return "" -} - -func (p *OppoType) rsaVerify(contents map[string]string) (bool, error) { - // 构建待签名字符串 - strContents := fmt.Sprintf("notifyId=%s&partnerOrder=%s&productName=%s&productDesc=%s&price=%s&count=%s&attach=%s", - contents["notifyId"], contents["partnerOrder"], contents["productName"], - contents["productDesc"], contents["price"], contents["count"], contents["attach"]) - - // 解析公钥 - publicKey := p.PublicKey - pemData := []byte("-----BEGIN PUBLIC KEY-----\n" + - strings.ReplaceAll(publicKey, " ", "\n") + - "\n-----END PUBLIC KEY-----") - - block, _ := pem.Decode(pemData) - if block == nil { - return false, fmt.Errorf("failed to decode PEM block") - } - - pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return false, err - } - - pubKey, ok := pubInterface.(*rsa.PublicKey) - if !ok { - return false, fmt.Errorf("public key is not an RSA public key") - } - - // 解码签名 - signature, err := base64.StdEncoding.DecodeString(contents["sign"]) - if err != nil { - return false, err - } - - // 计算内容的哈希值 - hash := sha1.New() - hash.Write([]byte(strContents)) - hashed := hash.Sum(nil) - - // 验证签名 - err = rsa.VerifyPKCS1v15(pubKey, crypto.SHA1, hashed, signature) - return err == nil, err + err = oppo.VerifySign(oppoPublicKey, bodyMap) + return }