支付宝token获取
This commit is contained in:
		
							parent
							
								
									d9c3e7ad88
								
							
						
					
					
						commit
						d15e6d43be
					
				| 
						 | 
				
			
			@ -0,0 +1,74 @@
 | 
			
		|||
package alipay
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"gitea.cdlsxd.cn/BaseSystem/plugin/alipay/vo"
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const BaseUri = "https://openapi.alipay.com/gateway.do"
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	Version  = "1.0"
 | 
			
		||||
	Format   = "json"
 | 
			
		||||
	SignType = "RSA2"
 | 
			
		||||
	Charset  = "UTF-8"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Client struct {
 | 
			
		||||
	AppId        string // 应用ID
 | 
			
		||||
	Prk          string // 私钥
 | 
			
		||||
	PukPath      string // 验签公钥
 | 
			
		||||
	MchCertPath  string // 商户证书路径
 | 
			
		||||
	RootCertPath string // 根证书路径
 | 
			
		||||
	NotifyUrl    string // 回调地址
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Req interface {
 | 
			
		||||
	Validate() error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ Req = (*Param)(nil)
 | 
			
		||||
 | 
			
		||||
type Param struct {
 | 
			
		||||
	AlipayRootCertSn string `json:"alipay_root_cert_sn"`
 | 
			
		||||
	AppCertSn        string `json:"app_cert_sn"`
 | 
			
		||||
	AppId            string `json:"app_id" validate:"required"`
 | 
			
		||||
	Method           string `json:"method" validate:"required"`
 | 
			
		||||
	Format           string `json:"format" validate:"required"`
 | 
			
		||||
	Charset          string `json:"charset" validate:"required"`
 | 
			
		||||
	SignType         string `json:"sign_type" validate:"required"`
 | 
			
		||||
	Timestamp        string `json:"timestamp" validate:"required"`
 | 
			
		||||
	Version          string `json:"version" validate:"required"`
 | 
			
		||||
	BizContent       string `json:"biz_content"`
 | 
			
		||||
	Sign             string `json:"sign"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ErrorResponse struct {
 | 
			
		||||
	Code    vo.Code `json:"code"`
 | 
			
		||||
	Msg     string  `json:"msg"`
 | 
			
		||||
	SubCode string  `json:"sub_code"`
 | 
			
		||||
	SubMsg  string  `json:"sub_msg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Notify struct {
 | 
			
		||||
	Charset      string `json:"charset"`
 | 
			
		||||
	UtcTimestamp string `json:"utc_timestamp"`
 | 
			
		||||
	AppId        string `json:"app_id"`
 | 
			
		||||
	Version      string `json:"version"`
 | 
			
		||||
	SignType     string `json:"sign_type"`
 | 
			
		||||
	NotifyId     string `json:"notify_id"`
 | 
			
		||||
	MsgMethod    string `json:"msg_method"`
 | 
			
		||||
	Sign         string `json:"sign"`
 | 
			
		||||
	BizContent   string `json:"biz_content"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (req *Param) Validate() error {
 | 
			
		||||
	err := validator.New().Struct(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		for _, err = range err.(validator.ValidationErrors) {
 | 
			
		||||
			return fmt.Errorf("参数有误:" + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,43 @@
 | 
			
		|||
package token
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"gitea.cdlsxd.cn/BaseSystem/plugin/alipay"
 | 
			
		||||
	"gitea.cdlsxd.cn/BaseSystem/plugin/alipay/utils"
 | 
			
		||||
	"github.com/carlmjohnson/requests"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Token alipay.Client
 | 
			
		||||
 | 
			
		||||
func (t *Token) Client(ctx context.Context, code string) (*AlipayTokenResponse, error) {
 | 
			
		||||
	cert, err := utils.GetCert(t.MchCertPath, t.RootCertPath, t.PukPath, t.AppId)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	param := &Param{
 | 
			
		||||
		AlipayRootCertSn: cert.RootCertSN,
 | 
			
		||||
		AppCertSn:        cert.MchCertSN,
 | 
			
		||||
		AppId:            t.AppId,
 | 
			
		||||
		Method:           "alipay.system.oauth.token",
 | 
			
		||||
		Format:           alipay.Format,
 | 
			
		||||
		Charset:          alipay.Charset,
 | 
			
		||||
		SignType:         alipay.SignType,
 | 
			
		||||
		Timestamp:        time.Now().Format(time.DateTime),
 | 
			
		||||
		Version:          alipay.Version,
 | 
			
		||||
		Sign:             "",
 | 
			
		||||
		GrantType:        "authorization_code",
 | 
			
		||||
		Code:             code,
 | 
			
		||||
	}
 | 
			
		||||
	uv, err := utils.UrlValues(t.Prk, param)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var response AlipayTokenResponse
 | 
			
		||||
	err = requests.URL(alipay.BaseUri).Post().Params(uv).ToJSON(&response).Fetch(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("请求异常,msg:" + err.Error())
 | 
			
		||||
	}
 | 
			
		||||
	return &response, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
package token
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestToken_Client(t *testing.T) {
 | 
			
		||||
	d := Token{
 | 
			
		||||
		AppId:        "2021004100663111",
 | 
			
		||||
		Prk:          "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDbA+YuMp4JUVj6rjzgwGKNXWkEMGX/rinqkfyBZ6B6p8EKz8zgA+ypiJLOixD3GyKnUnAzx4waNRZHfdEu+l57kJFtd/ipfwtJ28aTi7TqtqEpqD+UPY4ourt2CuyCFxWsonS6dczqtTvfAVArTdbGJYY+kNNVR3WiXgGUhkUu8N7vEowU00RUQGNdSVMUs4FX+HlU3RnEoRc/xUhPiaLf0Bm/g9wG96kwyg/TZvkNU7PpMVRdXeLrVORn0qThs3VA4dqondF+O12iC1TK4TKYGzFYczGAUsfuurtDyCc2GMoE+hH2FR8U7amQOVuYZFkutTdqaqukWpFQOr8wLeMzAgMBAAECggEAD715/3v3y6ejA3EeQvDQpGRANeLckcGMlaUkRpCSAf6oawSALuFZUt3T3zAzae7zUJ8mHTKMKR4DmeO68utfevXq3bkvj87nmslGvjfeKrgxYPMMjrTV0KuK6XLjiH3sOtn6FaR9s6iOwvovLs2LT/ZGbZyu84QNOjwTVP9JXZQkBgMItdKf+U3H2Cjp7U/qXBt8/9yzVFklp1g1883DAty0lzmT27dJimGVGaPQ8vNxo81+ZUEJAn6GUTk0K/GwJfhPTU8hh8G90n2LTyskoMjGxQe9lXfCcS9DmWawEQL4WTctPrDYlnS/cjCVMS0KXIFuxRNf6qaMYDeywC8BgQKBgQDyf40dvmw34Rrb46+NLayQ9W5CJI/dYeRajpCjoOomq5QYhbXzUCpVfbtByeGMsg3zN58NNsZGhl5SU0GdEdoOlxCk+2Hey2yQYF4ugQm/dTd68Jgqi3yujigAbNYa1ZhL9t3FqouPY1dGiaxl+DFYdSMIrsVFXh2NbrPyqTk5IQKBgQDnNaH7LCcIUqg9H8Tsls+8GLzP2HwF3hdll8asEsF3K3HX6/Zlp4VnnEcIkAxLRL/L0o5akXrmA18ZwfoSguTPXV9va3G2GgIiJHgcytmGtQVvbpFKuPnCXKz+avxnfO0flJqyYEuHr/40jsGbMkk0Kr52/n3ivXZbBUlT+tkt0wKBgB0qLgSnxEgsMJjFl3V5SsncWrhlwU+02Evz3X1wevjPpe4VFr7+ozjI+F5/MztCpt7bj6t9LPeKbYmlLb0ASqN6k6vj9+9ds97hWDJrnoqCRHvqt8JWKFauDi2O6WksyzZHqIB/dG14WyTGpg9VfEnRPLdsnZksKo26BLZol9NBAoGBAMz7oMNljqlzVtLyMo2q2zuhFuySusoc79NTL4FpE3rK2qCbA5V2YvDL/bIau7uTlRNodmrXZgU84fidIE9/Gsq5tp26vVK8Vj3c5Vxpf1dNcCct+MQtoMjvjzP0uBgsCrKf9lLEytHed1ozYnRsrbgBWWF4GTWH0cG6uxsoX5mfAoGAfYXKZdyRU+Su4y4EnDLMXd320ar7PaeuY8aZU7V6UQEsaOj6H3O8JMEOuBrOVEhAP2EkAC8ayargSXTSOkN97pg88agKDwA6jh4N6TKAK8XMft81YPPliVwZMsAUqihSKQBnKZ7ssHHLGWWWp5vfkyb7Y7dIkZcPzB0X3q/jL58=",
 | 
			
		||||
		PukPath:      "/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayCertPublicKey_RSA2.crt",
 | 
			
		||||
		MchCertPath:  "/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt",
 | 
			
		||||
		RootCertPath: "/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt",
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := d.Client(context.Background(), "349feb5fb58e455da1b00a7748ceMD68")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		if resp.IsSuccess() {
 | 
			
		||||
			t.Logf("Response: %+v", resp.Response)
 | 
			
		||||
		} else {
 | 
			
		||||
			t.Errorf("ErrorResponse: %+v", resp.ErrorResponse)
 | 
			
		||||
			t.Errorf("ErrorResponse Msg: %+v", resp.ErrorResponse.Msg)
 | 
			
		||||
			t.Errorf("ErrorResponse SubCode: %+v", resp.ErrorResponse.SubCode)
 | 
			
		||||
			t.Errorf("ErrorResponse SubMsg: %+v", resp.ErrorResponse.SubMsg)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
package token
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"gitea.cdlsxd.cn/BaseSystem/plugin/alipay"
 | 
			
		||||
	"github.com/go-playground/validator/v10"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Param struct {
 | 
			
		||||
	AlipayRootCertSn string `json:"alipay_root_cert_sn"`
 | 
			
		||||
	AppCertSn        string `json:"app_cert_sn"`
 | 
			
		||||
	AppId            string `json:"app_id" validate:"required"`
 | 
			
		||||
	Method           string `json:"method" validate:"required"`
 | 
			
		||||
	Format           string `json:"format" validate:"required"`
 | 
			
		||||
	Charset          string `json:"charset" validate:"required"`
 | 
			
		||||
	SignType         string `json:"sign_type" validate:"required"`
 | 
			
		||||
	Timestamp        string `json:"timestamp" validate:"required"`
 | 
			
		||||
	Version          string `json:"version" validate:"required"`
 | 
			
		||||
	BizContent       string `json:"biz_content"`
 | 
			
		||||
	Sign             string `json:"sign"`
 | 
			
		||||
 | 
			
		||||
	GrantType string `json:"grant_type"`
 | 
			
		||||
	Code      string `json:"code"`
 | 
			
		||||
}
 | 
			
		||||
type AlipayTokenSuccessResponse struct {
 | 
			
		||||
	UserId       string `json:"user_id"`
 | 
			
		||||
	OpenId       string `json:"open_id"`
 | 
			
		||||
	AccessToken  string `json:"access_token"`
 | 
			
		||||
	ExpiresIn    string `json:"expires_in"`
 | 
			
		||||
	RefreshToken string `json:"refresh_token"`
 | 
			
		||||
	ReExpiresIn  string `json:"re_expires_in"`
 | 
			
		||||
	AuthStart    string `json:"auth_start"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AlipayTokenResponse struct {
 | 
			
		||||
	ErrorResponse *alipay.ErrorResponse       `json:"error_response"`
 | 
			
		||||
	Response      *AlipayTokenSuccessResponse `json:"alipay_system_oauth_token_response"`
 | 
			
		||||
	Sign          string                      `json:"sign"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (req *Param) Validate() error {
 | 
			
		||||
	err := validator.New().Struct(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		for _, err = range err.(validator.ValidationErrors) {
 | 
			
		||||
			return fmt.Errorf("参数有误:" + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *AlipayTokenResponse) IsSuccess() bool {
 | 
			
		||||
	return r.ErrorResponse == nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,67 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CertConfig struct {
 | 
			
		||||
	MchCertSN  string
 | 
			
		||||
	RootCertSN string
 | 
			
		||||
	PublicKey  string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type manager struct {
 | 
			
		||||
	once        sync.Once
 | 
			
		||||
	mutex       sync.RWMutex
 | 
			
		||||
	CertConfigs map[string]*CertConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var instance manager
 | 
			
		||||
 | 
			
		||||
func getCertConfig(appId string) *CertConfig {
 | 
			
		||||
	c, ok := instance.CertConfigs[appId]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func setCertConfig(appId string, certConfig *CertConfig) {
 | 
			
		||||
	instance.mutex.Lock()
 | 
			
		||||
	defer instance.mutex.Unlock()
 | 
			
		||||
	instance.CertConfigs[appId] = certConfig
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	instance.CertConfigs = make(map[string]*CertConfig)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetCert(mchCertPath, rootCertPath, PublicKeyPath, appId string) (*CertConfig, error) {
 | 
			
		||||
	if mchCertPath == "" || rootCertPath == "" || appId == "" {
 | 
			
		||||
		return nil, fmt.Errorf("mchCertPath or rootCertPath or appId is empty")
 | 
			
		||||
	}
 | 
			
		||||
	c := getCertConfig(appId)
 | 
			
		||||
	if c != nil {
 | 
			
		||||
		return nil, fmt.Errorf("appId %s already exists", appId)
 | 
			
		||||
	}
 | 
			
		||||
	mchCertSN, err := getMchCertSN(mchCertPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("get mchCertSN error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	rootCertSN, err := getRootCertSN(rootCertPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("get rootCertSN error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	publicKey, err := getPublicKey(PublicKeyPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("get publicKey error: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	c = &CertConfig{
 | 
			
		||||
		MchCertSN:  mchCertSN,
 | 
			
		||||
		RootCertSN: rootCertSN,
 | 
			
		||||
		PublicKey:  publicKey,
 | 
			
		||||
	}
 | 
			
		||||
	setCertConfig(appId, c)
 | 
			
		||||
	return c, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,53 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCertSN(t *testing.T) {
 | 
			
		||||
	ms, err := getMchCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		//78a57140055b8e7853b1576bcf763361
 | 
			
		||||
		//78a57140055b8e7853b1576bcf763361
 | 
			
		||||
		t.Logf("merchantCertSN:%s", ms)
 | 
			
		||||
	}
 | 
			
		||||
	rs, err := getRootCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		//687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6
 | 
			
		||||
		//687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6
 | 
			
		||||
		t.Logf("alipayRootCertSN:%s", rs)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMchCertSN(t *testing.T) {
 | 
			
		||||
	s, err := getMchCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/appCertPublicKey_2021004100663111.crt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		//78a57140055b8e7853b1576bcf763361
 | 
			
		||||
		t.Log(s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestRootCertSN(t *testing.T) {
 | 
			
		||||
	s, err := getRootCertSN("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayRootCert.crt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		t.Log(s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPublicKey(t *testing.T) {
 | 
			
		||||
	s, err := getPublicKey("/Users/lsxd/code/php/yxxt/market/config/alipaycash/alipayCertPublicKey_RSA2.crt")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Error(err)
 | 
			
		||||
	} else {
 | 
			
		||||
		//78a57140055b8e7853b1576bcf763361
 | 
			
		||||
		t.Log(s)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/md5"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// md5Hash 计算 MD5 哈希值
 | 
			
		||||
func md5Hash(s string) string {
 | 
			
		||||
	h := md5.New()
 | 
			
		||||
	h.Write([]byte(s))
 | 
			
		||||
	return hex.EncodeToString(h.Sum(nil))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// hex2dec 将字节数组转换为十六进制字符串
 | 
			
		||||
func hex2dec(hex string) string {
 | 
			
		||||
	var dec int
 | 
			
		||||
	fmt.Sscanf(hex, "%x", &dec)
 | 
			
		||||
	return fmt.Sprintf("%d", dec)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCert(certData []byte) (*x509.Certificate, error) {
 | 
			
		||||
	block, _ := pem.Decode(certData)
 | 
			
		||||
	if block == nil {
 | 
			
		||||
		log.Fatal("Failed to parse PEM block containing the cert")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cert, err := x509.ParseCertificate(block.Bytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to parse certificate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cert, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"log"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// getMchCertSN 提取证书序列号
 | 
			
		||||
func getMchCertSN(certPath string) (string, error) {
 | 
			
		||||
	certData, err := ioutil.ReadFile(certPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Failed to read the cert file: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cert, err := getCert(certData)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to parse certificate: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return md5Hash(cert.Issuer.ToRDNSequence().String() + cert.SerialNumber.String()), nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,51 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// 从证书文件中提取公钥
 | 
			
		||||
func getPublicKey(certPath string) (string, error) {
 | 
			
		||||
	// 读取证书文件内容
 | 
			
		||||
	certData, err := ioutil.ReadFile(certPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to read certificate file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 解码 PEM 编码的证书
 | 
			
		||||
	block, _ := pem.Decode(certData)
 | 
			
		||||
	if block == nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to decode PEM block")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 解析证书
 | 
			
		||||
	cert, err := x509.ParseCertificate(block.Bytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to parse certificate: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 获取公钥
 | 
			
		||||
	pubKey := cert.PublicKey
 | 
			
		||||
 | 
			
		||||
	// 转换公钥为 PEM 格式
 | 
			
		||||
	pubKeyBytes, err := x509.MarshalPKIXPublicKey(pubKey)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to marshal public key: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 创建 PEM 块
 | 
			
		||||
	pemBlock := &pem.Block{
 | 
			
		||||
		Type:  "PUBLIC KEY",
 | 
			
		||||
		Bytes: pubKeyBytes,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 编码为 PEM 格式字符串
 | 
			
		||||
	pemBytes := pem.EncodeToMemory(pemBlock)
 | 
			
		||||
	publicKey := strings.TrimSpace(string(pemBytes))
 | 
			
		||||
 | 
			
		||||
	return publicKey, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// getRootCertSN 计算证书的序列号并返回最终字符串
 | 
			
		||||
func getRootCertSN(certPath string) (string, error) {
 | 
			
		||||
	certData, err := ioutil.ReadFile(certPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to read certificate file: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	var sn string
 | 
			
		||||
	blocks := strings.Split(string(certData), "-----END CERTIFICATE-----")
 | 
			
		||||
	for _, blockStr := range blocks[:len(blocks)-1] {
 | 
			
		||||
		cert, err := getCert([]byte(strings.TrimSpace(blockStr) + "\n-----END CERTIFICATE-----"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		serialNumber := cert.SerialNumber.String()
 | 
			
		||||
		if strings.HasPrefix(serialNumber, "0x") {
 | 
			
		||||
			serialNumber = hex2dec(serialNumber[2:])
 | 
			
		||||
		}
 | 
			
		||||
		if cert.SignatureAlgorithm == x509.SHA1WithRSA || cert.SignatureAlgorithm == x509.SHA256WithRSA {
 | 
			
		||||
			hash := md5Hash(cert.Issuer.ToRDNSequence().String() + serialNumber)
 | 
			
		||||
			if sn == "" {
 | 
			
		||||
				sn = hash
 | 
			
		||||
			} else {
 | 
			
		||||
				sn += "_" + hash
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return sn, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,107 @@
 | 
			
		|||
package utils
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto"
 | 
			
		||||
	"crypto/rand"
 | 
			
		||||
	"crypto/rsa"
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/pem"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"gitea.cdlsxd.cn/BaseSystem/plugin/alipay"
 | 
			
		||||
	"gitea.cdlsxd.cn/BaseSystem/plugin/utils"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func UrlValues(prk string, req alipay.Req) (url.Values, error) {
 | 
			
		||||
	if err := req.Validate(); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var strToBeSigned strings.Builder
 | 
			
		||||
	uv := url.Values{}
 | 
			
		||||
	kvRows := utils.SortStructJsonTag(req)
 | 
			
		||||
	for _, kv := range kvRows {
 | 
			
		||||
		if kv.Key == "sign" || kv.Value == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		uv.Set(kv.Key, kv.Value)
 | 
			
		||||
		strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
 | 
			
		||||
	}
 | 
			
		||||
	s := strings.TrimRight(strToBeSigned.String(), "&")
 | 
			
		||||
	sign, err := Sign(s, []byte(utils.NewPrivate().Build(prk)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	uv.Set("sign", sign)
 | 
			
		||||
 | 
			
		||||
	return uv, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Sign(data string, privateKeyPEM []byte) (string, error) {
 | 
			
		||||
	block, _ := pem.Decode(privateKeyPEM)
 | 
			
		||||
	if block == nil {
 | 
			
		||||
		return "", errors.New("failed to parse PEM block containing the private key")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	privyKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to parse DER encoded private key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hashed := sha256.Sum256([]byte(data))
 | 
			
		||||
	signature, err := rsa.SignPKCS1v15(rand.Reader, privyKey.(*rsa.PrivateKey), crypto.SHA256, hashed[:])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "", fmt.Errorf("failed to sign: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return base64.StdEncoding.EncodeToString(signature), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Verify(n *alipay.Notify, publicKeyPEM string) (bool, error) {
 | 
			
		||||
	var strToBeSigned strings.Builder
 | 
			
		||||
	uv := url.Values{}
 | 
			
		||||
	kvRows := utils.SortStructJsonTag(n)
 | 
			
		||||
	for _, kv := range kvRows {
 | 
			
		||||
		if kv.Key == "sign" || kv.Key == "sign_type" || kv.Value == "" {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		uv.Set(kv.Key, kv.Value)
 | 
			
		||||
		strToBeSigned.WriteString(fmt.Sprintf("%s=%s&", kv.Key, kv.Value))
 | 
			
		||||
	}
 | 
			
		||||
	s := strings.TrimRight(strToBeSigned.String(), "&")
 | 
			
		||||
	return check(s, n.Sign, []byte(publicKeyPEM))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func check(data, signature string, publicKeyPEM []byte) (bool, error) {
 | 
			
		||||
	block, _ := pem.Decode(publicKeyPEM)
 | 
			
		||||
	if block == nil {
 | 
			
		||||
		return false, fmt.Errorf("failed to parse DER encoded public key")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pubKey, err := x509.ParsePKIXPublicKey(block.Bytes)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, fmt.Errorf("failed to parse DER encoded public key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rsaPubKey, ok := pubKey.(*rsa.PublicKey)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return false, fmt.Errorf("failed to parse DER encoded public key: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hashed := sha256.Sum256([]byte(data))
 | 
			
		||||
	sig, err := base64.StdEncoding.DecodeString(signature)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false, fmt.Errorf("failed to decode signature: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = rsa.VerifyPKCS1v15(rsaPubKey, crypto.SHA256, hashed[:], sig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println("signature verification error:", err)
 | 
			
		||||
		return false, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return true, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
package vo
 | 
			
		||||
 | 
			
		||||
type Code string
 | 
			
		||||
 | 
			
		||||
const CodeSuccess Code = "10000"
 | 
			
		||||
 | 
			
		||||
func (c Code) IsSuccess() bool {
 | 
			
		||||
	return c == CodeSuccess
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								go.mod
								
								
								
								
							
							
						
						
									
										10
									
								
								go.mod
								
								
								
								
							| 
						 | 
				
			
			@ -3,7 +3,7 @@ module gitea.cdlsxd.cn/BaseSystem/plugin
 | 
			
		|||
go 1.22.2
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/envoyproxy/protoc-gen-validate v1.0.4
 | 
			
		||||
	github.com/carlmjohnson/requests v0.24.2
 | 
			
		||||
	github.com/go-playground/validator/v10 v10.22.0
 | 
			
		||||
	github.com/hashicorp/go-plugin v1.6.1
 | 
			
		||||
	github.com/pkg/errors v0.9.1
 | 
			
		||||
| 
						 | 
				
			
			@ -24,9 +24,9 @@ require (
 | 
			
		|||
	github.com/mattn/go-isatty v0.0.10 // indirect
 | 
			
		||||
	github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 // indirect
 | 
			
		||||
	github.com/oklog/run v1.0.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.21.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.18.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.14.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.25.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.27.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.16.0 // indirect
 | 
			
		||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										20
									
								
								go.sum
								
								
								
								
							
							
						
						
									
										20
									
								
								go.sum
								
								
								
								
							| 
						 | 
				
			
			@ -1,9 +1,9 @@
 | 
			
		|||
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
 | 
			
		||||
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
 | 
			
		||||
github.com/carlmjohnson/requests v0.24.2 h1:JDakhAmTIKL/qL/1P7Kkc2INGBJIkIFP6xUeUmPzLso=
 | 
			
		||||
github.com/carlmjohnson/requests v0.24.2/go.mod h1:duYA/jDnyZ6f3xbcF5PpZ9N8clgopubP2nK5i6MVMhU=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
 | 
			
		||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
 | 
			
		||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 | 
			
		||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
			
		||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
 | 
			
		||||
| 
						 | 
				
			
			@ -46,16 +46,16 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 | 
			
		|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 | 
			
		||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 | 
			
		||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 | 
			
		||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
 | 
			
		||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
 | 
			
		||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
 | 
			
		||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 | 
			
		||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
 | 
			
		||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
 | 
			
		||||
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
 | 
			
		||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
 | 
			
		||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 | 
			
		||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
 | 
			
		||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
 | 
			
		||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
 | 
			
		||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
 | 
			
		||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
 | 
			
		||||
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue