first commit
This commit is contained in:
166
pkg/partner/Apple/AppleClient.go
Normal file
166
pkg/partner/Apple/AppleClient.go
Normal file
@ -0,0 +1,166 @@
|
||||
package Apple
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"servicebase/pkg/common/HyTools"
|
||||
"time"
|
||||
|
||||
"github.com/anxpp/beego/logs"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
AppleServerApiUrl = "https://appleid.apple.com/auth/token"
|
||||
)
|
||||
|
||||
// 苹果登录
|
||||
type AppleClient struct {
|
||||
}
|
||||
|
||||
func NewAppleClient() *AppleClient {
|
||||
return &AppleClient{}
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
jwt.StandardClaims
|
||||
Iss string `json:"iss"`
|
||||
Aud string `json:"aud"`
|
||||
Exp int64 `json:"exp"`
|
||||
Iat int64 `json:"iat"`
|
||||
Sub string `json:"sub"`
|
||||
AtHash string `json:"at_hash"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified string `json:"email_verified"`
|
||||
AuthTime int64 `json:"auth_time"`
|
||||
NonceSupported bool `json:"nonce_supported"`
|
||||
}
|
||||
|
||||
type ResJwt struct {
|
||||
Token string `json:"access_token"`
|
||||
Type string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
IdToken string `json:"id_token"`
|
||||
}
|
||||
|
||||
// 验证code
|
||||
func (client *AppleClient) VerifyAppleAuthCode(authCode string) (appleUserId string, resultErr error) {
|
||||
|
||||
apiUrl := AppleServerApiUrl
|
||||
|
||||
clientId := viper.GetString("apple.IOSBundleId")
|
||||
|
||||
param := make(map[string]string, 0)
|
||||
param["client_id"] = clientId
|
||||
param["code"] = authCode
|
||||
param["grant_type"] = viper.GetString("apple.GrantType")
|
||||
|
||||
// 生成密钥
|
||||
secret := client.GenerateClientSecret()
|
||||
|
||||
if len(secret) == 0 {
|
||||
resultErr = errors.New("生成密钥出错")
|
||||
return
|
||||
}
|
||||
param["client_secret"] = secret
|
||||
|
||||
paramString := ""
|
||||
for k, v := range param {
|
||||
kv := k + "=" + v + "&"
|
||||
paramString += kv
|
||||
}
|
||||
|
||||
if len(paramString) > 0 {
|
||||
paramString = HyTools.Substr(paramString, 0, len(paramString)-1)
|
||||
}
|
||||
|
||||
logs.Info("signWithApple params:" + paramString)
|
||||
|
||||
headerMap := make(map[string]string, 0)
|
||||
headerMap["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"
|
||||
|
||||
result, err := HyTools.HttpDo(http.MethodPost, apiUrl, headerMap, paramString)
|
||||
if err != nil {
|
||||
logs.Error("苹果登录失败,请求API失败:" + err.Error())
|
||||
resultErr = err
|
||||
return
|
||||
}
|
||||
logs.Info("signWithApple result:" + result)
|
||||
var resultDTO ResJwt
|
||||
e := json.Unmarshal([]byte(result), &resultDTO)
|
||||
if e != nil {
|
||||
logs.Error("苹果登录失败,JSON:" + e.Error())
|
||||
resultErr = err
|
||||
return
|
||||
}
|
||||
token, _ := jwt.ParseWithClaims(resultDTO.IdToken, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v ", token.Header["alg"])
|
||||
}
|
||||
return token, nil
|
||||
})
|
||||
if claims, ok := token.Claims.(*Claims); ok {
|
||||
appleUserId = claims.Sub
|
||||
} else {
|
||||
resultErr = errors.New("jwt error, token not correct")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 生成jwt密钥
|
||||
func (client *AppleClient) GenerateClientSecret() (secret string) {
|
||||
|
||||
token := &jwt.Token{
|
||||
Header: map[string]interface{}{
|
||||
"alg": "ES256",
|
||||
"kid": viper.GetString("apple.KeyId"),
|
||||
},
|
||||
Claims: jwt.MapClaims{
|
||||
"iss": viper.GetString("apple.TeamId"),
|
||||
"iat": time.Now().Unix(),
|
||||
// constraint: exp - iat <= 180 days
|
||||
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||
"aud": "https://appleid.apple.com",
|
||||
"sub": viper.GetString("apple.IOSBundleId"),
|
||||
},
|
||||
Method: jwt.SigningMethodES256,
|
||||
}
|
||||
|
||||
ecdsaKey, _ := AuthKeyFromBytes([]byte(viper.GetString("apple.PrivateKey")))
|
||||
ss, _ := token.SignedString(ecdsaKey)
|
||||
|
||||
secret = ss
|
||||
return
|
||||
}
|
||||
|
||||
func AuthKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) {
|
||||
var err error
|
||||
|
||||
// Parse PEM block
|
||||
var block *pem.Block
|
||||
if block, _ = pem.Decode(key); block == nil {
|
||||
return nil, errors.New("token: AuthKey must be a valid .p8 PEM file")
|
||||
}
|
||||
|
||||
// Parse the key
|
||||
var parsedKey interface{}
|
||||
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pkey *ecdsa.PrivateKey
|
||||
var ok bool
|
||||
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
|
||||
return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
|
||||
}
|
||||
|
||||
return pkey, nil
|
||||
}
|
||||
32
pkg/partner/Apple/AppleClient_test.go
Normal file
32
pkg/partner/Apple/AppleClient_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package Apple
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/anxpp/beego/logs"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppleClient_VerifyAppleAuthCode(t *testing.T) {
|
||||
result := `{"access_token":"a43aca909b09c47e7ab17cf65f2f87d5a.0.mrwxs.NGHQpX4Dp1mHUeGiC7UF7A","token_type":"Bearer","expires_in":3600,"refresh_token":"r472107c8150844c0995604692008d3c5.0.mrwxs.WE3wQnwhQpVVX77-U6169A","id_token":"eyJraWQiOiI4NkQ4OEtmIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY24ubWVldGFsay5lbmVuIiwiZXhwIjoxNTk4MTA1NDE2LCJpYXQiOjE1OTgxMDQ4MTYsInN1YiI6IjAwMTY3Mi43NTQyNGFiMGRjYWM0YTM2YTkxMmI5NDNmMzg5MmQ4ZC4xMDUzIiwiYXRfaGFzaCI6IlUwVUM5QTdnRWc4cnFfOHVORWxocVEiLCJlbWFpbCI6IjgzNDA3NzA2NUBxcS5jb20iLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJhdXRoX3RpbWUiOjE1OTgxMDQ4MTMsIm5vbmNlX3N1cHBvcnRlZCI6dHJ1ZX0.G1GwHu5qWW1Hj8-E2BObM3BIwi0ntDeoX0ymHR2EufgXxWcBI7rePadO2vGqJIRFpzsoN7ixRT1-ChpvUT7X37izDqCMHRTsBgEEKqh15gvgB-siYfssBmojcJx4W_DcKx24-4Z_mi4_BejPU6_kstJrlOPzh33dOPiUZJcY0pAWpynaTPFR3WkdbLd7RB9N-s5Z9jY2VF2SQLA8kkTcdRZQsfagErGcawJK6zttkR8EwK3D0wo_cfXoJ9Q49fayzAwkyhqfMrAWZZVwzDDtdNq4dmXfnPxNG1pootRF4PZE1IkJKteN0262xp4XZEBuWsMBeUwwjHlpu4gr77Re9w"}`
|
||||
|
||||
var resultDTO ResJwt
|
||||
e := json.Unmarshal([]byte(result), &resultDTO)
|
||||
if e != nil {
|
||||
logs.Error("JSON:" + e.Error())
|
||||
return
|
||||
}
|
||||
secret := NewAppleClient().GenerateClientSecret()
|
||||
token, jwtError := jwt.ParseWithClaims(resultDTO.IdToken, &Claims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
// Don't forget to validate the alg is what you expect:
|
||||
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
|
||||
return nil, fmt.Errorf("Unexpected signing method: %v ", token.Header["alg"])
|
||||
}
|
||||
return []byte(secret), nil
|
||||
})
|
||||
logs.Info(jwtError)
|
||||
logs.Info(token.Valid)
|
||||
logs.Info(token.SigningString())
|
||||
logs.Info(token.Claims.(*Claims).Sub)
|
||||
}
|
||||
456
pkg/partner/WxPay/WxPayClient.go
Normal file
456
pkg/partner/WxPay/WxPayClient.go
Normal file
@ -0,0 +1,456 @@
|
||||
package WxPay
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"net/http"
|
||||
"servicebase/pkg/common"
|
||||
"servicebase/pkg/htools"
|
||||
"servicebase/pkg/log"
|
||||
"servicebase/pkg/partner/wxpay_utility"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/downloader"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/option"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/refunddomestic"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||||
|
||||
"github.com/anxpp/beego/logs"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
|
||||
)
|
||||
|
||||
const ()
|
||||
|
||||
type WxPayClient struct {
|
||||
AppId string
|
||||
MchId string
|
||||
Secret string
|
||||
CertificateSerialNo string
|
||||
MchAPIv3Key string
|
||||
PrivateKeyPath string
|
||||
}
|
||||
|
||||
// 支付对象
|
||||
type WxPayModel struct {
|
||||
DeviceInfo string // 自定义参数,可以为终端设备号
|
||||
Body string // 商品简单描述
|
||||
Detail string // 商品详细描述,对于使用单品优惠的商户,改字段必须按照规范上传,详见“单品优惠参数说明”
|
||||
Attach string // 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
|
||||
OutTradeNo string // 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。详见商户订单号
|
||||
TotalFee string // 订单总金额,单位为分,详见支付金额
|
||||
SpbillCreateIp string //APP和网页支付提交用户端ip,Native支付填调用微信支付API的机器IP
|
||||
NotifyUrl string // 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
|
||||
Openid string //trade_type=JSAPI时(即公众号支付),此参数必传,此参数为微信用户在商户对应appid下的唯一标识
|
||||
TradeType string // 交易类型 JSAPI=公众号支付 NATIVE=扫码支付 APP=APP支付 MWEB=H5支付
|
||||
}
|
||||
|
||||
// 统一下单返回结果对象
|
||||
type WxCreatePayOrderResult struct {
|
||||
ReturnCode string `xml:"return_code"`
|
||||
ReturnMsg string `xml:"return_msg"`
|
||||
AppId string `xml:"appid"`
|
||||
MchId string `xml:"mch_id"`
|
||||
NonceStr string `xml:"nonce_str"`
|
||||
ResultCode string `xml:"result_code"`
|
||||
Sign string `xml:"sign"`
|
||||
PrepayId string `xml:"prepay_id"`
|
||||
TradeType string `xml:"trade_type"`
|
||||
ErrCode string `xml:"err_code"`
|
||||
ErrCodeDes string `xml:"err_code_des"`
|
||||
MwebUrl string `xml:"mweb_url"`
|
||||
CodeUrl string `xml:"code_url"`
|
||||
}
|
||||
|
||||
type RefundModel struct {
|
||||
TransactionId string // wx支付单号
|
||||
OutRefundNo string // 退款单号,DD Game内部退款唯一单号
|
||||
Reason string // 退款原因 1、该退款原因参数的长度不得超过80个字节;2、当订单退款金额小于等于1元且为部分退款时,退款原因将不会在消息中体现。
|
||||
NotifyUrl string // 退款回调url
|
||||
RefundAmount int64 // 退款金额,单位是分
|
||||
PayAmount int64 // 原支付订单的金额
|
||||
}
|
||||
|
||||
func (r *RefundModel) CheckParam() error {
|
||||
if len(r.Reason) > 80 {
|
||||
return errors.New("该退款原因参数的长度不得超过80个字节")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 微信通知对象
|
||||
type WxNotifyDataModel struct {
|
||||
Appid string `xml:"appid" json:"appid"`
|
||||
BankType string `xml:"bank_type" json:"bank_type"`
|
||||
CashFee string `xml:"cash_fee" json:"cash_fee"`
|
||||
CouponCount string `xml:"coupon_count" json:"coupon_count"`
|
||||
CouponFee string `xml:"coupon_fee" json:"coupon_fee"`
|
||||
CouponFee0 string `xml:"coupon_fee_0" json:"coupon_fee_0"`
|
||||
CouponId0 string `xml:"coupon_id_0" json:"coupon_id_0"`
|
||||
CouponFee1 string `xml:"coupon_fee_1" json:"coupon_fee_1"`
|
||||
CouponId1 string `xml:"coupon_id_1" json:"coupon_id_1"`
|
||||
CouponFee2 string `xml:"coupon_fee_2" json:"coupon_fee_2"`
|
||||
CouponId2 string `xml:"coupon_id_2" json:"coupon_id_2"`
|
||||
FeeType string `xml:"fee_type" json:"fee_type"`
|
||||
IsSubscribe string `xml:"is_subscribe" json:"is_subscribe"`
|
||||
MchId string `xml:"mch_id" json:"mch_id"`
|
||||
NonceStr string `xml:"nonce_str" json:"nonce_str"`
|
||||
Openid string `xml:"openid" json:"openid"`
|
||||
OutTradeNo string `xml:"out_trade_no" json:"out_trade_no"`
|
||||
ResultCode string `xml:"result_code" json:"result_code"`
|
||||
ReturnCode string `xml:"return_code" json:"return_code"`
|
||||
Sign string `xml:"sign" json:"sign"`
|
||||
SignType string `xml:"sign_type" json:"sign_type"`
|
||||
TimeEnd string `xml:"time_end" json:"time_end"`
|
||||
TotalFee string `xml:"total_fee" json:"total_fee"`
|
||||
TradeType string `xml:"trade_type" json:"trade_type"`
|
||||
TransactionId string `xml:"transaction_id" json:"transaction_id"`
|
||||
Attach string `xml:"attach" json:"attach"`
|
||||
}
|
||||
|
||||
type Resource struct {
|
||||
Algorithm string `json:"algorithm"` // AEAD_AES_256_GCM
|
||||
OriginalType string `json:"original_type"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
AssociatedData string `json:"associated_data"`
|
||||
Nonce string `json:"nonce"`
|
||||
}
|
||||
|
||||
type WxRefundNotifyDataModel struct {
|
||||
MchID string `json:"mchid"`
|
||||
TransactionID string `json:"transaction_id"`
|
||||
OutTradeNo string `json:"out_trade_no"`
|
||||
RefundID string `json:"refund_id"`
|
||||
OutRefundNo string `json:"out_refund_no"`
|
||||
RefundStatus string `json:"refund_status"`
|
||||
SuccessTime time.Time `json:"success_time"`
|
||||
UserReceivedAccount string `json:"user_received_account"`
|
||||
Amount refunddomestic.Amount `json:"amount"`
|
||||
}
|
||||
|
||||
type RefundAmount struct {
|
||||
Total int `json:"total"`
|
||||
Refund int `json:"refund"`
|
||||
PayerTotal int `json:"payer_total"`
|
||||
PayerRefund int `json:"payer_refund"`
|
||||
}
|
||||
|
||||
func NewWxpayClient(appid, mch_id, secret string) *WxPayClient {
|
||||
return &WxPayClient{
|
||||
AppId: appid,
|
||||
MchId: mch_id,
|
||||
Secret: secret,
|
||||
CertificateSerialNo: viper.GetString("tencent.wxPay.CertificateSerialNo"),
|
||||
MchAPIv3Key: viper.GetString("tencent.wxPay.MchAPIv3Key"),
|
||||
PrivateKeyPath: viper.GetString("tencent.wxPay.PrivateKeyPath"),
|
||||
}
|
||||
}
|
||||
|
||||
// 生成微信支付参数
|
||||
func (client *WxPayClient) generatePayRequest(payModel WxPayModel) map[string]string {
|
||||
|
||||
result := make(map[string]string)
|
||||
result["appid"] = client.AppId //appID
|
||||
result["mch_id"] = client.MchId // 商户号
|
||||
result["device_info"] = payModel.DeviceInfo
|
||||
result["nonce_str"] = htools.GetRandomString(8) // 随机字符串
|
||||
result["sign_type"] = "MD5" // 签名类型,默认为MD5,支持HMAC-SHA256和MD5。
|
||||
result["body"] = payModel.Body
|
||||
result["detail"] = payModel.Detail
|
||||
result["attach"] = payModel.Attach
|
||||
result["out_trade_no"] = payModel.OutTradeNo
|
||||
result["fee_type"] = "CNY" // 符合ISO 4217标准的三位字母代码,默认人民币:CNY,详细列表请参见货币类型
|
||||
result["total_fee"] = payModel.TotalFee
|
||||
result["spbill_create_ip"] = payModel.SpbillCreateIp
|
||||
result["notify_url"] = payModel.NotifyUrl
|
||||
result["trade_type"] = payModel.TradeType // 取值如下:JSAPI,NATIVE,APP等,说明详见参数规定
|
||||
|
||||
// 公众号支付
|
||||
if payModel.TradeType == common.WEIXIN_PAY_TRADER_TYPE_GONGZHONGHAO {
|
||||
result["openid"] = payModel.Openid
|
||||
}
|
||||
|
||||
//H5支付
|
||||
if payModel.TradeType == common.WEIXIN_PAY_TRADER_TYPE_H5 {
|
||||
// 场景
|
||||
result["scene_info"] = `{"h5_info":{"type":"Wap","wap_url":"https://www.enen.tech","wap_name":"东东电竞"}}`
|
||||
}
|
||||
|
||||
// 生成签名
|
||||
result["sign"] = client.generateSign(result) // 签名
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
// 生成微信支付签名
|
||||
func (client *WxPayClient) generateSign(parameterMap map[string]string) string {
|
||||
|
||||
// 生成待签名字符串
|
||||
sb := getWaitSignString(parameterMap)
|
||||
|
||||
// 拼上key
|
||||
sb.Append("&key=" + client.Secret)
|
||||
|
||||
result := htools.StringToMD5(sb.ToString())
|
||||
|
||||
result = strings.ToUpper(result)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// map转待签名字符串 ,按key值asc升序,空值不参与计算
|
||||
func getWaitSignString(m map[string]string) *htools.StringBuilder {
|
||||
keys := make([]string, 0)
|
||||
|
||||
for key, val := range m {
|
||||
if key == "sign" {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(val) == 0 {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
// 按key升序
|
||||
sort.Strings(keys)
|
||||
|
||||
// 拼接字符串
|
||||
sb := htools.NewStringBuilder()
|
||||
|
||||
keysCount := len(keys)
|
||||
i := 0
|
||||
for _, value := range keys {
|
||||
k := value
|
||||
v := m[k]
|
||||
sb.Append(k + "=" + v)
|
||||
if i < keysCount-1 {
|
||||
sb.Append("&")
|
||||
}
|
||||
i++
|
||||
}
|
||||
return sb
|
||||
}
|
||||
|
||||
// 验证签名
|
||||
func (client *WxPayClient) CheckSign(result map[string]string) error {
|
||||
|
||||
inputSign, ok := result["sign"]
|
||||
|
||||
if !ok {
|
||||
return errors.New("待验证参数中缺少sign")
|
||||
}
|
||||
|
||||
calSign := client.generateSign(result)
|
||||
|
||||
if inputSign != calSign {
|
||||
return errors.New("签名验证失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (client *WxPayClient) CheckRefundNotifySign(request *http.Request) (*WxRefundNotifyDataModel, error) {
|
||||
|
||||
privateKey, err := utils.LoadPrivateKeyWithPath(client.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
// 1. 使用 `RegisterDownloaderWithPrivateKey` 注册下载器
|
||||
err = downloader.MgrInstance().RegisterDownloaderWithPrivateKey(ctx, privateKey, client.CertificateSerialNo, client.MchId, client.MchAPIv3Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 2. 获取商户号对应的微信支付平台证书访问器
|
||||
certificateVisitor := downloader.MgrInstance().GetCertificateVisitor(client.MchId)
|
||||
// 3. 使用证书访问器初始化 `notify.Handler`
|
||||
handler, err := notify.NewRSANotifyHandler(client.MchAPIv3Key, verifiers.NewSHA256WithRSAVerifier(certificateVisitor))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content := new(map[string]interface{})
|
||||
notifyReq, err := handler.ParseNotifyRequest(context.Background(), request, &content)
|
||||
// 如果验签未通过,或者解密失败
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var rst WxRefundNotifyDataModel
|
||||
log.InfoF("回调通知数据: %s", notifyReq.Resource.Plaintext)
|
||||
err = json.Unmarshal([]byte(notifyReq.Resource.Plaintext), &rst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.InfoF("notifyReq : %+v", notifyReq)
|
||||
return &rst, nil
|
||||
}
|
||||
|
||||
// 微信统一下单
|
||||
func (client *WxPayClient) CreatePayOrder(payModel WxPayModel) (resultMap map[string]string, resultErr error) {
|
||||
|
||||
url := "https://api.mch.weixin.qq.com/pay/unifiedorder"
|
||||
|
||||
payRequestMap := client.generatePayRequest(payModel)
|
||||
|
||||
payXml := htools.MapToXML(payRequestMap)
|
||||
result, err := htools.HttpPost(url, payXml)
|
||||
|
||||
if err != nil {
|
||||
logs.Error("统一下单失败:" + err.Error())
|
||||
resultErr = err
|
||||
return
|
||||
}
|
||||
|
||||
logs.Info("统一下单OK:%s, params: %+v", result, payModel)
|
||||
|
||||
var resultModel WxCreatePayOrderResult
|
||||
|
||||
err2 := xml.Unmarshal([]byte(result), &resultModel)
|
||||
|
||||
if err2 != nil {
|
||||
logs.Error("统一下单解析返回结果失败:" + err2.Error())
|
||||
resultErr = err2
|
||||
return
|
||||
}
|
||||
|
||||
if resultModel.ReturnCode != "SUCCESS" {
|
||||
logs.Error("统一下单返回失败:" + resultModel.ReturnCode + "-" + resultModel.ReturnMsg)
|
||||
resultErr = errors.New("统一下单返回失败:" + resultModel.ReturnCode + "-" + resultModel.ReturnMsg)
|
||||
return
|
||||
}
|
||||
|
||||
if resultModel.ResultCode != "SUCCESS" {
|
||||
logs.Error("统一下单交易失败:" + resultModel.ResultCode + "-" + resultModel.ErrCode + resultModel.ErrCodeDes)
|
||||
resultErr = errors.New("统一下单交易失败:" + resultModel.ReturnCode + "-" + resultModel.ReturnMsg)
|
||||
return
|
||||
}
|
||||
|
||||
switch payModel.TradeType {
|
||||
case common.WEIXIN_PAY_TRADER_TYPE_GONGZHONGHAO:
|
||||
// 生成公众号需要的支付对象
|
||||
resultMap = client.GenerateGongZhongHaoPayMap(resultModel)
|
||||
return
|
||||
case common.WEIXIN_PAY_TRADER_TYPE_APP:
|
||||
// 生成app需要的支付对象
|
||||
resultMap = client.GenerateAppPayMap(resultModel)
|
||||
|
||||
return
|
||||
case common.WEIXIN_PAY_TRADER_TYPE_H5:
|
||||
// 生成app需要的支付对象
|
||||
resultMap = client.GenerateWxH5PayMap(resultModel)
|
||||
return
|
||||
case common.WEIXIN_PAY_TRADER_TYPE_NATIVE:
|
||||
// 生成native
|
||||
resultMap = client.GenerateWxNativePayMap(resultModel)
|
||||
return
|
||||
default:
|
||||
resultErr = errors.New("只支持APP和公众号支付")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 生成app的支付对象
|
||||
func (client *WxPayClient) GenerateAppPayMap(createPayOrderResult WxCreatePayOrderResult) map[string]string {
|
||||
|
||||
result := make(map[string]string)
|
||||
result["appid"] = createPayOrderResult.AppId
|
||||
result["partnerid"] = createPayOrderResult.MchId
|
||||
result["prepayid"] = createPayOrderResult.PrepayId
|
||||
result["package"] = "Sign=WXPay"
|
||||
result["noncestr"] = htools.GetRandomString(6)
|
||||
result["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
// 生成签名
|
||||
sign := client.generateSign(result)
|
||||
result["sign"] = sign
|
||||
result["pack"] = "Sign=WXPay" // java因为package是关键字 增加这个字段替代
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 生成公众号的支付对象
|
||||
func (client *WxPayClient) GenerateGongZhongHaoPayMap(createPayOrderResult WxCreatePayOrderResult) map[string]string {
|
||||
|
||||
result := make(map[string]string)
|
||||
result["appId"] = createPayOrderResult.AppId
|
||||
result["package"] = "prepay_id=" + createPayOrderResult.PrepayId
|
||||
result["nonceStr"] = htools.GetRandomString(6)
|
||||
result["timeStamp"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
result["signType"] = "MD5"
|
||||
// 生成签名
|
||||
sign := client.generateSign(result)
|
||||
result["paySign"] = sign
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 生成H5的支付对象
|
||||
func (client *WxPayClient) GenerateWxH5PayMap(createPayOrderResult WxCreatePayOrderResult) map[string]string {
|
||||
|
||||
result := make(map[string]string)
|
||||
result["appId"] = createPayOrderResult.AppId
|
||||
result["wxH5PayUrl"] = createPayOrderResult.MwebUrl
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 生成Native的支付对象
|
||||
func (client *WxPayClient) GenerateWxNativePayMap(createPayOrderResult WxCreatePayOrderResult) map[string]string {
|
||||
|
||||
result := make(map[string]string)
|
||||
result["codeUrl"] = createPayOrderResult.CodeUrl
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GenerateWxRefund 生成Native的支付对象
|
||||
func (client *WxPayClient) GenerateWxRefund(model *RefundModel) (*refunddomestic.Refund, error) {
|
||||
err := model.CheckParam()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mchPrivateKey, err := utils.LoadPrivateKeyWithPath(client.PrivateKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
// 使用商户私钥等初始化 client,并使它具有自动定时获取微信支付平台证书的能力
|
||||
opts := []core.ClientOption{
|
||||
option.WithWechatPayAutoAuthCipher(client.MchId, client.CertificateSerialNo, mchPrivateKey, client.MchAPIv3Key),
|
||||
}
|
||||
newClient, err := core.NewClient(ctx, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
svc := refunddomestic.RefundsApiService{
|
||||
Client: newClient,
|
||||
}
|
||||
refund, _, err := svc.Create(context.Background(), refunddomestic.CreateRequest{
|
||||
TransactionId: wxpay_utility.String(model.TransactionId),
|
||||
OutRefundNo: wxpay_utility.String(model.OutRefundNo),
|
||||
Reason: wxpay_utility.String(model.Reason),
|
||||
NotifyUrl: wxpay_utility.String(model.NotifyUrl),
|
||||
FundsAccount: refunddomestic.REQFUNDSACCOUNT_AVAILABLE.Ptr(),
|
||||
Amount: &refunddomestic.AmountReq{
|
||||
Refund: wxpay_utility.Int64(model.RefundAmount),
|
||||
Total: wxpay_utility.Int64(model.PayAmount),
|
||||
Currency: wxpay_utility.String("CNY"),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return refund, nil
|
||||
}
|
||||
122
pkg/partner/YiDun/YiDunClient.go
Normal file
122
pkg/partner/YiDun/YiDunClient.go
Normal file
@ -0,0 +1,122 @@
|
||||
package YiDun
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"servicebase/pkg/common/HyTools"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/anxpp/beego/logs"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const ()
|
||||
|
||||
// 易盾API 一键登录等
|
||||
type YiDunClient struct {
|
||||
SecretId string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
func NewYiDunClient() *YiDunClient {
|
||||
secretId := viper.GetString("netease.yidun.secretId")
|
||||
secretKey := viper.GetString("netease.yidun.secretKey")
|
||||
return &YiDunClient{SecretId: secretId, SecretKey: secretKey}
|
||||
}
|
||||
|
||||
// 生成易盾签名
|
||||
func (client *YiDunClient) GenerateSign(params map[string]string) (sign string) {
|
||||
|
||||
var keys []string
|
||||
for key, _ := range params {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
buf := bytes.NewBufferString("")
|
||||
for _, key := range keys {
|
||||
buf.WriteString(key + params[key])
|
||||
}
|
||||
buf.WriteString(client.SecretKey)
|
||||
has := md5.Sum(buf.Bytes())
|
||||
|
||||
sign = fmt.Sprintf("%x", has)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取易盾请求公共参数
|
||||
func (client *YiDunClient) GetCommonParams(businessId string) (params map[string]string) {
|
||||
|
||||
params = make(map[string]string, 0)
|
||||
params["secretId"] = client.SecretId
|
||||
params["businessId"] = businessId
|
||||
params["version"] = "v1"
|
||||
params["timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
params["nonce"] = HyTools.GetUUID()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 一键登录API
|
||||
func (client *YiDunClient) OneClickApi(params map[string]string) (mobile string, resultErr error) {
|
||||
|
||||
apiUrl := "https://ye.dun.163.com/v1/oneclick/check"
|
||||
|
||||
paramString := ""
|
||||
for k, v := range params {
|
||||
kv := k + "=" + v + "&"
|
||||
paramString += kv
|
||||
}
|
||||
|
||||
if len(paramString) > 0 {
|
||||
paramString = HyTools.Substr(paramString, 0, len(paramString)-1)
|
||||
}
|
||||
|
||||
logs.Info("oneClick params:" + paramString)
|
||||
|
||||
headerMap := make(map[string]string, 0)
|
||||
headerMap["Content-Type"] = "application/x-www-form-urlencoded;charset=utf-8"
|
||||
|
||||
result, err := HyTools.HttpDo(http.MethodPost, apiUrl, headerMap, paramString)
|
||||
logs.Info("oneClick result:" + result)
|
||||
|
||||
if err != nil {
|
||||
logs.Error("一键登录失败,请求API失败:" + err.Error())
|
||||
resultErr = err
|
||||
}
|
||||
|
||||
//{"code":200,"msg":"ok","data":{"phone":"13079217909","resultCode":"0"}}
|
||||
|
||||
var resultDTO struct {
|
||||
Code int64 `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data map[string]interface{} `json:"data"`
|
||||
}
|
||||
|
||||
err2 := json.Unmarshal([]byte(result), &resultDTO)
|
||||
|
||||
if err2 != nil {
|
||||
logs.Error("一键登录失败:解析API结果失败" + err2.Error())
|
||||
resultErr = err2
|
||||
return
|
||||
}
|
||||
|
||||
if resultDTO.Code != 200 {
|
||||
logs.Error("一键登录失败(%s) code[%v]:"+resultDTO.Msg, "", resultDTO.Code)
|
||||
resultErr = errors.New(resultDTO.Msg)
|
||||
return
|
||||
}
|
||||
|
||||
// 结果
|
||||
|
||||
if phone, ok := resultDTO.Data["phone"]; ok {
|
||||
mobile = phone.(string)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
212
pkg/partner/ZhimaCertify/ZhimaCertifyClient.go
Normal file
212
pkg/partner/ZhimaCertify/ZhimaCertifyClient.go
Normal file
@ -0,0 +1,212 @@
|
||||
package ZhimaCertify
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/url"
|
||||
"servicebase/pkg/htools"
|
||||
|
||||
"github.com/anxpp/beego/logs"
|
||||
)
|
||||
|
||||
// 芝麻认证SDK
|
||||
type ZhimaCertifyClient struct {
|
||||
GatewayUrl string
|
||||
AppId string
|
||||
PublicKey string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
// 芝麻开放平台的key
|
||||
const (
|
||||
// 蚂蚁开放平台 https://openapi.alipay.com/gateway.do
|
||||
// 芝麻信用api https://zmopenapi.zmxy.com.cn/openapi.do
|
||||
ZHIMA_MERCHANT_ID = "268282100002002118952271"
|
||||
ZHIMA_CERTIFY_GATEWAY_URL = "https://zmopenapi.zmxy.com.cn/openapi.do" // 网关
|
||||
|
||||
ZHIMA_CERTIFY_APP_ID = "30000229371" // APPID
|
||||
|
||||
// 芝麻公钥
|
||||
ZHIMA_CERTIFY_PUBLIC_KEY = `
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQK22BgQCNunjjn51b3XUnsTXGa6C3dBnA
|
||||
U7UP1FCxJN4T1CIxCC5I+Fi9lrzFy899g224JzcTywePp3jIg4aG/11qIJS2edkTUo
|
||||
7XM8qxpl+ApaRoCLx4NezDzOxVSGYqnu2295wnKW/ft3T+aZQwgELw60IrVutFMOKr1
|
||||
H7yrrzbar/OrgaOxLQIDAQAB
|
||||
-----END PUBLIC KEY-----
|
||||
`
|
||||
// 商户私钥
|
||||
ZHIMA_CERTIFY_PRIVATE_KEY = `
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIICWwIBAAKBgQDHbe15atuwmQaIUiX9kx45T22dRDmfYz7+rIoQCjR3+J2AQkMX1g3
|
||||
9BQZfRJyN8/31GD98zjZr0KowxFYnDJC422w+6mnAtY63fJW32VvIqTbz7PHF/iyVE0
|
||||
WCWu2IlukzljaZFg6SK2SD5+ROQEYL3dhzg87/4iDvDIBKuK222HftTMPvLcwIDAQAB
|
||||
AoGAREH0uM0BOnV1zezIAf17p8NitmyY3v22fr1RV+IWSUWzEWhryPdj289LegoCx3d
|
||||
OYbgV6sKtz8MQrTZWAnGDiynMe1yucNIRCsq1b0rCoMFGo52vFidYKH2SeGnRKQcMq
|
||||
6lohM+ialbwabDJW8eFBq6jqm+3KcIPyETmpcBLIawX0c21ECQQDotn2hHOTo0IHsx
|
||||
oxtwA3i8u0OL8dGbrOyBL5xYVRpxas2Sry9WcCLuDCC52VNKszQq+W2xTPriqvuv/V
|
||||
rBvrtGxLAkEA22LR0G9PNHGIg2K6+lcQWZ8F53cO32cSP3pKfO6A2m2wzii2mPViU6
|
||||
ONlTJKAB3VlHn13XVxVcaFhdfwcxLARUeQJABq3eC+VH22l2FrR8RvsCpZV8lkKesZ
|
||||
4gb3jsmoChz/MkwndFNU330+c2ijs84Mces8qLQC22h+du0DZky2QyNPSruwJAPpeB
|
||||
GSDIzkOeGS4v/oCcj5VJT/Kt8SPFkFDw0UgyjCY22u9GuXk5JnfBS1HfvwzEBOqO9T
|
||||
IutbRAICGt3ob5CEsQJABLSmUBbUmbILI0ymD9AFmX11t1mijZFnC1X22I5kR4TM511
|
||||
Oe3j6CFhvyXOg0L9jXKMxcD7GJN73PkglcVypxbzqg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
`
|
||||
|
||||
ZHIMA_CERTIFY_CHARSET = "UTF-8" // 编码
|
||||
ZHIMA_CERTIFY_PRODUCT_CODE = "w1010100000000002978" // 固定值
|
||||
)
|
||||
|
||||
func NewZhimaCertifyClient() *ZhimaCertifyClient {
|
||||
return &ZhimaCertifyClient{GatewayUrl: ZHIMA_CERTIFY_GATEWAY_URL, AppId: ZHIMA_CERTIFY_APP_ID, PublicKey: ZHIMA_CERTIFY_PUBLIC_KEY, PrivateKey: ZHIMA_CERTIFY_PRIVATE_KEY}
|
||||
}
|
||||
|
||||
// 芝麻认证系统参数
|
||||
/*
|
||||
每个接口文档中详细定义了接口业务参数列表,业务参数本身是通过系统参数params发送到服务器端的。这里主要描述这些业务参数是以什么格式组合,并拼装成了系统参数params的。假设接口A定义了两个业务参数,param1和param2,值分别为param1_value和param2_value。系统参数params和业务参数的关系为:params= URLEncode (Base64(RSAEncrypt(param1=URLEncode(param1_value) & param2=URLEncode(param2_value))))。
|
||||
首先对于每个业务参数,先进行一次URLEncode,然后按照URL中传递参数的方式把param1和param2拼接起来(使用&和=号)
|
||||
然后对拼接起来的业务参数进行RSA的加密,得到byte数组
|
||||
然后对RSA加密后得到byte数组进行Base64编码
|
||||
最后对Base64编码后的params参数做一次URLEncode,URLEncode后的值最终组成了系统参数params的值
|
||||
*/
|
||||
|
||||
// 芝麻认证初始化
|
||||
func (s *ZhimaCertifyClient) ZhimaCertifyInit(certifyId, certNo, trueName string) (result string, err error) {
|
||||
|
||||
// 方法名
|
||||
method := "zhima.customer.certification.initialize"
|
||||
// 业务参数
|
||||
certMap := make(map[string]string, 0)
|
||||
certMap["identity_type"] = "CERT_INFO"
|
||||
certMap["cert_type"] = "IDENTITY_CARD"
|
||||
certMap["cert_name"] = trueName
|
||||
certMap["cert_no"] = certNo
|
||||
|
||||
certMapByte, _ := json.Marshal(certMap)
|
||||
|
||||
bizMap := make(map[string]string, 0)
|
||||
bizMap["transaction_id"] = certifyId
|
||||
bizMap["product_code"] = ZHIMA_CERTIFY_PRODUCT_CODE
|
||||
bizMap["biz_code"] = "FACE_SDK"
|
||||
bizMap["identity_param"] = string(certMapByte)
|
||||
//bizMap["merchant_config"] = `{"need_user_authorization":"false"}`
|
||||
|
||||
// 请求参数
|
||||
requestParam := getCertifyRequestParams(method, bizMap)
|
||||
|
||||
headerMap := make(map[string]string, 0)
|
||||
|
||||
logs.Error("初始化芝麻信用API request:" + requestParam)
|
||||
|
||||
url := ZHIMA_CERTIFY_GATEWAY_URL + "?" + requestParam
|
||||
responseBody, httpErr := htools.HttpDo("GET", url, headerMap, "")
|
||||
|
||||
logs.Debug("初始化芝麻信用 response:" + responseBody)
|
||||
|
||||
if httpErr != nil {
|
||||
err = httpErr
|
||||
logs.Error("初始化芝麻信用失败:" + httpErr.Error())
|
||||
return
|
||||
}
|
||||
|
||||
result = responseBody
|
||||
return
|
||||
}
|
||||
|
||||
// 芝麻认证查询结果
|
||||
func (s *ZhimaCertifyClient) ZhimaCertifyQueryResult(bizNo string) (result string, err error) {
|
||||
// 方法名
|
||||
method := "zhima.customer.certification.query"
|
||||
// 业务参数
|
||||
bizMap := map[string]string{"biz_no": bizNo}
|
||||
|
||||
// 请求参数
|
||||
requestParam := getCertifyRequestParams(method, bizMap)
|
||||
|
||||
if len(requestParam) == 0 {
|
||||
logs.Info("请求参数为空")
|
||||
err = errors.New("请求参数错误")
|
||||
return
|
||||
}
|
||||
headerMap := make(map[string]string, 0)
|
||||
url := ZHIMA_CERTIFY_GATEWAY_URL + "?" + requestParam
|
||||
responseBody, httpErr := htools.HttpDo("GET", url, headerMap, "")
|
||||
|
||||
if httpErr != nil {
|
||||
err = httpErr
|
||||
return
|
||||
}
|
||||
|
||||
result = responseBody
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// 获取芝麻认证请求参数
|
||||
func getCertifyRequestParams(method string, bizMap map[string]string) string {
|
||||
|
||||
// 系统参数转请求
|
||||
sysMap := make(map[string]string, 0)
|
||||
sysMap["app_id"] = url.QueryEscape(ZHIMA_CERTIFY_APP_ID)
|
||||
sysMap["charset"] = url.QueryEscape(ZHIMA_CERTIFY_CHARSET)
|
||||
sysMap["method"] = url.QueryEscape(method)
|
||||
sysMap["platform"] = url.QueryEscape("zmop")
|
||||
sysMap["version"] = url.QueryEscape("1.0")
|
||||
|
||||
// 待签名的参数
|
||||
waitSignParam := getNotEncryptBizParam(bizMap)
|
||||
|
||||
logs.Error("待签名参数:" + waitSignParam)
|
||||
// 签名
|
||||
sign, signErr := htools.RsaSHA1Sign(waitSignParam, ZHIMA_CERTIFY_PRIVATE_KEY)
|
||||
|
||||
logs.Error("签名:" + sign)
|
||||
|
||||
if signErr != nil {
|
||||
logs.Error("签名失败" + signErr.Error())
|
||||
return ""
|
||||
}
|
||||
sysMap["sign"] = url.QueryEscape(sign)
|
||||
|
||||
// 获取加密过的业务参数
|
||||
params := getCertifyBizParams(bizMap)
|
||||
sysMap["params"] = params
|
||||
logs.Error("加密过的业务参数:" + params)
|
||||
|
||||
sysParams := htools.MapToUrlParams(sysMap)
|
||||
|
||||
return sysParams
|
||||
}
|
||||
|
||||
// 获取芝麻认证业务参数
|
||||
func getCertifyBizParams(bizMap map[string]string) string {
|
||||
|
||||
//1. 首先对于每个业务参数,先进行一次 URLEncode,然后按照 URL 中传递参数的方式把 param1 和 param2 拼接起来(使用 & 和 = 号)然后对拼接起来的业务参数进行 RSA 的加密,得到 byte 数组
|
||||
|
||||
urlParams := getNotEncryptBizParam(bizMap)
|
||||
//urlParams := "1"
|
||||
//RSA加密
|
||||
paramByte, _ := htools.RsaEncrypt(urlParams, ZHIMA_CERTIFY_PUBLIC_KEY)
|
||||
|
||||
//2. 然后对 RSA 加密后得到 byte 数组进行 Base64 编码
|
||||
base64Param := paramByte
|
||||
|
||||
//3. 最后对 Base64 编码后的 params 参数做一次 URLEncode,URLEncode 后的值最终组成了系统参数 params 的值
|
||||
|
||||
return url.QueryEscape(base64Param)
|
||||
}
|
||||
|
||||
// 获取未加密的业务参数
|
||||
func getNotEncryptBizParam(bizMap map[string]string) string {
|
||||
|
||||
mapData := make(map[string]string, 0)
|
||||
for k, v := range bizMap {
|
||||
mapData[k] = url.QueryEscape(v)
|
||||
}
|
||||
|
||||
urlParams := htools.MapToUrlParams(mapData)
|
||||
|
||||
return urlParams
|
||||
}
|
||||
129
pkg/partner/ali_auth/auth_client_do.go
Normal file
129
pkg/partner/ali_auth/auth_client_do.go
Normal file
@ -0,0 +1,129 @@
|
||||
package ali_auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
cloudauth "github.com/alibabacloud-go/cloudauth-20190307/v3/client"
|
||||
|
||||
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
)
|
||||
|
||||
func CreateClient(endpoint *string) (_result *cloudauth.Client, _err error) {
|
||||
// 初始化Client。
|
||||
config := &openapi.Config{
|
||||
// 使用Credential配置凭证。
|
||||
AccessKeyId: tea.String(viper.GetString("aliyun.sms.accessKeyId")),
|
||||
AccessKeySecret: tea.String(viper.GetString("aliyun.sms.accessKeySecret")),
|
||||
ConnectTimeout: tea.Int(3000),
|
||||
ReadTimeout: tea.Int(6000),
|
||||
// RegionId: tea.String("cn-shanghai"),
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
//_result = &cloudauth.Client{}
|
||||
_result, _err = cloudauth.NewClient(config)
|
||||
return _result, _err
|
||||
}
|
||||
|
||||
func getReturnUrl() string {
|
||||
return viper.GetString("aliyun.face-verify.returnUrl")
|
||||
}
|
||||
|
||||
func AliFaceVerify(token, userId, orderNo, metaInfo string) (res *cloudauth.InitFaceVerifyResponse, _err error) {
|
||||
request := &cloudauth.InitFaceVerifyRequest{
|
||||
SceneId: tea.Int64(1000014704),
|
||||
OuterOrderNo: tea.String(orderNo),
|
||||
ProductCode: tea.String("LR_FR"),
|
||||
Model: tea.String("LIVENESS"),
|
||||
// CertifyUrlType: tea.String("H5"),
|
||||
CertifyUrlStyle: tea.String("S"),
|
||||
CallbackToken: tea.String(orderNo),
|
||||
// LIVENESS(默认):眨眼 PHOTINUS_LIVENESS:眨眼 + 炫彩 MULTI_ACTION:眨眼 + 摇头 (眨眼和摇头顺序随机) MULTI_PHOTINUS:眨眼 + 摇头 (眨眼和摇头顺序随机)+ 炫彩活体检测。 MOVE_ACTION(推荐):远近移动 + 眨眼 (远近和眨眼顺序随机) MULTI_FAPTCHA:眨眼 + 形迹
|
||||
UserId: tea.String(userId),
|
||||
MetaInfo: tea.String(metaInfo),
|
||||
ReturnUrl: tea.String(fmt.Sprintf(getReturnUrl(), token, orderNo)),
|
||||
}
|
||||
client, _err := CreateClient(tea.String("cloudauth.aliyuncs.com"))
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
response, _err := client.InitFaceVerify(request)
|
||||
// 不支持服务自动路由。
|
||||
// response, _err := InitFaceVerify(tea.String("cloudauth.cn-shanghai.aliyuncs.com"), request)
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
// fmt.Println(*response.Body.RequestId)
|
||||
// fmt.Println(*response.Body.Code)
|
||||
// fmt.Println(*response.Body.Message)
|
||||
// fmt.Println(*response.Body.RequestId)
|
||||
// fmt.Println(*response.Body.ResultObject.CertifyId)
|
||||
// fmt.Println(*response.Body.ResultObject.CertifyUrl)
|
||||
return response, _err
|
||||
}
|
||||
|
||||
func AliFaceVerifyQuery(certifyId string) (res *cloudauth.DescribeFaceVerifyResponse, _err error) {
|
||||
client, _err := CreateClient(tea.String("cloudauth.aliyuncs.com"))
|
||||
if _err != nil {
|
||||
return nil, _err
|
||||
}
|
||||
return client.DescribeFaceVerify(&cloudauth.DescribeFaceVerifyRequest{
|
||||
SceneId: tea.Int64(1000014704),
|
||||
CertifyId: tea.String(certifyId),
|
||||
})
|
||||
}
|
||||
|
||||
func InitFaceVerifyAutoRoute(request *cloudauth.InitFaceVerifyRequest) (_result *cloudauth.InitFaceVerifyResponse, _err error) {
|
||||
endpoints := []*string{tea.String("cloudauth.aliyuncs.com"), tea.String("cloudauth-dualstack.aliyuncs.com")}
|
||||
var lastResponse *cloudauth.InitFaceVerifyResponse
|
||||
for _, endpoint := range endpoints {
|
||||
response, _err := InitFaceVerify(endpoint, request)
|
||||
lastResponse = response
|
||||
if _err != nil {
|
||||
var err = &tea.SDKError{}
|
||||
if _t, ok := _err.(*tea.SDKError); ok {
|
||||
err = _t
|
||||
// 系统异常,切换到下个地域调用。
|
||||
if *err.StatusCode == 500 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
return _result, _err
|
||||
}
|
||||
|
||||
if *response.StatusCode == 500 {
|
||||
continue
|
||||
}
|
||||
|
||||
if *response.Body.Code == "500" {
|
||||
continue
|
||||
}
|
||||
|
||||
_result = response
|
||||
return _result, _err
|
||||
|
||||
}
|
||||
_result = lastResponse
|
||||
return _result, _err
|
||||
}
|
||||
|
||||
func InitFaceVerify(endpoint *string, request *cloudauth.InitFaceVerifyRequest) (_result *cloudauth.InitFaceVerifyResponse, _err error) {
|
||||
client, _err := CreateClient(endpoint)
|
||||
if _err != nil {
|
||||
return _result, _err
|
||||
}
|
||||
// 创建RuntimeObject实例并设置运行参数。
|
||||
runtime := &util.RuntimeOptions{}
|
||||
runtime.ReadTimeout = tea.Int(10000)
|
||||
runtime.ConnectTimeout = tea.Int(10000)
|
||||
_result = &cloudauth.InitFaceVerifyResponse{}
|
||||
_body, _err := client.InitFaceVerifyWithOptions(request, runtime)
|
||||
if _err != nil {
|
||||
return _result, _err
|
||||
}
|
||||
_result = _body
|
||||
return _result, _err
|
||||
}
|
||||
150
pkg/partner/ali_auth/client.go
Normal file
150
pkg/partner/ali_auth/client.go
Normal file
@ -0,0 +1,150 @@
|
||||
package ali_auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"servicebase/pkg/log"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
url = "https://telvertify.market.alicloudapi.com/lianzhuo/telvertify"
|
||||
urlSimple = "https://checkone.market.alicloudapi.com/communication/personal/10101"
|
||||
urlSimpleV2 = "https://kzidcardv1.market.alicloudapi.com/api-mall/api/id_card/check"
|
||||
)
|
||||
|
||||
type AuthResult struct {
|
||||
Data struct {
|
||||
Areacode string `json:"areacode"`
|
||||
Isp string `json:"isp"`
|
||||
ID string `json:"id"`
|
||||
City string `json:"city"`
|
||||
Mobile string `json:"Mobile"`
|
||||
Name string `json:"Name"`
|
||||
Postcode string `json:"postcode"`
|
||||
} `json:"data"`
|
||||
Resp struct {
|
||||
Code string `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
} `json:"resp"`
|
||||
}
|
||||
|
||||
type AuthSimpleResult struct {
|
||||
// {"code":"10000","message":"成功","data":{"result":"1","birthday":"","address":"","sex":""},"seqNo":"YMX7AK4J250820134839851"}
|
||||
Code string `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
Result string `json:"result"`
|
||||
Birthday string `json:"birthday"`
|
||||
Address string `json:"address"`
|
||||
Sex string `json:"sex"`
|
||||
} `json:"data"`
|
||||
SeqNo string `json:"seqNo"`
|
||||
}
|
||||
|
||||
type AuthSimpleResultV2 struct {
|
||||
// {"code":"10000","message":"成功","data":{"result":"1","birthday":"","address":"","sex":""},"seqNo":"YMX7AK4J250820134839851"}
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data struct {
|
||||
OrderNo string `json:"orderNo"`
|
||||
Result int `json:"result"`
|
||||
Birthday string `json:"birthday"`
|
||||
Address string `json:"address"`
|
||||
Sex string `json:"sex"`
|
||||
Desc string `json:"desc"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func RealAuth(name, mobile, id string) (result AuthResult, e error) {
|
||||
if strings.HasPrefix(mobile, "1601234") {
|
||||
result = AuthResult{Data: struct {
|
||||
Areacode string `json:"areacode"`
|
||||
Isp string `json:"isp"`
|
||||
ID string `json:"id"`
|
||||
City string `json:"city"`
|
||||
Mobile string `json:"Mobile"`
|
||||
Name string `json:"Name"`
|
||||
Postcode string `json:"postcode"`
|
||||
}{
|
||||
Areacode: "",
|
||||
Isp: "",
|
||||
ID: id,
|
||||
City: "",
|
||||
Mobile: mobile,
|
||||
Name: name,
|
||||
Postcode: "",
|
||||
}, Resp: struct {
|
||||
Code string `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
}{
|
||||
Code: "0",
|
||||
Desc: "",
|
||||
}}
|
||||
return
|
||||
}
|
||||
client := resty.New()
|
||||
if _, e = client.R().
|
||||
SetHeader("Authorization", fmt.Sprintf("APPCODE %s", viper.GetString("aliyun.auth.appCode"))).
|
||||
SetQueryParams(map[string]string{
|
||||
"telnumber": mobile,
|
||||
"name": name,
|
||||
"id": id,
|
||||
}).
|
||||
SetResult(&result). // or SetResult(AuthSuccess{}).
|
||||
Get(url); e != nil {
|
||||
fmt.Printf("RealAuth error: %v", e)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func RealAuthSimple(name, id string) (result AuthSimpleResult, e error) {
|
||||
client := resty.New()
|
||||
var i *resty.Response
|
||||
if i, e = client.R().
|
||||
SetHeader("Authorization", fmt.Sprintf("APPCODE %s", viper.GetString("aliyun.auth.appCode"))).
|
||||
SetHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
|
||||
SetBody(fmt.Sprintf("name=%s&idcard=%s", name, id)).
|
||||
SetResult(&result). // or SetResult(AuthSuccess{}).
|
||||
Post(urlSimple); e != nil {
|
||||
fmt.Printf("RealAuthSimple error: %v", e)
|
||||
return
|
||||
} else if i != nil {
|
||||
fmt.Printf("RealAuthSimple success: %v", i.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RealAuthSimpleV2 https://market.aliyun.com/detail/cmapi00066570#sku=yuncode6057000002
|
||||
func RealAuthSimpleV2(name, id string) (*AuthSimpleResultV2, error) {
|
||||
var (
|
||||
result *AuthSimpleResultV2
|
||||
err error
|
||||
)
|
||||
client := resty.New()
|
||||
var i *resty.Response
|
||||
i, err = client.R().
|
||||
SetHeader("Authorization", fmt.Sprintf("APPCODE %s", viper.GetString("aliyun.auth.appCode"))).
|
||||
SetHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8").
|
||||
SetBody(fmt.Sprintf("name=%s&idcard=%s", name, id)).
|
||||
SetResult(&result). // or SetResult(AuthSuccess{}).
|
||||
Post(urlSimpleV2)
|
||||
if err != nil {
|
||||
fmt.Printf("RealAuthSimple error: %v", err)
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if i == nil {
|
||||
log.Error("身份信息二要素返回结果为nil")
|
||||
return nil, errors.New("身份信息二要素认证请求失败")
|
||||
}
|
||||
if i.StatusCode() != 200 {
|
||||
log.ErrorF("身份信息二要素返回http code (%d) not 200", i.StatusCode())
|
||||
return nil, errors.New("身份信息二要素认证请求失败")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
70
pkg/partner/ali_auth/client_test.go
Normal file
70
pkg/partner/ali_auth/client_test.go
Normal file
@ -0,0 +1,70 @@
|
||||
package ali_auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRealAuth(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
mobile string
|
||||
id string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult AuthResult
|
||||
}{
|
||||
{name: "t1", args: struct {
|
||||
name string
|
||||
mobile string
|
||||
id string
|
||||
}{name: "123", mobile: "", id: "234"}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotResult, e := RealAuth(tt.args.name, tt.args.mobile, tt.args.id)
|
||||
fmt.Printf("%++v %+v\n", gotResult, e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealAuthSimple(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
id string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantResult AuthSimpleResult
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "t1", args: struct {
|
||||
name string
|
||||
id string
|
||||
}{name: "", id: ""}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotResult, err := RealAuthSimple(tt.args.name, tt.args.id)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("RealAuthSimple() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotResult, tt.wantResult) {
|
||||
t.Errorf("RealAuthSimple() = %v, want %v", gotResult, tt.wantResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRealAuthSimpleV2(t *testing.T) {
|
||||
v2, err := RealAuthSimpleV2("", "")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
println(v2.Data.Address)
|
||||
}
|
||||
93
pkg/partner/mq/http/client.go
Normal file
93
pkg/partner/mq/http/client.go
Normal file
@ -0,0 +1,93 @@
|
||||
package mq_http
|
||||
|
||||
import (
|
||||
"servicebase/pkg/htools"
|
||||
"servicebase/pkg/partner/mq/message"
|
||||
"servicebase/pkg/partner/mq/pusher"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
dataHost string
|
||||
}
|
||||
|
||||
func init() {
|
||||
//rlog.SetLogger()
|
||||
}
|
||||
|
||||
func NewHttpClient() pusher.PushClient {
|
||||
return &Client{
|
||||
dataHost: viper.GetString("service.host.data"),
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) Push(tag, key string, message []byte) error {
|
||||
var e error
|
||||
switch tag {
|
||||
case "transaction":
|
||||
_, e = htools.HttpPost(fmt.Sprintf("%s%s", client.dataHost, "/api/subscriber/transaction"), string(message))
|
||||
case "transaction-update":
|
||||
_, e = htools.HttpPost(fmt.Sprintf("%s%s", client.dataHost, "/api/subscriber/transaction/update"), string(message))
|
||||
case "register":
|
||||
_, e = htools.HttpPost(fmt.Sprintf("%s%s", client.dataHost, "/api/subscriber/register"), string(message))
|
||||
case "event":
|
||||
_, e = htools.HttpPost(fmt.Sprintf("%s%s", client.dataHost, "/api/subscriber/event"), string(message))
|
||||
case "active":
|
||||
_, e = htools.HttpPost(fmt.Sprintf("%s%s", client.dataHost, "/api/subscriber/active"), string(message))
|
||||
case "error":
|
||||
_, e = htools.HttpPost(fmt.Sprintf("%s%s", client.dataHost, "/api/subscriber/error"), string(message))
|
||||
case "active-new":
|
||||
default:
|
||||
// "ignore"
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (client *Client) PushString(tag, key, message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 发布注册消息
|
||||
func (client *Client) PushRegisterMessage(message message.RegisterMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("register", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布事件消息
|
||||
func (client *Client) PushEventTopicMessage(message message.EventMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("event", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布交易消息
|
||||
func (client *Client) PushTransactionMessage(message message.TransactionMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("transaction", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布交易更新消息
|
||||
func (client *Client) PushTransactionUpdateMessage(message message.TransactionUpdateMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("transaction-update", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布活跃消息
|
||||
func (client *Client) PushActiveTopicMessage(message message.ActiveMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("active", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布错误消息
|
||||
func (client *Client) PushErrorTopicMessage(message message.ErrorMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("error", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布激活消息
|
||||
func (client *Client) PushActiveNewTopicMessage(message message.ActiveNewMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("active-new", message.MessageId, bytes)
|
||||
}
|
||||
5
pkg/partner/mq/message/message.go
Normal file
5
pkg/partner/mq/message/message.go
Normal file
@ -0,0 +1,5 @@
|
||||
package message
|
||||
|
||||
type Message struct {
|
||||
MessageId string
|
||||
}
|
||||
12
pkg/partner/mq/message/message_active.go
Normal file
12
pkg/partner/mq/message/message_active.go
Normal file
@ -0,0 +1,12 @@
|
||||
package message
|
||||
|
||||
// 活跃消息
|
||||
type ActiveMessage struct {
|
||||
Message
|
||||
UserId string // 用户ID
|
||||
DeviceId string // 设备ID
|
||||
ActiveTime string // 活跃的平台
|
||||
ActivePlatform string // 活跃的平台
|
||||
ActiveDeviceModel string // 活跃的设备型号
|
||||
ActiveIp string // 活跃的IP
|
||||
}
|
||||
16
pkg/partner/mq/message/message_active_new.go
Normal file
16
pkg/partner/mq/message/message_active_new.go
Normal file
@ -0,0 +1,16 @@
|
||||
package message
|
||||
|
||||
// 激活消息
|
||||
type ActiveNewMessage struct {
|
||||
Message
|
||||
ClientIp string // 用户Ip
|
||||
ClientVersion string // 用户ID
|
||||
DeviceId string // 设备ID
|
||||
Platform string // 活跃的平台
|
||||
MarketChannel string // 活跃的平台
|
||||
DeviceModel string // 活跃的设备型号
|
||||
TimeStamp string // 活跃的IP
|
||||
BundleId string // 活跃的IP
|
||||
OsVersion string // 活跃的IP
|
||||
ApiVersionNum string // 活跃的IP
|
||||
}
|
||||
34
pkg/partner/mq/message/message_error.go
Normal file
34
pkg/partner/mq/message/message_error.go
Normal file
@ -0,0 +1,34 @@
|
||||
package message
|
||||
|
||||
// 错误消息(补偿)
|
||||
type ErrorMessage struct {
|
||||
Message
|
||||
Code string // 错误代码
|
||||
Content string // 错误内容(json)
|
||||
Tag ErrorTag // 消息标签:auto=自动处理 manual=手动处理 auto_to_manual=自动转手动
|
||||
}
|
||||
|
||||
type ErrorTag string
|
||||
|
||||
const (
|
||||
Auto ErrorTag = "AUTO"
|
||||
Manual ErrorTag = "MANUAL"
|
||||
AutoToManual ErrorTag = "AUTO_TO_MANUAL"
|
||||
)
|
||||
|
||||
// CMQ异常消息code
|
||||
const (
|
||||
// 注册云信失败 需要自动补偿注册
|
||||
CMQ_ERROR_CODE_SIGNUP_TO_YUNXIN_FAIL = "CMQ_E001"
|
||||
// 抽奖更新总奖池失败
|
||||
CMQ_ERROR_CODE_OPEN_BOX_UPDATE_DATA_FAIL = "CMQ_E002"
|
||||
// 系统警告
|
||||
CMQ_ERROR_CODE_SYSTEM_WARN = "CMQ_E003"
|
||||
)
|
||||
|
||||
// 系统警告
|
||||
type SystemWarnBody struct {
|
||||
ServiceName string
|
||||
ApiName string
|
||||
ErrMsg string
|
||||
}
|
||||
35
pkg/partner/mq/message/message_event.go
Normal file
35
pkg/partner/mq/message/message_event.go
Normal file
@ -0,0 +1,35 @@
|
||||
package message
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// 事件
|
||||
type EventMessage struct {
|
||||
Message
|
||||
Tag EventTag // 消息标签:EventTagUser=用户
|
||||
Flag EventFlag // 消息标签:EventFlagCreate=创建 EventFlagUpdate=更新 EventFlagSave=创建或更新 EventFlagDelete=删除
|
||||
EventId string // 事件ID
|
||||
EventContent interface{} // 事件内容
|
||||
}
|
||||
|
||||
type EventTag string
|
||||
|
||||
const (
|
||||
EventTagUser = "user"
|
||||
EventTagRoomInto EventTag = "into_room"
|
||||
)
|
||||
|
||||
type EventFlag string
|
||||
|
||||
const (
|
||||
EventFlagSave EventFlag = "save"
|
||||
EventFlagCreate EventFlag = "create"
|
||||
EventFlagUpdate EventFlag = "update"
|
||||
EventFlagDelete EventFlag = "delete"
|
||||
)
|
||||
|
||||
func (message *EventMessage) ToJson() string {
|
||||
b, _ := json.Marshal(message)
|
||||
return string(b)
|
||||
}
|
||||
12
pkg/partner/mq/message/message_register.go
Normal file
12
pkg/partner/mq/message/message_register.go
Normal file
@ -0,0 +1,12 @@
|
||||
package message
|
||||
|
||||
// 注册消息
|
||||
type RegisterMessage struct {
|
||||
Message
|
||||
Platform string // 设备平台
|
||||
UserId string // 用户ID
|
||||
DeviceId string // 设备ID
|
||||
RegisterTime string // 注册时间
|
||||
ChannelCode string // 渠道代码
|
||||
Ip string //ip
|
||||
}
|
||||
104
pkg/partner/mq/message/message_transaction.go
Normal file
104
pkg/partner/mq/message/message_transaction.go
Normal file
@ -0,0 +1,104 @@
|
||||
package message
|
||||
|
||||
// 交易消息
|
||||
type TransactionMessage struct {
|
||||
Message
|
||||
FromUserId string // 发起用户ID
|
||||
FromCurrency TransactionCurrencyEnum // 扣款类型
|
||||
FromAmount string // 扣款数量
|
||||
ToUserId string // 接收用户
|
||||
ToCurrency TransactionCurrencyEnum // 接收类型
|
||||
ToAmount string // 接收数量
|
||||
TransactionRate string // 交易抽成
|
||||
TransactionState TransactionStateEnum // 交易状态
|
||||
TransactionType TransactionTypeEnum // 充值、打赏、购买表情、购买守护、兑换、砸蛋、提现、订单等
|
||||
TransactionId string // 充值ID、打赏ID、购买表情ID、购买守护ID、兑换ID、砸蛋ID、提现ID、订单ID等
|
||||
TransactionCreateTime string // 交易创建时间
|
||||
TransactionCompleteTime string // 交易完成时间
|
||||
TransactionSubjectId string // 充值为产品ID、打赏为房间ID、购买表情为表情ID、购买守护为房间ID、兑换为配置ID、砸蛋为配置ID、提现为配置ID、订单为品类ID
|
||||
TransactionCode TransactionCodeEnum // 充值为充值渠道(支付宝H5、支付宝App、微信、运营赠送等)、打赏为打赏类型(单个、批量)、购买表情为空、购买守护为守护类型、兑换为空、砸蛋为中奖等级、提现为银行名称、订单为空
|
||||
TransactionAmount string // 交易金额(单位为扣款类型)
|
||||
TransactionAmountCoupon string // 交易优惠金额(单位为扣款类型)
|
||||
TransactionAmountCouponId string // 交易优惠凭证ID
|
||||
TransactionAmountPay string // 交易实际支付金额(单位为扣款类型)
|
||||
FromPlatform string // 操作用户的平台
|
||||
GuildId string // 俱乐部ID
|
||||
}
|
||||
|
||||
// TransactionUpdateMessage 交易更新消息
|
||||
type TransactionUpdateMessage struct {
|
||||
Message
|
||||
TransactionState TransactionStateEnum // 交易状态
|
||||
TransactionId string // 充值ID、打赏ID、购买表情ID、购买守护ID、兑换ID、砸蛋ID、提现ID、订单ID等
|
||||
TransactionCompleteTime string // 交易完成时间
|
||||
}
|
||||
|
||||
type TransactionCodeEnum string
|
||||
|
||||
const (
|
||||
RewardSimple TransactionCodeEnum = "SIMPLE" // 单个打赏
|
||||
RewardMulti TransactionCodeEnum = "MULTI" // 批量打赏
|
||||
RewardBagSimple TransactionCodeEnum = "BAG_SIMPLE" // 背包礼物单个打赏
|
||||
RewardBagMulti TransactionCodeEnum = "BAG_MULTI" // 背包礼物批量打赏
|
||||
|
||||
GuardLevel01 TransactionCodeEnum = "1" // 青铜守护
|
||||
GuardLevel02 TransactionCodeEnum = "2" // 白银守护
|
||||
GuardLevel03 TransactionCodeEnum = "3" // 黄金守护
|
||||
|
||||
Hunting1 TransactionCodeEnum = "1" // 单砸
|
||||
Hunting10 TransactionCodeEnum = "10" // 十砸
|
||||
Hunting100 TransactionCodeEnum = "100" // 百砸
|
||||
|
||||
RechargeAppAlipay TransactionCodeEnum = "AppAlipay" //app支付宝
|
||||
RechargeAppWeChat TransactionCodeEnum = "AppWeChat" //app微信
|
||||
RechargeAppIap TransactionCodeEnum = "AppIap" //appIap
|
||||
RechargeH5Alipay TransactionCodeEnum = "H5Alipay" //H5支付宝
|
||||
RechargeH5WeChatClub TransactionCodeEnum = "H5WeChatClub" //H5微信公众号
|
||||
RechargeOperation TransactionCodeEnum = "Operation" //Operation,后台充值
|
||||
RechargeH5WeChatWap TransactionCodeEnum = "H5WeChatWap" //H5网页支付
|
||||
)
|
||||
|
||||
type TransactionStateEnum string
|
||||
|
||||
const (
|
||||
CREATED TransactionStateEnum = "CREATED" // 已创建
|
||||
SUCCESS TransactionStateEnum = "SUCCESS" // 成功
|
||||
FAILED TransactionStateEnum = "FAILED" // 失败
|
||||
)
|
||||
|
||||
type TransactionCurrencyEnum string
|
||||
|
||||
const (
|
||||
DIAMOND TransactionCurrencyEnum = "DIAMOND" // 钻石
|
||||
NCoin TransactionCurrencyEnum = "GOLD" //N币
|
||||
CRYSTAL TransactionCurrencyEnum = "CRYSTAL" // 晶石
|
||||
MONEY TransactionCurrencyEnum = "MONEY" // 钱
|
||||
FRAGMENT TransactionCurrencyEnum = "FRAGMENT" // 碎片
|
||||
)
|
||||
|
||||
type TransactionTypeEnum string
|
||||
|
||||
const (
|
||||
RECHARGE TransactionTypeEnum = "RECHARGE" // 充值
|
||||
RechargeNCoin TransactionTypeEnum = "RECHARGE_N_COIN" // 充值N币
|
||||
REWARD TransactionTypeEnum = "REWARD" // 打赏
|
||||
REWARDNCoin TransactionTypeEnum = "REWARD_N_COIN" // 打赏
|
||||
GUARD TransactionTypeEnum = "GUARD" // 购买守护
|
||||
EMOJI TransactionTypeEnum = "EMOJI" // 购买表情
|
||||
OperationDiamond TransactionTypeEnum = "OPERATION_DIAMOND" // 手工加钻石
|
||||
OperationDiamondRetrieve TransactionTypeEnum = "OPERATION_DIAMOND_RETRIEVE" // 手工扣除钻石
|
||||
OperationCrystal TransactionTypeEnum = "OPERATION_CRYSTAL" // 手工加晶石
|
||||
OperationCrystalReduce TransactionTypeEnum = "OPERATION_CRYSTAL_REDUCE" // 手工扣除晶石
|
||||
EXCHANGE TransactionTypeEnum = "EXCHANGE" // 兑换
|
||||
EXCHANGENCoin TransactionTypeEnum = "EXCHANGE_N_COIN" // 兑换
|
||||
HUNTING TransactionTypeEnum = "HUNTING" // 星空寻宝
|
||||
WITHDRAW TransactionTypeEnum = "WITHDRAW" // 提现
|
||||
WithdrawNCoin TransactionTypeEnum = "WITHDRAW_N_COIN" // 提现 N币
|
||||
WithdrawTimely TransactionTypeEnum = "WITHDRAW_TIMELY" // 提现
|
||||
ORDER TransactionTypeEnum = "ORDER" // 订单
|
||||
ORDERNCoin TransactionTypeEnum = "ORDER_N_COIN" // 订单
|
||||
OrderH5 TransactionTypeEnum = "ORDER_H5" // 订单-H5
|
||||
OrderH5NCoin TransactionTypeEnum = "ORDER_H5_N_COIN" // 订单-H5
|
||||
LuckGift TransactionTypeEnum = "LUCK_GIFT" // 幸运礼物
|
||||
LuckGiftNCoin TransactionTypeEnum = "LUCK_GIFT_N_COIN" // 幸运礼物
|
||||
)
|
||||
625
pkg/partner/mq/message_client.go
Normal file
625
pkg/partner/mq/message_client.go
Normal file
@ -0,0 +1,625 @@
|
||||
package mq
|
||||
|
||||
import (
|
||||
"servicebase/pkg/common/HyTools"
|
||||
mq_http "servicebase/pkg/partner/mq/http"
|
||||
message2 "servicebase/pkg/partner/mq/message"
|
||||
"servicebase/pkg/partner/mq/pusher"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
client pusher.PushClient
|
||||
)
|
||||
|
||||
// MessageClient Message客户端
|
||||
type MessageClient struct {
|
||||
}
|
||||
|
||||
type MessageClientStarter struct {
|
||||
}
|
||||
|
||||
func (s *MessageClientStarter) Init() error {
|
||||
client = mq_http.NewHttpClient()
|
||||
return nil
|
||||
}
|
||||
|
||||
// PushRewardMessage 发布打赏消息
|
||||
func (*MessageClient) PushRewardMessage(from, to string, fromAmount, toAmount, createTime, rewardId, roomId, fromPlatform, guildId string, code message2.TransactionCodeEnum) (e error) {
|
||||
fromA, _ := strconv.Atoi(fromAmount)
|
||||
toA, _ := strconv.Atoi(toAmount)
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: from, FromCurrency: message2.DIAMOND, FromAmount: fromAmount,
|
||||
ToUserId: to, ToCurrency: message2.CRYSTAL, ToAmount: toAmount,
|
||||
TransactionRate: strconv.Itoa(fromA - toA),
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.REWARD,
|
||||
TransactionId: rewardId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: roomId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: fromAmount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: fromAmount,
|
||||
FromPlatform: fromPlatform,
|
||||
GuildId: guildId,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushRewardNCoinMessage 发布打赏消息
|
||||
func (*MessageClient) PushRewardNCoinMessage(from, to string, fromAmount, toAmount, createTime, rewardId, roomId, fromPlatform, guildId string, code message2.TransactionCodeEnum) (e error) {
|
||||
fromA, _ := strconv.Atoi(fromAmount)
|
||||
toA, _ := strconv.Atoi(toAmount)
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: from, FromCurrency: message2.DIAMOND, FromAmount: fromAmount,
|
||||
ToUserId: to, ToCurrency: message2.CRYSTAL, ToAmount: toAmount,
|
||||
TransactionRate: strconv.Itoa(fromA - toA),
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.REWARDNCoin,
|
||||
TransactionId: rewardId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: roomId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: fromAmount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: fromAmount,
|
||||
FromPlatform: fromPlatform,
|
||||
GuildId: guildId,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushGuardMessage 发布购买守护消息
|
||||
func (*MessageClient) PushGuardMessage(from, to string, fromAmount, toAmount, createTime, rewardId, roomId, fromPlatform string, code message2.TransactionCodeEnum) (e error) {
|
||||
fromA, _ := strconv.Atoi(fromAmount)
|
||||
toA, _ := strconv.Atoi(toAmount)
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: from, FromCurrency: message2.DIAMOND, FromAmount: fromAmount,
|
||||
ToUserId: to, ToCurrency: message2.CRYSTAL, ToAmount: toAmount,
|
||||
TransactionRate: strconv.Itoa(fromA - toA),
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.GUARD,
|
||||
TransactionId: rewardId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: roomId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: fromAmount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: fromAmount,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushExchangeMessage 发布兑换消息
|
||||
func (*MessageClient) PushExchangeMessage(userId, crystal, exchangeId, createTime, fromPlatform string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.CRYSTAL, FromAmount: crystal,
|
||||
ToUserId: userId, ToCurrency: message2.DIAMOND, ToAmount: crystal,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.EXCHANGE,
|
||||
TransactionId: exchangeId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: crystal,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: crystal,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushExchangeNCoinMessage 发布兑换消息
|
||||
func (*MessageClient) PushExchangeNCoinMessage(userId, crystal, exchangeId, createTime, fromPlatform string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.CRYSTAL, FromAmount: crystal,
|
||||
ToUserId: userId, ToCurrency: message2.DIAMOND, ToAmount: crystal,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.EXCHANGENCoin,
|
||||
TransactionId: exchangeId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: crystal,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: crystal,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushHuntingMessage 发布星空寻宝(砸蛋)消息
|
||||
func (*MessageClient) PushHuntingMessage(userId, pay, win, huntingId, solutionId, createTime, fromPlatform string, code message2.TransactionCodeEnum) (e error) {
|
||||
payAmount, _ := strconv.Atoi(pay)
|
||||
winAmount, _ := strconv.Atoi(win)
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.DIAMOND, FromAmount: pay,
|
||||
ToUserId: userId, ToCurrency: message2.DIAMOND, ToAmount: win,
|
||||
TransactionRate: strconv.Itoa(payAmount - winAmount),
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.HUNTING,
|
||||
TransactionId: huntingId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: solutionId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: pay,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: pay,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushLuckGiftMessage 发布幸运礼物消息
|
||||
func (*MessageClient) PushLuckGiftMessage(userId, toUserId, pay, win, luckId, solutionId, createTime, fromPlatform string, code message2.TransactionCodeEnum) (e error) {
|
||||
payAmount, _ := strconv.Atoi(pay)
|
||||
winAmount, _ := strconv.Atoi(win)
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.DIAMOND, FromAmount: pay,
|
||||
ToUserId: toUserId, ToCurrency: message2.DIAMOND, ToAmount: win,
|
||||
TransactionRate: strconv.Itoa(payAmount - winAmount),
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.LuckGift,
|
||||
TransactionId: luckId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: solutionId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: pay,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: pay,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushLuckGiftNCoinMessage 发布幸运礼物消息
|
||||
func (*MessageClient) PushLuckGiftNCoinMessage(userId, toUserId, pay, win, luckId, solutionId, createTime, fromPlatform string, code message2.TransactionCodeEnum) (e error) {
|
||||
payAmount, _ := strconv.Atoi(pay)
|
||||
winAmount, _ := strconv.Atoi(win)
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.DIAMOND, FromAmount: pay,
|
||||
ToUserId: toUserId, ToCurrency: message2.DIAMOND, ToAmount: win,
|
||||
TransactionRate: strconv.Itoa(payAmount - winAmount),
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.LuckGiftNCoin,
|
||||
TransactionId: luckId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: solutionId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: pay,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: pay,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushRechargeMessage 发布充值消息(还未到账)
|
||||
func (*MessageClient) PushRechargeMessage(userId, pay, get, rechargeId, productId, createTime, fromPlatform string, code message2.TransactionCodeEnum) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.MONEY, FromAmount: pay,
|
||||
ToUserId: userId, ToCurrency: message2.DIAMOND, ToAmount: get,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.RECHARGE,
|
||||
TransactionId: rechargeId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: productId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: pay,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: pay,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushRechargeNCoinMessage 发布充值N币消息(还未到账)
|
||||
func (*MessageClient) PushRechargeNCoinMessage(userId, pay, get, rechargeId, productId, createTime, fromPlatform string, code message2.TransactionCodeEnum) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.MONEY, FromAmount: pay,
|
||||
ToUserId: userId, ToCurrency: message2.NCoin, ToAmount: get,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.RechargeNCoin,
|
||||
TransactionId: rechargeId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: productId,
|
||||
TransactionCode: code,
|
||||
TransactionAmount: pay,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: pay,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOperationDiamondMessage 发布加钻石消息
|
||||
func (*MessageClient) PushOperationDiamondMessage(userId, amount, businessId, createTime string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.MONEY, FromAmount: "0",
|
||||
ToUserId: userId, ToCurrency: message2.DIAMOND, ToAmount: amount,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.OperationDiamond,
|
||||
TransactionId: businessId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: amount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: "0",
|
||||
FromPlatform: "OPERATION",
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOperationDiamondRetrieveMessage 发布钻石回收消息
|
||||
func (*MessageClient) PushOperationDiamondRetrieveMessage(userId, amount, businessId, createTime string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.MONEY, FromAmount: "0",
|
||||
ToUserId: userId, ToCurrency: message2.DIAMOND, ToAmount: amount,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.OperationDiamond,
|
||||
TransactionId: businessId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: amount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: "0",
|
||||
FromPlatform: "OPERATION",
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOperationCrystalMessage 发布加晶石消息
|
||||
func (*MessageClient) PushOperationCrystalMessage(userId, amount, businessId, createTime string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.MONEY, FromAmount: "0",
|
||||
ToUserId: userId, ToCurrency: message2.CRYSTAL, ToAmount: amount,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.OperationCrystal,
|
||||
TransactionId: businessId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: amount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: "0",
|
||||
FromPlatform: "OPERATION",
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOperationCrystalReduceMessage 发布扣除晶石消息
|
||||
func (*MessageClient) PushOperationCrystalReduceMessage(userId, amount, businessId, createTime string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.MONEY, FromAmount: "0",
|
||||
ToUserId: userId, ToCurrency: message2.CRYSTAL, ToAmount: amount,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.SUCCESS,
|
||||
TransactionType: message2.OperationCrystalReduce,
|
||||
TransactionId: businessId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: createTime,
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: amount,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: "0",
|
||||
FromPlatform: "OPERATION",
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushWithdrawMessage 发布提现消息(还未完成)
|
||||
func (*MessageClient) PushWithdrawMessage(userId, crystal, money, huntingId, createTime, fromPlatform string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.CRYSTAL, FromAmount: crystal,
|
||||
ToUserId: userId, ToCurrency: message2.MONEY, ToAmount: money,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.WITHDRAW,
|
||||
TransactionId: huntingId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: crystal,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: crystal,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushWithdrawNCoinMessage 发布N币提现消息(还未完成)
|
||||
func (*MessageClient) PushWithdrawNCoinMessage(userId, crystal, money, huntingId, createTime, fromPlatform string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.NCoin, FromAmount: crystal,
|
||||
ToUserId: userId, ToCurrency: message2.MONEY, ToAmount: money,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.WithdrawNCoin,
|
||||
TransactionId: huntingId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: crystal,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: crystal,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushWithdrawTimelyMessage 发布提现消息(还未完成)
|
||||
func (*MessageClient) PushWithdrawTimelyMessage(userId, crystal, money, rate, payMoney, huntingId, createTime, fromPlatform string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: userId, FromCurrency: message2.CRYSTAL, FromAmount: crystal,
|
||||
ToUserId: userId, ToCurrency: message2.MONEY, ToAmount: money,
|
||||
TransactionRate: rate,
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.WithdrawTimely,
|
||||
TransactionId: huntingId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: "",
|
||||
TransactionCode: "",
|
||||
TransactionAmount: crystal,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: crystal,
|
||||
FromPlatform: fromPlatform,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOrderMessage 发布订单消息(还未完成)下单用户、接单用户、支付钻石、到账晶石、订单id、技能id、创建时间、下单平台
|
||||
func (*MessageClient) PushOrderMessage(fromUserId, toUserId, diamond, crystal, orderId, skillId, createTime, fromPlatform, guildId string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: fromUserId, FromCurrency: message2.DIAMOND, FromAmount: diamond,
|
||||
ToUserId: toUserId, ToCurrency: message2.CRYSTAL, ToAmount: crystal,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.ORDER,
|
||||
TransactionId: orderId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: skillId,
|
||||
TransactionCode: "",
|
||||
TransactionAmount: diamond,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: diamond,
|
||||
FromPlatform: fromPlatform,
|
||||
GuildId: guildId,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOrderNCoinMessage 发布订单消息(还未完成)下单用户、接单用户、支付钻石、到账晶石、订单id、技能id、创建时间、下单平台
|
||||
func (*MessageClient) PushOrderNCoinMessage(fromUserId, toUserId, diamond, crystal, orderId, skillId, createTime, fromPlatform, guildId string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: fromUserId, FromCurrency: message2.DIAMOND, FromAmount: diamond,
|
||||
ToUserId: toUserId, ToCurrency: message2.CRYSTAL, ToAmount: crystal,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.ORDERNCoin,
|
||||
TransactionId: orderId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: skillId,
|
||||
TransactionCode: "",
|
||||
TransactionAmount: diamond,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: diamond,
|
||||
FromPlatform: fromPlatform,
|
||||
GuildId: guildId,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOrderMessageH5 发布订单消息(还未完成)下单用户、接单用户、支付钻石、到账晶石、订单id、技能id、创建时间、下单平台
|
||||
func (*MessageClient) PushOrderMessageH5(fromUserId, toUserId, diamond, crystal, orderId, skillId, createTime, fromPlatform, guildId, channel string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: fromUserId, FromCurrency: message2.DIAMOND, FromAmount: diamond,
|
||||
ToUserId: toUserId, ToCurrency: message2.CRYSTAL, ToAmount: crystal,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.OrderH5,
|
||||
TransactionId: orderId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: skillId,
|
||||
TransactionCode: message2.TransactionCodeEnum(channel),
|
||||
TransactionAmount: diamond,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: diamond,
|
||||
FromPlatform: fromPlatform,
|
||||
GuildId: guildId,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushOrderNCoinMessageH5 发布订单消息(还未完成)下单用户、接单用户、支付钻石、到账晶石、订单id、技能id、创建时间、下单平台
|
||||
func (*MessageClient) PushOrderNCoinMessageH5(fromUserId, toUserId, diamond, crystal, orderId, skillId, createTime, fromPlatform, guildId, channel string) (e error) {
|
||||
message := message2.TransactionMessage{
|
||||
FromUserId: fromUserId, FromCurrency: message2.DIAMOND, FromAmount: diamond,
|
||||
ToUserId: toUserId, ToCurrency: message2.CRYSTAL, ToAmount: crystal,
|
||||
TransactionRate: "0",
|
||||
TransactionState: message2.CREATED,
|
||||
TransactionType: message2.OrderH5NCoin,
|
||||
TransactionId: orderId,
|
||||
TransactionCreateTime: createTime,
|
||||
TransactionCompleteTime: "",
|
||||
TransactionSubjectId: skillId,
|
||||
TransactionCode: message2.TransactionCodeEnum(channel),
|
||||
TransactionAmount: diamond,
|
||||
TransactionAmountCoupon: "0",
|
||||
TransactionAmountCouponId: "",
|
||||
TransactionAmountPay: diamond,
|
||||
FromPlatform: fromPlatform,
|
||||
GuildId: guildId,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushTransactionCompleteMessage 发布交易更新消息(提现完成、充值到账等)
|
||||
func (*MessageClient) PushTransactionCompleteMessage(businessId, completeTime string, state message2.TransactionStateEnum) (e error) {
|
||||
if (state) != (message2.SUCCESS) && (state) != (message2.FAILED) {
|
||||
e = errors.New("交易状态只能是完成状态")
|
||||
return
|
||||
}
|
||||
message := message2.TransactionUpdateMessage{
|
||||
TransactionState: state,
|
||||
TransactionId: businessId,
|
||||
TransactionCompleteTime: completeTime,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushTransactionUpdateMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushActiveMessage 发布活跃消息
|
||||
func (*MessageClient) PushActiveMessage(userId, deviceId, time, platform, deviceModel, ip string) (e error) {
|
||||
if len(strings.Split(time, " ")) != 2 {
|
||||
e = errors.New("活跃时间格式错误!")
|
||||
return
|
||||
}
|
||||
message := message2.ActiveMessage{
|
||||
UserId: userId,
|
||||
DeviceId: deviceId,
|
||||
ActiveTime: time,
|
||||
ActivePlatform: platform,
|
||||
ActiveDeviceModel: deviceModel,
|
||||
ActiveIp: ip,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushActiveTopicMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushErrorMessage 发布错误消息
|
||||
func (*MessageClient) PushErrorMessage(code string, content interface{}, tag message2.ErrorTag) (e error) {
|
||||
b, e := json.Marshal(content)
|
||||
if nil != e {
|
||||
return
|
||||
}
|
||||
message := message2.ErrorMessage{
|
||||
Code: code,
|
||||
Content: string(b),
|
||||
Tag: tag,
|
||||
}
|
||||
e = client.PushErrorTopicMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushEventMessage 发布事件消息
|
||||
func (*MessageClient) PushEventMessage(tag message2.EventTag, flag message2.EventFlag, eventId string, content interface{}) (e error) {
|
||||
message := message2.EventMessage{
|
||||
Tag: tag,
|
||||
Flag: flag,
|
||||
EventId: eventId,
|
||||
EventContent: content,
|
||||
}
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
e = client.PushEventTopicMessage(message)
|
||||
return
|
||||
}
|
||||
|
||||
// PushActiveNewTopicMessage 发布激活消息
|
||||
func (*MessageClient) PushActiveNewTopicMessage(message message2.ActiveNewMessage) (e error) {
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
return client.PushActiveNewTopicMessage(message)
|
||||
}
|
||||
|
||||
// PushRegisterMessage 发布注册消息
|
||||
func (*MessageClient) PushRegisterMessage(message message2.RegisterMessage) (e error) {
|
||||
message.MessageId = HyTools.GetUUID()
|
||||
return client.PushRegisterMessage(message)
|
||||
}
|
||||
13
pkg/partner/mq/pusher/push_client.go
Normal file
13
pkg/partner/mq/pusher/push_client.go
Normal file
@ -0,0 +1,13 @@
|
||||
package pusher
|
||||
|
||||
import "servicebase/pkg/partner/mq/message"
|
||||
|
||||
type PushClient interface {
|
||||
PushRegisterMessage(m message.RegisterMessage) (e error)
|
||||
PushEventTopicMessage(m message.EventMessage) (e error)
|
||||
PushTransactionMessage(m message.TransactionMessage) (e error)
|
||||
PushTransactionUpdateMessage(m message.TransactionUpdateMessage) (e error)
|
||||
PushActiveTopicMessage(m message.ActiveMessage) (e error)
|
||||
PushErrorTopicMessage(m message.ErrorMessage) (e error)
|
||||
PushActiveNewTopicMessage(m message.ActiveNewMessage) (e error)
|
||||
}
|
||||
159
pkg/partner/mq/rocket/client.go
Normal file
159
pkg/partner/mq/rocket/client.go
Normal file
@ -0,0 +1,159 @@
|
||||
package rocket
|
||||
|
||||
import (
|
||||
"servicebase/pkg/common"
|
||||
"servicebase/pkg/partner/mq/message"
|
||||
"servicebase/pkg/partner/mq/pusher"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/anxpp/beego/logs"
|
||||
"github.com/apache/rocketmq-client-go/v2"
|
||||
"github.com/apache/rocketmq-client-go/v2/primitive"
|
||||
"github.com/apache/rocketmq-client-go/v2/producer"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
mqProducer rocketmq.Producer
|
||||
mP sync.Mutex
|
||||
topicName string
|
||||
topicGroup string
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
}
|
||||
|
||||
func init() {
|
||||
//rlog.SetLogger()
|
||||
}
|
||||
|
||||
func NewRocketMQClient(topic, group string) pusher.PushClient {
|
||||
mP.Lock()
|
||||
defer mP.Unlock()
|
||||
|
||||
if mqProducer != nil {
|
||||
return &Client{}
|
||||
}
|
||||
|
||||
topicName = topic
|
||||
topicGroup = group
|
||||
|
||||
nsList := strings.Split(viper.GetString("mq.default.nameserver"), ";")
|
||||
var ns []string
|
||||
if len(nsList) > 0 {
|
||||
for _, item := range nsList {
|
||||
ns = append(ns, item)
|
||||
}
|
||||
} else {
|
||||
ns = []string{"47.97.157.234:9876"}
|
||||
}
|
||||
|
||||
logs.Info("NewRocketMQClient: %s %s %v", topicName, topicGroup, ns)
|
||||
|
||||
var e error
|
||||
mqProducer, e = producer.NewDefaultProducer(
|
||||
producer.WithNameServer(ns),
|
||||
producer.WithRetry(2),
|
||||
producer.WithGroupName(topicGroup),
|
||||
)
|
||||
if e != nil {
|
||||
logs.Error("producer.NewDefaultProducer error: " + e.Error())
|
||||
panic(e)
|
||||
}
|
||||
if mqProducer == nil {
|
||||
logs.Error("producer.NewDefaultProducer error: mqProducer==nil")
|
||||
}
|
||||
err := mqProducer.Start()
|
||||
if err != nil {
|
||||
logs.Error("start producer error: %s", err.Error())
|
||||
panic(err)
|
||||
} else {
|
||||
logs.Info("NewRocketMQClient success")
|
||||
}
|
||||
return &Client{}
|
||||
}
|
||||
|
||||
func (client *Client) Push(tag, key string, message []byte) error {
|
||||
|
||||
defer func() {
|
||||
if x := recover(); x != nil {
|
||||
logs.Error(common.LOG_QUOTE_STRING+"服务器异常 Push Message 获取到Panic @%"+common.LOG_QUOTE_STRING, x)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
|
||||
if nil == mqProducer {
|
||||
NewRocketMQClient(topicName, topicName)
|
||||
}
|
||||
|
||||
msg := primitive.NewMessage(topicName, message)
|
||||
msg.WithTag(tag)
|
||||
msg.WithKeys([]string{key})
|
||||
res, err := mqProducer.SendSync(context.Background(), msg)
|
||||
if err != nil {
|
||||
fmt.Printf("send message error: %s\n", err)
|
||||
} else {
|
||||
fmt.Printf("send message success: result=%s\n", res.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) PushString(tag, key, message string) error {
|
||||
msg := primitive.NewMessage(topicName, []byte(message))
|
||||
msg.WithTag(tag)
|
||||
msg.WithKeys([]string{key})
|
||||
res, err := mqProducer.SendSync(context.Background(), msg)
|
||||
if err != nil {
|
||||
fmt.Printf("send message error: %s\n", err)
|
||||
} else {
|
||||
fmt.Printf("send message success: result=%s\n", res.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 发布注册消息
|
||||
func (client *Client) PushRegisterMessage(message message.RegisterMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("register", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布事件消息
|
||||
func (client *Client) PushEventTopicMessage(message message.EventMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("event", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布交易消息
|
||||
func (client *Client) PushTransactionMessage(message message.TransactionMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("transaction", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布交易更新消息
|
||||
func (client *Client) PushTransactionUpdateMessage(message message.TransactionUpdateMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("transaction-update", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布活跃消息
|
||||
func (client *Client) PushActiveTopicMessage(message message.ActiveMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("active", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布错误消息
|
||||
func (client *Client) PushErrorTopicMessage(message message.ErrorMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("error", message.MessageId, bytes)
|
||||
}
|
||||
|
||||
// 发布激活消息
|
||||
func (client *Client) PushActiveNewTopicMessage(message message.ActiveNewMessage) (e error) {
|
||||
bytes, _ := json.Marshal(message)
|
||||
return client.Push("active-new", message.MessageId, bytes)
|
||||
}
|
||||
25
pkg/partner/mq/rocket_test.go
Normal file
25
pkg/partner/mq/rocket_test.go
Normal file
@ -0,0 +1,25 @@
|
||||
package mq
|
||||
|
||||
import (
|
||||
"servicebase/pkg/common"
|
||||
"servicebase/pkg/partner/mq/message"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/anxpp/beego"
|
||||
)
|
||||
|
||||
func TestRocket(t *testing.T) {
|
||||
e := client.PushRegisterMessage(message.RegisterMessage{
|
||||
Message: message.Message{
|
||||
MessageId: "001",
|
||||
},
|
||||
Platform: "android",
|
||||
UserId: "001",
|
||||
DeviceId: "001",
|
||||
RegisterTime: beego.Date(time.Now(), common.TimeDefaultFormat),
|
||||
ChannelCode: "001",
|
||||
Ip: "0.0.0.0",
|
||||
})
|
||||
println(e)
|
||||
}
|
||||
21
pkg/partner/qiniu/common.go
Normal file
21
pkg/partner/qiniu/common.go
Normal file
@ -0,0 +1,21 @@
|
||||
package qiniu
|
||||
|
||||
type MusicConvertNotifyParam struct {
|
||||
Id string `json:"id"`
|
||||
Pipeline string `json:"pipeline"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
Reqid string `json:"reqid"`
|
||||
InputBucket string `json:"inputBucket"`
|
||||
InputKey string `json:"inputKey"`
|
||||
Items []MusicConvertNotifyParamItem `json:"items"`
|
||||
}
|
||||
|
||||
type MusicConvertNotifyParamItem struct {
|
||||
Cmd string `json:"cmd"`
|
||||
Code int `json:"code"`
|
||||
Desc string `json:"desc"`
|
||||
Hash string `json:"hash"`
|
||||
Key string `json:"key"`
|
||||
ReturnOld int `json:"returnOld"`
|
||||
}
|
||||
137
pkg/partner/qiniu/qiniu_client.go
Normal file
137
pkg/partner/qiniu/qiniu_client.go
Normal file
@ -0,0 +1,137 @@
|
||||
package qiniu
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"strings"
|
||||
|
||||
"github.com/anxpp/beego"
|
||||
"github.com/anxpp/beego/logs"
|
||||
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
||||
"github.com/qiniu/go-sdk/v7/storage"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func getKey() string {
|
||||
return viper.GetString("qiniu.key")
|
||||
}
|
||||
|
||||
func getSecret() string {
|
||||
return viper.GetString("qiniu.secret")
|
||||
}
|
||||
|
||||
// 获取上传图片的token
|
||||
func GenerateUploadPhotoToken(expireSeconds uint64) (token string) {
|
||||
bucket := viper.GetString("qiniu.bucket.photo")
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
ReturnBody: `{"key":"$(key)","w": $(imageInfo.width), "h": $(imageInfo.height)}`,
|
||||
MimeLimit: "image/jpeg;image/png",
|
||||
}
|
||||
//有效期
|
||||
putPolicy.Expires = expireSeconds
|
||||
mac := qbox.NewMac(getKey(), getSecret())
|
||||
token = putPolicy.UploadToken(mac)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取后台上传到七牛的token
|
||||
func GenerateOperationUploadToken(expireSeconds uint64) (token string) {
|
||||
bucket := viper.GetString("qiniu.bucket.photo")
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
ReturnBody: `{"key":"$(key)"}`,
|
||||
MimeLimit: "!application/json;text/plain",
|
||||
}
|
||||
// 有效期
|
||||
putPolicy.Expires = expireSeconds
|
||||
mac := qbox.NewMac(getKey(), getSecret())
|
||||
token = putPolicy.UploadToken(mac)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传音频的token
|
||||
func GenerateUploadAudioToken(expireSeconds uint64) (token string) {
|
||||
bucket := viper.GetString("qiniu.bucket.media")
|
||||
saveMp3Entry := base64.URLEncoding.EncodeToString([]byte(bucket + ":$(key).mp3"))
|
||||
//数据处理指令,支持多个指令
|
||||
avthumbMp3Fop := "avthumb/mp3/ab/192k|saveas/" + saveMp3Entry
|
||||
//连接多个操作指令
|
||||
persistentOps := strings.Join([]string{avthumbMp3Fop}, ";")
|
||||
pipeline := "FYToMP3Quene"
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
PersistentOps: persistentOps,
|
||||
PersistentPipeline: pipeline,
|
||||
PersistentNotifyURL: viper.GetString("open.notify.qiniuMusicConvert"),
|
||||
}
|
||||
putPolicy.Expires = expireSeconds
|
||||
// 转码通知
|
||||
logs.Info("GenerateUploadAudioToken.notifyUrl: " + viper.GetString("open.notify.qiniuMusicConvert"))
|
||||
mac := qbox.NewMac(getKey(), getSecret())
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
token = upToken
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传视频的token
|
||||
func GenerateUploadVideoToken(expireSeconds uint64) (token string) {
|
||||
bucket := viper.GetString("qiniu.bucket.video")
|
||||
saveMp4Entry := base64.URLEncoding.EncodeToString([]byte(bucket + ":$(key).mp4"))
|
||||
saveJpgEntry := base64.URLEncoding.EncodeToString([]byte(bucket + ":$(key).jpg"))
|
||||
//数据处理指令,支持多个指令
|
||||
avthumbMp4Fop := "avthumb/mp4/vcodec/libx264/s/1280x1280/autoscale/1/avsmart/1/enhance/0|saveas/" + saveMp4Entry
|
||||
vframeJpgFop := "vframe/jpg/offset/1|saveas/" + saveJpgEntry
|
||||
//连接多个操作指令
|
||||
// persistentOps := strings.Join([]string{avthumbMp4Fop, vframeJpgFop}, ";")
|
||||
persistentOps := strings.Join([]string{avthumbMp4Fop, vframeJpgFop}, ";")
|
||||
pipeline := "FYVideoToMP4Quene"
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
PersistentOps: persistentOps,
|
||||
PersistentPipeline: pipeline,
|
||||
}
|
||||
putPolicy.Expires = expireSeconds
|
||||
|
||||
mac := qbox.NewMac(getKey(), getSecret())
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
|
||||
token = upToken
|
||||
return
|
||||
}
|
||||
|
||||
func UploadFile(ctx context.Context, bucket string, fileByte []byte, key string) (fileKey string) {
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: bucket,
|
||||
Expires: 600, //10分钟过期
|
||||
}
|
||||
mac := qbox.NewMac(getKey(), getSecret())
|
||||
upToken := putPolicy.UploadToken(mac)
|
||||
cfg := storage.Config{}
|
||||
// 空间对应的机房
|
||||
cfg.Zone = &storage.ZoneHuadong
|
||||
// 是否使用https域名
|
||||
cfg.UseHTTPS = false
|
||||
// 上传是否使用CDN上传加速
|
||||
cfg.UseCdnDomains = false
|
||||
// 构建表单上传的对象
|
||||
formUploader := storage.NewFormUploader(&cfg)
|
||||
ret := storage.PutRet{}
|
||||
// 可选配置
|
||||
putExtra := storage.PutExtra{}
|
||||
|
||||
dataLen := int64(len(fileByte))
|
||||
|
||||
err := formUploader.Put(ctx, &ret, upToken, key, bytes.NewReader(fileByte), dataLen, &putExtra)
|
||||
if err != nil {
|
||||
beego.BeeLogger.Error("上传失败" + err.Error())
|
||||
fileKey = ""
|
||||
return
|
||||
}
|
||||
|
||||
//上传成功
|
||||
fileKey = key
|
||||
return
|
||||
}
|
||||
78
pkg/partner/recommend/client.go
Normal file
78
pkg/partner/recommend/client.go
Normal file
@ -0,0 +1,78 @@
|
||||
package recommend
|
||||
|
||||
import (
|
||||
"servicebase/pkg/cache"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/anxpp/beego"
|
||||
"github.com/anxpp/beego/logs"
|
||||
redis "github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const (
|
||||
keyUser = "user"
|
||||
redisKey = "RECOMMEND"
|
||||
)
|
||||
|
||||
type ItemScore struct {
|
||||
Key string
|
||||
Score float64
|
||||
}
|
||||
|
||||
type recommendRes struct {
|
||||
Code int `json:"code"`
|
||||
Data []ItemScore `json:"data"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func GetRecommendUserList(page, size int, skillId string, input map[string]string) (list []string) {
|
||||
rk := fmt.Sprintf("%s_%s_%s_%s", redisKey, keyUser, skillId, input["user.id"])
|
||||
redisClient := cache.GetCommonRedisInstance().RedisClient
|
||||
// 更新缓存 存在缓存穿透
|
||||
if page == 0 {
|
||||
members := recommendList(keyUser, skillId, input)
|
||||
var mList []redis.Z
|
||||
for _, item := range members {
|
||||
mList = append(mList, redis.Z{
|
||||
Score: item.Score,
|
||||
Member: item.Key,
|
||||
})
|
||||
}
|
||||
if len(mList) > 0 {
|
||||
redisClient.ZAdd(cache.Ctx(), rk, mList...)
|
||||
}
|
||||
}
|
||||
list, _ = redisClient.ZRevRange(cache.Ctx(), rk, int64(page*size), int64((page+1)*size-1)).Result()
|
||||
return
|
||||
}
|
||||
|
||||
func recommendList(key, group string, input map[string]string) (list []ItemScore) {
|
||||
logs.Info("load recommend info from recommend service.")
|
||||
url := fmt.Sprintf("%s%s?group=%s", beego.AppConfig.String("recommendServiceUrl"), key, group)
|
||||
requestBody, _ := json.Marshal(input)
|
||||
resp, err := http.Post(url, "application/json;charset=UTF-8", bytes.NewReader(requestBody))
|
||||
if err != nil {
|
||||
logs.Info(err.Error())
|
||||
}
|
||||
if nil == resp {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
logs.Info(string(body))
|
||||
var res recommendRes
|
||||
_ = json.Unmarshal(body, &res)
|
||||
if res.Code != 6000 {
|
||||
logs.Info(res.Msg)
|
||||
}
|
||||
return res.Data
|
||||
}
|
||||
142
pkg/partner/submail/submail_client.go
Normal file
142
pkg/partner/submail/submail_client.go
Normal file
@ -0,0 +1,142 @@
|
||||
package submail
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"servicebase/pkg/utils"
|
||||
"sort"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
// submailUrl = "https://api.mysubmail.com/message/xsend.json"
|
||||
// submailAppId = "105610"
|
||||
// submialAppKey = "6fec871bd91b4184cec9a6840bc55a31"
|
||||
// submailSignType = "normal"
|
||||
|
||||
// // 国际短信
|
||||
// internationalUrl = "https://api-v4.mysubmail.com/internationalsms/xsend"
|
||||
// internationalAppId = "62587"
|
||||
// internationalAppKey = "7b1e063c06d45b29a2490e61363981c4"
|
||||
)
|
||||
|
||||
type SubmailClient struct {
|
||||
ToMobile string
|
||||
TplId string
|
||||
Vars map[string]string
|
||||
}
|
||||
|
||||
type SubmailResponse struct {
|
||||
Status string `json:"status"`
|
||||
SendId string `json:"send_id"`
|
||||
Fee int `json:"fee"`
|
||||
SmsCredits string `json:"sms_credits"`
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
// 用Submail发送短信
|
||||
func (s *SubmailClient) SendMessageBySubmail() (submailRes SubmailResponse) {
|
||||
|
||||
if len(s.ToMobile) == 0 || len(s.TplId) == 0 {
|
||||
panic("缺少手机号或模板ID")
|
||||
}
|
||||
|
||||
parameters := make(map[string]string)
|
||||
parameters["appid"] = viper.GetString("submail.sms.appId")
|
||||
parameters["to"] = s.ToMobile
|
||||
parameters["project"] = s.TplId
|
||||
parameters["sign_type"] = viper.GetString("submail.sms.signType")
|
||||
|
||||
if len(s.Vars) > 0 {
|
||||
varbyte, err := json.Marshal(s.Vars)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
parameters["vars"] = string(varbyte)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(parameters)-1)
|
||||
|
||||
for k := range parameters {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
//对key按asc升序
|
||||
sort.Strings(keys)
|
||||
|
||||
//待签名字符串
|
||||
waitSignString := ""
|
||||
for _, value := range keys {
|
||||
waitSignString += value + "=" + parameters[value] + "&"
|
||||
}
|
||||
strlen := len(waitSignString)
|
||||
if strlen > 0 {
|
||||
waitSignString = waitSignString[:strlen-1]
|
||||
}
|
||||
|
||||
waitSignString += "&signature=" + viper.GetString("submail.sms.appKey")
|
||||
|
||||
res, err1 := utils.HttpPost(viper.GetString("submail.sms.url"), waitSignString)
|
||||
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = json.Unmarshal([]byte(res), &submailRes)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// 用Submail发送国际短信
|
||||
func (s *SubmailClient) SendInternationalMessageBySubmail() (submailRes SubmailResponse) {
|
||||
|
||||
if len(s.ToMobile) == 0 || len(s.TplId) == 0 {
|
||||
panic("缺少手机号或模板ID")
|
||||
}
|
||||
|
||||
parameters := make(map[string]string)
|
||||
parameters["appid"] = viper.GetString("submail.international.appId")
|
||||
parameters["to"] = s.ToMobile
|
||||
parameters["project"] = s.TplId
|
||||
parameters["sign_type"] = viper.GetString("submail.international.signType")
|
||||
|
||||
if len(s.Vars) > 0 {
|
||||
varbyte, err := json.Marshal(s.Vars)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
parameters["vars"] = string(varbyte)
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(parameters)-1)
|
||||
|
||||
for k := range parameters {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
//对key按asc升序
|
||||
sort.Strings(keys)
|
||||
|
||||
//待签名字符串
|
||||
waitSignString := ""
|
||||
for _, value := range keys {
|
||||
waitSignString += value + "=" + parameters[value] + "&"
|
||||
}
|
||||
strlen := len(waitSignString)
|
||||
if strlen > 0 {
|
||||
waitSignString = waitSignString[:strlen-1]
|
||||
}
|
||||
|
||||
waitSignString += "&signature=" + viper.GetString("submail.international.appKey")
|
||||
|
||||
res, err1 := utils.HttpPost(viper.GetString("submail.international.url"), waitSignString)
|
||||
|
||||
if err1 != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_ = json.Unmarshal([]byte(res), &submailRes)
|
||||
return
|
||||
}
|
||||
580
pkg/partner/wxpay_utility/util.go
Normal file
580
pkg/partner/wxpay_utility/util.go
Normal file
@ -0,0 +1,580 @@
|
||||
package wxpay_utility
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/tjfoc/gmsm/sm3"
|
||||
)
|
||||
|
||||
// MchConfig 商户信息配置,用于调用商户API
|
||||
type MchConfig struct {
|
||||
mchId string
|
||||
certificateSerialNo string
|
||||
privateKeyFilePath string
|
||||
wechatPayPublicKeyId string
|
||||
wechatPayPublicKeyFilePath string
|
||||
privateKey *rsa.PrivateKey
|
||||
wechatPayPublicKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
// MchId 商户号
|
||||
func (c *MchConfig) MchId() string {
|
||||
return c.mchId
|
||||
}
|
||||
|
||||
// CertificateSerialNo 商户API证书序列号
|
||||
func (c *MchConfig) CertificateSerialNo() string {
|
||||
return c.certificateSerialNo
|
||||
}
|
||||
|
||||
// PrivateKey 商户API证书对应的私钥
|
||||
func (c *MchConfig) PrivateKey() *rsa.PrivateKey {
|
||||
return c.privateKey
|
||||
}
|
||||
|
||||
// WechatPayPublicKeyId 微信支付公钥ID
|
||||
func (c *MchConfig) WechatPayPublicKeyId() string {
|
||||
return c.wechatPayPublicKeyId
|
||||
}
|
||||
|
||||
// WechatPayPublicKey 微信支付公钥
|
||||
func (c *MchConfig) WechatPayPublicKey() *rsa.PublicKey {
|
||||
return c.wechatPayPublicKey
|
||||
}
|
||||
|
||||
// CreateMchConfig MchConfig 构造函数
|
||||
func CreateMchConfig(
|
||||
mchId string,
|
||||
certificateSerialNo string,
|
||||
privateKeyFilePath string,
|
||||
wechatPayPublicKeyId string,
|
||||
wechatPayPublicKeyFilePath string,
|
||||
) (*MchConfig, error) {
|
||||
mchConfig := &MchConfig{
|
||||
mchId: mchId,
|
||||
certificateSerialNo: certificateSerialNo,
|
||||
privateKeyFilePath: privateKeyFilePath,
|
||||
wechatPayPublicKeyId: wechatPayPublicKeyId,
|
||||
wechatPayPublicKeyFilePath: wechatPayPublicKeyFilePath,
|
||||
}
|
||||
privateKey, err := LoadPrivateKeyWithPath(mchConfig.privateKeyFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mchConfig.privateKey = privateKey
|
||||
wechatPayPublicKey, err := LoadPublicKeyWithPath(mchConfig.wechatPayPublicKeyFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mchConfig.wechatPayPublicKey = wechatPayPublicKey
|
||||
return mchConfig, nil
|
||||
}
|
||||
|
||||
// LoadPrivateKey 通过私钥的文本内容加载私钥
|
||||
func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) {
|
||||
block, _ := pem.Decode([]byte(privateKeyStr))
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("decode private key err")
|
||||
}
|
||||
if block.Type != "PRIVATE KEY" {
|
||||
return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY")
|
||||
}
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse private key err:%s", err.Error())
|
||||
}
|
||||
privateKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("not a RSA private key")
|
||||
}
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// LoadPublicKey 通过公钥的文本内容加载公钥
|
||||
func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) {
|
||||
block, _ := pem.Decode([]byte(publicKeyStr))
|
||||
if block == nil {
|
||||
return nil, errors.New("decode public key error")
|
||||
}
|
||||
if block.Type != "PUBLIC KEY" {
|
||||
return nil, fmt.Errorf("the kind of PEM should be PUBLIC KEY")
|
||||
}
|
||||
key, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse public key err:%s", err.Error())
|
||||
}
|
||||
publicKey, ok := key.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s is not rsa public key", publicKeyStr)
|
||||
}
|
||||
return publicKey, nil
|
||||
}
|
||||
|
||||
// LoadPrivateKeyWithPath 通过私钥的文件路径内容加载私钥
|
||||
func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
|
||||
privateKeyBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read private pem file err:%s", err.Error())
|
||||
}
|
||||
return LoadPrivateKey(string(privateKeyBytes))
|
||||
}
|
||||
|
||||
// LoadPublicKeyWithPath 通过公钥的文件路径加载公钥
|
||||
func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) {
|
||||
publicKeyBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read certificate pem file err:%s", err.Error())
|
||||
}
|
||||
return LoadPublicKey(string(publicKeyBytes))
|
||||
}
|
||||
|
||||
// EncryptOAEPWithPublicKey 使用 OAEP padding方式用公钥进行加密
|
||||
func EncryptOAEPWithPublicKey(message string, publicKey *rsa.PublicKey) (ciphertext string, err error) {
|
||||
if publicKey == nil {
|
||||
return "", fmt.Errorf("you should input *rsa.PublicKey")
|
||||
}
|
||||
ciphertextByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(message), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("encrypt message with public key err:%s", err.Error())
|
||||
}
|
||||
ciphertext = base64.StdEncoding.EncodeToString(ciphertextByte)
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// DecryptAES256GCM 使用 AEAD_AES_256_GCM 算法进行解密
|
||||
//
|
||||
// 可以使用此算法完成微信支付回调报文解密
|
||||
func DecryptAES256GCM(aesKey, associatedData, nonce, ciphertext string) (plaintext string, err error) {
|
||||
decodedCiphertext, err := base64.StdEncoding.DecodeString(ciphertext)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c, err := aes.NewCipher([]byte(aesKey))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
gcm, err := cipher.NewGCM(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dataBytes, err := gcm.Open(nil, []byte(nonce), decodedCiphertext, []byte(associatedData))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(dataBytes), nil
|
||||
}
|
||||
|
||||
// SignSHA256WithRSA 通过私钥对字符串以 SHA256WithRSA 算法生成签名信息
|
||||
func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) {
|
||||
if privateKey == nil {
|
||||
return "", fmt.Errorf("private key should not be nil")
|
||||
}
|
||||
h := crypto.Hash.New(crypto.SHA256)
|
||||
_, err = h.Write([]byte(source))
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
hashed := h.Sum(nil)
|
||||
signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(signatureByte), nil
|
||||
}
|
||||
|
||||
// VerifySHA256WithRSA 通过公钥对字符串和签名结果以 SHA256WithRSA 验证签名有效性
|
||||
func VerifySHA256WithRSA(source string, signature string, publicKey *rsa.PublicKey) error {
|
||||
if publicKey == nil {
|
||||
return fmt.Errorf("public key should not be nil")
|
||||
}
|
||||
|
||||
sigBytes, err := base64.StdEncoding.DecodeString(signature)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify failed: signature is not base64 encoded")
|
||||
}
|
||||
hashed := sha256.Sum256([]byte(source))
|
||||
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], sigBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify signature with public key error:%s", err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenerateNonce 生成一个长度为 NonceLength 的随机字符串(只包含大小写字母与数字)
|
||||
func GenerateNonce() (string, error) {
|
||||
const (
|
||||
// NonceSymbols 随机字符串可用字符集
|
||||
NonceSymbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// NonceLength 随机字符串的长度
|
||||
NonceLength = 32
|
||||
)
|
||||
|
||||
bytes := make([]byte, NonceLength)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
symbolsByteLength := byte(len(NonceSymbols))
|
||||
for i, b := range bytes {
|
||||
bytes[i] = NonceSymbols[b%symbolsByteLength]
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// BuildAuthorization 构建请求头中的 Authorization 信息
|
||||
func BuildAuthorization(
|
||||
mchid string,
|
||||
certificateSerialNo string,
|
||||
privateKey *rsa.PrivateKey,
|
||||
method string,
|
||||
canonicalURL string,
|
||||
body []byte,
|
||||
) (string, error) {
|
||||
const (
|
||||
SignatureMessageFormat = "%s\n%s\n%d\n%s\n%s\n" // 数字签名原文格式
|
||||
// HeaderAuthorizationFormat 请求头中的 Authorization 拼接格式
|
||||
HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
|
||||
)
|
||||
|
||||
nonce, err := GenerateNonce()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
timestamp := time.Now().Unix()
|
||||
message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body)
|
||||
signature, err := SignSHA256WithRSA(message, privateKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
authorization := fmt.Sprintf(
|
||||
HeaderAuthorizationFormat,
|
||||
mchid, nonce, timestamp, certificateSerialNo, signature,
|
||||
)
|
||||
return authorization, nil
|
||||
}
|
||||
|
||||
// ExtractResponseBody 提取应答报文的 Body
|
||||
func ExtractResponseBody(response *http.Response) ([]byte, error) {
|
||||
if response.Body == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body err:[%s]", err.Error())
|
||||
}
|
||||
response.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
return body, nil
|
||||
}
|
||||
|
||||
const (
|
||||
WechatPayTimestamp = "Wechatpay-Timestamp" // 微信支付回包时间戳
|
||||
WechatPayNonce = "Wechatpay-Nonce" // 微信支付回包随机字符串
|
||||
WechatPaySignature = "Wechatpay-Signature" // 微信支付回包签名信息
|
||||
WechatPaySerial = "Wechatpay-Serial" // 微信支付回包平台序列号
|
||||
RequestID = "Request-Id" // 微信支付回包请求ID
|
||||
)
|
||||
|
||||
func validateWechatPaySignature(
|
||||
wechatpayPublicKeyId string,
|
||||
wechatpayPublicKey *rsa.PublicKey,
|
||||
headers *http.Header,
|
||||
body []byte,
|
||||
) error {
|
||||
timestampStr := headers.Get(WechatPayTimestamp)
|
||||
serialNo := headers.Get(WechatPaySerial)
|
||||
signature := headers.Get(WechatPaySignature)
|
||||
nonce := headers.Get(WechatPayNonce)
|
||||
|
||||
// 拒绝过期请求
|
||||
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid timestamp: %w", err)
|
||||
}
|
||||
if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute {
|
||||
return fmt.Errorf("timestamp expired: %d", timestamp)
|
||||
}
|
||||
|
||||
if serialNo != wechatpayPublicKeyId {
|
||||
return fmt.Errorf(
|
||||
"serial-no mismatch: got %s, expected %s",
|
||||
serialNo,
|
||||
wechatpayPublicKeyId,
|
||||
)
|
||||
}
|
||||
|
||||
message := fmt.Sprintf("%s\n%s\n%s\n", timestampStr, nonce, body)
|
||||
if err := VerifySHA256WithRSA(message, signature, wechatpayPublicKey); err != nil {
|
||||
return fmt.Errorf("invalid signature: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateResponse 验证微信支付回包的签名信息
|
||||
func ValidateResponse(
|
||||
wechatpayPublicKeyId string,
|
||||
wechatpayPublicKey *rsa.PublicKey,
|
||||
headers *http.Header,
|
||||
body []byte,
|
||||
) error {
|
||||
if err := validateWechatPaySignature(wechatpayPublicKeyId, wechatpayPublicKey, headers, body); err != nil {
|
||||
return fmt.Errorf("validate response err: %w, RequestID: %s", err, headers.Get(RequestID))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateNotification(
|
||||
wechatpayPublicKeyId string,
|
||||
wechatpayPublicKey *rsa.PublicKey,
|
||||
headers *http.Header,
|
||||
body []byte,
|
||||
) error {
|
||||
if err := validateWechatPaySignature(wechatpayPublicKeyId, wechatpayPublicKey, headers, body); err != nil {
|
||||
return fmt.Errorf("validate notification err: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource 微信支付通知请求中的资源数据
|
||||
type Resource struct {
|
||||
Algorithm string `json:"algorithm"`
|
||||
Ciphertext string `json:"ciphertext"`
|
||||
AssociatedData string `json:"associated_data"`
|
||||
Nonce string `json:"nonce"`
|
||||
OriginalType string `json:"original_type"`
|
||||
}
|
||||
|
||||
// Notification 微信支付通知的数据结构
|
||||
type Notification struct {
|
||||
ID string `json:"id"`
|
||||
CreateTime *time.Time `json:"create_time"`
|
||||
EventType string `json:"event_type"`
|
||||
ResourceType string `json:"resource_type"`
|
||||
Resource *Resource `json:"resource"`
|
||||
Summary string `json:"summary"`
|
||||
|
||||
Plaintext string // 解密后的业务数据(JSON字符串)
|
||||
}
|
||||
|
||||
func (c *Notification) validate() error {
|
||||
if c.Resource == nil {
|
||||
return errors.New("resource is nil")
|
||||
}
|
||||
|
||||
if c.Resource.Algorithm != "AEAD_AES_256_GCM" {
|
||||
return fmt.Errorf("unsupported algorithm: %s", c.Resource.Algorithm)
|
||||
}
|
||||
|
||||
if c.Resource.Ciphertext == "" {
|
||||
return errors.New("ciphertext is empty")
|
||||
}
|
||||
|
||||
if c.Resource.AssociatedData == "" {
|
||||
return errors.New("associated_data is empty")
|
||||
}
|
||||
|
||||
if c.Resource.Nonce == "" {
|
||||
return errors.New("nonce is empty")
|
||||
}
|
||||
|
||||
if c.Resource.OriginalType == "" {
|
||||
return fmt.Errorf("original_type is empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Notification) decrypt(apiv3Key string) error {
|
||||
if err := c.validate(); err != nil {
|
||||
return fmt.Errorf("notification format err: %w", err)
|
||||
}
|
||||
|
||||
plaintext, err := DecryptAES256GCM(
|
||||
apiv3Key,
|
||||
c.Resource.AssociatedData,
|
||||
c.Resource.Nonce,
|
||||
c.Resource.Ciphertext,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("notification decrypt err: %w", err)
|
||||
}
|
||||
|
||||
c.Plaintext = plaintext
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseNotification 解析微信支付通知的报文,返回通知中的业务数据
|
||||
// Notification.PlainText 为解密后的业务数据JSON字符串,请自行反序列化后使用
|
||||
func ParseNotification(
|
||||
wechatpayPublicKeyId string,
|
||||
wechatpayPublicKey *rsa.PublicKey,
|
||||
apiv3Key string,
|
||||
headers *http.Header,
|
||||
body []byte,
|
||||
) (*Notification, error) {
|
||||
if err := validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
notification := &Notification{}
|
||||
if err := json.Unmarshal(body, notification); err != nil {
|
||||
return nil, fmt.Errorf("parse notification err: %w", err)
|
||||
}
|
||||
|
||||
if err := notification.decrypt(apiv3Key); err != nil {
|
||||
return nil, fmt.Errorf("notification decrypt err: %w", err)
|
||||
}
|
||||
|
||||
return notification, nil
|
||||
}
|
||||
|
||||
// ApiException 微信支付API错误异常,发送HTTP请求成功,但返回状态码不是 2XX 时抛出本异常
|
||||
type ApiException struct {
|
||||
statusCode int // 应答报文的 HTTP 状态码
|
||||
header http.Header // 应答报文的 Header 信息
|
||||
body []byte // 应答报文的 Body 原文
|
||||
errorCode string // 微信支付回包的错误码
|
||||
errorMessage string // 微信支付回包的错误信息
|
||||
}
|
||||
|
||||
func (c *ApiException) Error() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.WriteString(fmt.Sprintf("api error:[StatusCode: %d, Body: %s", c.statusCode, string(c.body)))
|
||||
if len(c.header) > 0 {
|
||||
buf.WriteString(" Header: ")
|
||||
for key, value := range c.header {
|
||||
buf.WriteString(fmt.Sprintf("\n - %v=%v", key, value))
|
||||
}
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
buf.WriteString("]")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (c *ApiException) StatusCode() int {
|
||||
return c.statusCode
|
||||
}
|
||||
|
||||
func (c *ApiException) Header() http.Header {
|
||||
return c.header
|
||||
}
|
||||
|
||||
func (c *ApiException) Body() []byte {
|
||||
return c.body
|
||||
}
|
||||
|
||||
func (c *ApiException) ErrorCode() string {
|
||||
return c.errorCode
|
||||
}
|
||||
|
||||
func (c *ApiException) ErrorMessage() string {
|
||||
return c.errorMessage
|
||||
}
|
||||
|
||||
func NewApiException(statusCode int, header http.Header, body []byte) error {
|
||||
ret := &ApiException{
|
||||
statusCode: statusCode,
|
||||
header: header,
|
||||
body: body,
|
||||
}
|
||||
|
||||
bodyObject := map[string]interface{}{}
|
||||
if err := json.Unmarshal(body, &bodyObject); err == nil {
|
||||
if val, ok := bodyObject["code"]; ok {
|
||||
ret.errorCode = val.(string)
|
||||
}
|
||||
if val, ok := bodyObject["message"]; ok {
|
||||
ret.errorMessage = val.(string)
|
||||
}
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// Time 复制 time.Time 对象,并返回复制体的指针
|
||||
func Time(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
// String 复制 string 对象,并返回复制体的指针
|
||||
func String(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// Bytes 复制 []byte 对象,并返回复制体的指针
|
||||
func Bytes(b []byte) *[]byte {
|
||||
return &b
|
||||
}
|
||||
|
||||
// Bool 复制 bool 对象,并返回复制体的指针
|
||||
func Bool(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// Float64 复制 float64 对象,并返回复制体的指针
|
||||
func Float64(f float64) *float64 {
|
||||
return &f
|
||||
}
|
||||
|
||||
// Float32 复制 float32 对象,并返回复制体的指针
|
||||
func Float32(f float32) *float32 {
|
||||
return &f
|
||||
}
|
||||
|
||||
// Int64 复制 int64 对象,并返回复制体的指针
|
||||
func Int64(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Int32 复制 int64 对象,并返回复制体的指针
|
||||
func Int32(i int32) *int32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// generateHashFromStream 从io.Reader流中生成哈希值的通用函数
|
||||
func generateHashFromStream(reader io.Reader, hashFunc func() hash.Hash, algorithmName string) (string, error) {
|
||||
hash := hashFunc()
|
||||
if _, err := io.Copy(hash, reader); err != nil {
|
||||
return "", fmt.Errorf("failed to read stream for %s: %w", algorithmName, err)
|
||||
}
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// GenerateSHA256FromStream 从io.Reader流中生成SHA256哈希值
|
||||
func GenerateSHA256FromStream(reader io.Reader) (string, error) {
|
||||
return generateHashFromStream(reader, sha256.New, "SHA256")
|
||||
}
|
||||
|
||||
// GenerateSHA1FromStream 从io.Reader流中生成SHA1哈希值
|
||||
func GenerateSHA1FromStream(reader io.Reader) (string, error) {
|
||||
return generateHashFromStream(reader, sha1.New, "SHA1")
|
||||
}
|
||||
|
||||
// GenerateSM3FromStream 从io.Reader流中生成SM3哈希值
|
||||
func GenerateSM3FromStream(reader io.Reader) (string, error) {
|
||||
h := sm3.New()
|
||||
if _, err := io.Copy(h, reader); err != nil {
|
||||
return "", fmt.Errorf("failed to read stream for SM3: %w", err)
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
Reference in New Issue
Block a user