first commit

This commit is contained in:
Yangtao
2025-11-18 17:48:20 +08:00
commit 6e56cab848
196 changed files with 65809 additions and 0 deletions

View 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
}

View 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)
}

View 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和网页支付提交用户端ipNative支付填调用微信支付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 // 取值如下JSAPINATIVEAPP等说明详见参数规定
// 公众号支付
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
}

View 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
}

View 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参数做一次URLEncodeURLEncode后的值最终组成了系统参数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 参数做一次 URLEncodeURLEncode 后的值最终组成了系统参数 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
}

View 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
}

View 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
}

View 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)
}

View 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)
}

View File

@ -0,0 +1,5 @@
package message
type Message struct {
MessageId string
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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" // 幸运礼物
)

View 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)
}

View 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)
}

View 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)
}

View 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)
}

View 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"`
}

View 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
}

View 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
}

View 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
}

View 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
}