commit 622614da7e5f085926a64e62452615299bde2a38 Author: go_rely_warehouse <> Date: Mon Sep 26 10:00:06 2022 +0000 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3ef5a04 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +# Test binary, built with `go test -c` +*.test +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +.idea +.vscode +.DS_Store +Thumbs.db +*.swp +*.swn +*.swo +*.swm +*.7z +*.zip +*.rar +*.tar +*.tar.gz +go.sum +/etc/cfg.yaml +images +test/test.json +etc/cfg.yml +t.json +t1.json +t2.json +t3.json +t.go +wait-for-it.sh +test.go +xorm +test.csv +nginx.conf +.devcontainer +.devcontainer/Dockerfile +.devcontainer/sources.list +/t1.go +.idea/* +/.idea/modules.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..cc3e6cc --- /dev/null +++ b/db/db.go @@ -0,0 +1,84 @@ +package db + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + "database/sql" + "fmt" + _ "github.com/go-sql-driver/mysql" //必须导入mysql驱动,否则会panic + "os" + "time" + "xorm.io/xorm" + "xorm.io/xorm/log" +) + +var Db *xorm.Engine + +//根据DB配置文件初始化数据库 +func InitDB(c *md.DBCfg) error { + var ( + err error + f *os.File + ) + //创建Orm引擎 + if Db, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4", c.User, c.Psw, c.Host, c.Name)); err != nil { + return err + } + Db.SetConnMaxLifetime(c.MaxLifetime * time.Second) //设置最长连接时间 + Db.SetMaxOpenConns(c.MaxOpenConns) //设置最大打开连接数 + Db.SetMaxIdleConns(c.MaxIdleConns) //设置连接池的空闲数大小 + if err = Db.Ping(); err != nil { //尝试ping数据库 + return err + } + if c.ShowLog { //根据配置文件设置日志 + Db.ShowSQL(true) //设置是否打印sql + Db.Logger().SetLevel(0) //设置日志等级 + //修改日志文件存放路径文件名是%s.logs + path := fmt.Sprintf(c.Path, c.Name) + f, err = os.OpenFile(path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777) + if err != nil { + os.RemoveAll(c.Path) + if f, err = os.OpenFile(c.Path, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0777); err != nil { + return err + } + } + logger := log.NewSimpleLogger(f) + logger.ShowSQL(true) + Db.SetLogger(logger) + } + return nil +} + +/********************************************* 公用方法 *********************************************/ + +// QueryNativeString 查询原生sql +func QueryNativeString(Db *xorm.Engine, sql string, args ...interface{}) ([]map[string]string, error) { + results, err := Db.SQL(sql, args...).QueryString() + return results, err +} + +// UpdateComm common update +func UpdateComm(Db *xorm.Engine, id interface{}, model interface{}) (int64, error) { + row, err := Db.ID(id).Update(model) + return row, err +} + +// InsertComm common insert +func InsertComm(Db *xorm.Engine, model interface{}) (int64, error) { + row, err := Db.InsertOne(model) + return row, err +} + +// InsertCommWithSession common insert +func InsertCommWithSession(session *xorm.Session, model interface{}) (int64, error) { + row, err := session.InsertOne(model) + return row, err +} + +// ExecuteOriginalSql 执行原生sql +func ExecuteOriginalSql(session *xorm.Session, sql string) (sql.Result, error) { + result, err := session.Exec(sql) + if err != nil { + return nil, err + } + return result, nil +} diff --git a/db/model/pay_channel.go b/db/model/pay_channel.go new file mode 100644 index 0000000..702a641 --- /dev/null +++ b/db/model/pay_channel.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type PayChannel struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + Name string `json:"name" xorm:"not null comment('渠道名称') VARCHAR(255)"` + IsUse int `json:"is_use" xorm:"not null default 1 comment('是否开启;1:开启;0:关闭') TINYINT(1)"` + ThirdPartyId string `json:"third_party_id" xorm:"not null comment('第三方聚合支付的渠道id') VARCHAR(20)"` + Kind string `json:"kind" xorm:"not null comment('渠道种类(self:自有支付 zhiYing:智莺支付 thirdParty 第三方聚合支付 )') VARCHAR(255)"` + ModuleIdentifier string `json:"module_identifier" xorm:"not null comment('模块标识符') VARCHAR(255)"` + ModuleName string `json:"module_name" xorm:"not null comment('模块名称') VARCHAR(255)"` + CommissionRate string `json:"commission_rate" xorm:"not null comment('抽成比例(千分之)') VARCHAR(255)"` + Memo string `json:"memo" xorm:"not null comment('备注') VARCHAR(255)"` + Ext string `json:"ext" xorm:"comment('拓展字段(json存储)') TEXT"` + CreateAt time.Time `json:"create_at" xorm:"default 'CURRENT_TIMESTAMP' comment('创建时间') TIMESTAMP"` + UpdateAt time.Time `json:"update_at" xorm:"default 'CURRENT_TIMESTAMP' comment('更新时间') TIMESTAMP"` +} diff --git a/db/model/pay_channel_master.go b/db/model/pay_channel_master.go new file mode 100644 index 0000000..c72f497 --- /dev/null +++ b/db/model/pay_channel_master.go @@ -0,0 +1,12 @@ +package model + +type PayChannelMaster struct { + Id int `json:"id" xorm:"not null pk autoincr INT(11)"` + ChannelId int `json:"channel_id" xorm:"not null default 1 comment('支付通道id') INT(11)"` + Identifier string `json:"identifier" xorm:"not null comment('场景标识符') VARCHAR(255)"` + MasterId int `json:"master_id" xorm:"not null default 1 comment('站长id') INT(11)"` + MasterNickname string `json:"master_nickname" xorm:"not null comment('站长名称') VARCHAR(255)"` + ThirdPartyId string `json:"third_party_id" xorm:"not null comment('第三方聚合支付的渠道id') VARCHAR(50)"` + MasterAppName string `json:"master_app_name" xorm:"not null comment('站长应用名称') VARCHAR(50)"` + AppId int `json:"app_id" xorm:"not null default 1 comment('应用id(mid)') INT(20)"` +} diff --git a/db/model/sys_cfg.go b/db/model/sys_cfg.go new file mode 100644 index 0000000..22d906b --- /dev/null +++ b/db/model/sys_cfg.go @@ -0,0 +1,7 @@ +package model + +type SysCfg struct { + Key string `json:"key" xorm:"not null pk comment('键') VARCHAR(127)"` + Val string `json:"val" xorm:"comment('值') TEXT"` + Memo string `json:"memo" xorm:"not null default '' comment('备注') VARCHAR(255)"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..17610f7 --- /dev/null +++ b/go.mod @@ -0,0 +1,19 @@ +module code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git + +go 1.15 + +require ( + github.com/forgoer/openssl v1.2.1 + github.com/gin-gonic/gin v1.8.0 + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-sql-driver/mysql v1.6.0 + github.com/gomodule/redigo v1.8.8 + github.com/iGoogle-ink/gopay v1.5.36 + github.com/pkg/errors v0.9.1 + github.com/syyongx/php2go v0.9.7 + go.uber.org/zap v1.16.0 + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 + xorm.io/builder v0.3.10 // indirect + xorm.io/xorm v1.3.0 +) diff --git a/lib/icbc/api.go b/lib/icbc/api.go new file mode 100644 index 0000000..afdd29a --- /dev/null +++ b/lib/icbc/api.go @@ -0,0 +1,151 @@ +package icbc + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + zhios_pay_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils" + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "golang.org/x/crypto/ssh" + "sort" + "strings" + "time" +) + +func ShowIntegral(appId, privateKeyStr string, param map[string]string) (int, error) { + bizContent := map[string]string{ + "name": param["name"], //客户姓名 + "mobile": param["mobile"], //手机号码 + "mobileArea": param["mobileArea"], //手机国际代码 853、852、86 + "optFlag": param["optFlag"], //客户姓名 + "cardNo": param["cardNo"], + "cardType": param["cardType"], + } + if param["optFlag"] == "1" { + bizContent["otp"] = param["otp"] + } + marshal, _ := json.Marshal(bizContent) + param["biz_content"] = string(marshal) + send := Send("/macaufh/mybank/account/cuscredit/cuscreditquery/V1", appId, privateKeyStr, param) + var data = md.IcbcRes{} + err := json.Unmarshal([]byte(send), &data) + fmt.Println("icbc===============", send) + fmt.Println("icbc===============", err) + var cusCredit = 0 + if err != nil { + return cusCredit, err + } + if data.ResponseBizContent.Success { + return data.ResponseBizContent.CusCredit, nil + } + return cusCredit, errors.New(data.ResponseBizContent.ReturnMsg) +} + +//积分扣除 +func DeductIntegral(appId, privateKeyStr string, param map[string]string) error { + bizContent := map[string]string{ + "name": param["name"], //客户姓名 + "mobile": param["mobile"], //手机号码 + "mobileArea": param["mobileArea"], //手机国际代码 853、852、86 + "optFlag": param["optFlag"], //客户姓名 + "cardNo": param["cardNo"], + "cardType": param["cardType"], + "cusCredit": param["cusCredit"], + } + if param["optFlag"] == "1" { + bizContent["otp"] = param["otp"] + } + marshal, _ := json.Marshal(bizContent) + param["biz_content"] = string(marshal) + send := Send("/macaufh/mybank/account/cuscredit/cuscreditconsume/V1", appId, privateKeyStr, param) + var data = md.IcbcRes{} + err := json.Unmarshal([]byte(send), &data) + fmt.Println("icbc===============", send) + fmt.Println("icbc===============", err) + if err != nil { + return err + } + if data.ResponseBizContent.Success { + return nil + } + return errors.New(data.ResponseBizContent.ReturnMsg) + +} +func DefaultParam(method, appId, privateKeyStr string, param map[string]string) map[string]interface{} { + now := time.Now() + arg := map[string]interface{}{ + "app_id": appId, + "msg_id": zhios_pay_utils.Int64ToStr(now.Unix()), + "format": "json", + "charset": "utf-8", + "sign_type": "RSA2", + "timestamp": now.Format("2006-01-02 15:04:05"), + } + if param["biz_content"] != "" { + arg["biz_content"] = param["biz_content"] + } + getSign, err2 := GetSign(method, privateKeyStr, arg) + if err2 != nil { + return nil + } + fmt.Println(getSign) + arg["sign"] = getSign + return arg +} +func Send(method string, appId, privateKeyStr string, param map[string]string) string { + req := DefaultParam(method, appId, privateKeyStr, param) + url := "https://apipcs.icbcmo.site/api" + method + headers := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded", + } + fmt.Println(req) + + //for k, v := range req { + // if strings.Contains(url, "?") == false { + // url += "?" + k + "=" + php2go.URLEncode(v.(string)) + // } else { + // url += "&" + k + "=" + php2go.URLEncode(v.(string)) + // } + //} + fmt.Println(url) + res, err := zhios_pay_utils.CurlPost(url, req, headers) + if err != nil { + fmt.Println(err) + return "" + } + fmt.Println(string(res)) + return string(res) +} +func GetSign(method, privateKeyStr string, param map[string]interface{}) (string, error) { + privateKey, err := ssh.ParseRawPrivateKey([]byte(privateKeyStr)) + if err != nil { + return "", err + } + keys := make([]string, 0, len(param)) + for k := range param { + keys = append(keys, k) + } + sort.Strings(keys) + data := "/api" + method + for _, v := range keys { + if strings.Contains(data, "?") == false { + data += "?" + v + "=" + param[v].(string) + } else { + data += "&" + v + "=" + param[v].(string) + } + } + fmt.Println(privateKey.(*rsa.PrivateKey)) + fmt.Println(data) + h := crypto.Hash.New(crypto.SHA256) + h.Write([]byte(data)) + hashed := h.Sum(nil) + sign, err := rsa.SignPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), crypto.SHA256, hashed) + if err != nil { + return "", nil + } + return base64.StdEncoding.EncodeToString(sign), nil +} diff --git a/lib/local_alipay/api.go b/lib/local_alipay/api.go new file mode 100644 index 0000000..2723e41 --- /dev/null +++ b/lib/local_alipay/api.go @@ -0,0 +1,176 @@ +package local_alipay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + "fmt" + "github.com/iGoogle-ink/gopay" + "github.com/iGoogle-ink/gopay/alipay" +) + +// TradeAppPay is 支付宝APP支付 +// 抖音头条小程序使用APP调起 +func TradeAppPay(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS string, paySet *md.PayData) (string, error) { + //初始化支付宝客户端 + // appID 是在支付宝申请的APPID + // priKey 是支付宝私钥 + // subject 是支付订单的主题 + // orderID 是智莺这边生成的订单id + // amount 是付费金额 + // notiURL 通知地址url + // passback_params 回调通知参数 + + client := alipay.NewClient(appID, priKey, true) + client.DebugSwitch = gopay.DebugOn + //判断密钥的类型 + rsa_type := alipay.RSA2 + pkcs_type := alipay.PKCS1 + if RSA == "1" { + rsa_type = alipay.RSA + } + if PKCS == "1" { + pkcs_type = alipay.PKCS8 + } + if paySet.PayAliUseType == "1" { + rsa_type = alipay.RSA2 + pkcs_type = alipay.PKCS8 + } + //配置公共参数 + client.SetCharset("utf-8"). + SetSignType(rsa_type). + SetPrivateKeyType(pkcs_type) + if notiURL != "" { + client.SetNotifyUrl(notiURL) + } + //新支付宝支付 + if paySet.PayAliUseType == "1" { + client.SetAppCertSN(paySet.PayAppCertSn) + aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6" + client.SetAliPayRootCertSN(aliPayRootCertSN) + client.SetAliPayPublicCertSN(paySet.PayAlipayrsaPublicKey) + } + fmt.Println(client) + //请求参数 + body := make(gopay.BodyMap) + body.Set("subject", subject) + body.Set("body", subject) + body.Set("out_trade_no", orderID) + body.Set("total_amount", amount) + body.Set("timeout_express", "30m") + + // body.Set("passback_params", orderID) + //手机APP支付参数请求 + payParam, err := client.TradeAppPay(body) + if err != nil { + return "", err + } + return payParam, nil +} + +// TradeAppPay is 支付宝H5支付 +func TradeWapPay(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS, page_url string, paySet *md.PayData) (string, error) { + //aliPayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wn1sU/8Q0rYLlZ6sq3enrPZw2ptp6FecHR2bBFLjJ+sKzepROd0bKddgj+Mr1ffr3Ej78mLdWV8IzLfpXUi945DkrQcOUWLY0MHhYVG2jSs/qzFfpzmtut2Cl2TozYpE84zom9ei06u2AXLMBkU6VpznZl+R4qIgnUfByt3Ix5b3h4Cl6gzXMAB1hJrrrCkq+WvWb3Fy0vmk/DUbJEz8i8mQPff2gsHBE1nMPvHVAMw1GMk9ImB4PxucVek4ZbUzVqxZXphaAgUXFK2FSFU+Q+q1SPvHbUsjtIyL+cLA6H/6ybFF9Ffp27Y14AHPw29+243/SpMisbGcj2KD+evBwIDAQAB" + privateKey := priKey + //判断密钥的类型 + rsa_type := alipay.RSA2 + pkcs_type := alipay.PKCS1 + if RSA == "1" { + rsa_type = alipay.RSA + } + if PKCS == "1" { + pkcs_type = alipay.PKCS8 + } + if paySet.PayAliUseType == "1" { + rsa_type = alipay.RSA2 + pkcs_type = alipay.PKCS8 + } + //初始化支付宝客户端 + // appId:应用ID + // privateKey:应用秘钥 + // isProd:是否是正式环境 + client := alipay.NewClient(appID, privateKey, true) + //配置公共参数 + client.SetCharset("utf-8"). + SetSignType(rsa_type). + SetPrivateKeyType(pkcs_type). + SetReturnUrl(page_url). + SetNotifyUrl(notiURL) + //新支付宝支付 + if paySet.PayAliUseType == "1" { + client.SetAppCertSN(paySet.PayAppCertSn) + aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6" + client.SetAliPayRootCertSN(aliPayRootCertSN) + client.SetAliPayPublicCertSN(paySet.PayAlipayrsaPublicKey) + } + //请求参数 + body := make(gopay.BodyMap) + body.Set("subject", subject) + body.Set("out_trade_no", orderID) + // quit_url is 用户付款中途退出返回商户网站的地址 + body.Set("quit_url", notiURL) + body.Set("total_amount", amount) + // product_code is 销售产品码,商家和支付宝签约的产品码 + body.Set("product_code", "QUICK_WAP_WAY") + //手机网站支付请求 + payUrl, err := client.TradeWapPay(body) + if err != nil { + return "", err + + } + return payUrl, nil +} + +// TradeAppPay is 支付宝小程序本身支付 +func TradeCreate(appID, priKey, subject, orderID, amount, notiURL, RSA, PKCS string, paySet *md.PayData) (*alipay.TradeCreateResponse, error) { + //aliPayPublicKey := "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1wn1sU/8Q0rYLlZ6sq3enrPZw2ptp6FecHR2bBFLjJ+sKzepROd0bKddgj+Mr1ffr3Ej78mLdWV8IzLfpXUi945DkrQcOUWLY0MHhYVG2jSs/qzFfpzmtut2Cl2TozYpE84zom9ei06u2AXLMBkU6VpznZl+R4qIgnUfByt3Ix5b3h4Cl6gzXMAB1hJrrrCkq+WvWb3Fy0vmk/DUbJEz8i8mQPff2gsHBE1nMPvHVAMw1GMk9ImB4PxucVek4ZbUzVqxZXphaAgUXFK2FSFU+Q+q1SPvHbUsjtIyL+cLA6H/6ybFF9Ffp27Y14AHPw29+243/SpMisbGcj2KD+evBwIDAQAB" + privateKey := priKey + rsa_type := alipay.RSA2 + pkcs_type := alipay.PKCS1 + if RSA == "1" { + rsa_type = alipay.RSA + } + if PKCS == "1" { + pkcs_type = alipay.PKCS8 + } + if paySet.PayAliUseType == "1" { + rsa_type = alipay.RSA2 + pkcs_type = alipay.PKCS8 + } + //初始化支付宝客户端 + // appId:应用ID + // privateKey:应用私钥,支持PKCS1和PKCS8 + // isProd:是否是正式环境 + client := alipay.NewClient(appID, privateKey, true) + //配置公共参数 + client.SetCharset("utf-8"). + SetSignType(rsa_type). + SetPrivateKeyType(pkcs_type). + SetNotifyUrl(notiURL) + if paySet.PayAliUseType == "1" { + client.SetAppCertSN(paySet.PayAppCertSn) + aliPayRootCertSN := "687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6" + client.SetAliPayRootCertSN(aliPayRootCertSN) + client.SetAliPayPublicCertSN(paySet.PayAlipayrsaPublicKey) + } + //请求参数 + body := make(gopay.BodyMap) + body.Set("subject", subject) + // 支付宝小程序支付时 buyer_id 为必传参数,需要提前获取,获取方法如下两种 + // 1、local_alipay.SystemOauthToken() 返回取值:rsp.SystemOauthTokenResponse.UserId + // 2、client.SystemOauthToken() 返回取值:aliRsp.SystemOauthTokenResponse.UserId + buyer_id, err := client.SystemOauthToken(body) + if err != nil { + return nil, err + } + body.Set("buyer_id", buyer_id) + body.Set("out_trade_no", orderID) + body.Set("total_amount", amount) + //创建订单 + aliRsp, err := client.TradeCreate(body) + + if err != nil { + return nil, err + } + + return aliRsp, nil + +} diff --git a/lib/local_wechat/api.go b/lib/local_wechat/api.go new file mode 100644 index 0000000..b89835a --- /dev/null +++ b/lib/local_wechat/api.go @@ -0,0 +1,303 @@ +package local_wxpay + +import ( + zhios_pay_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils" + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils/logx" + "fmt" + "github.com/iGoogle-ink/gopay" + "github.com/iGoogle-ink/gopay/pkg/util" + "github.com/iGoogle-ink/gopay/wechat" + v3 "github.com/iGoogle-ink/gopay/wechat/v3" + "strconv" + "time" +) + +func NewClient(appId, mchId, apiKey string, isProd bool) *wechat.Client { + // 初始化微信客户端 + // appId:应用ID + // mchId:商户ID + // apiKey:API秘钥值 + // isProd:是否是正式环境 + client := wechat.NewClient(appId, mchId, apiKey, isProd) + // 打开Debug开关,输出请求日志,默认关闭 + client.DebugSwitch = gopay.DebugOn + // 设置国家:不设置默认 中国国内 + // wechat.China:中国国内 + // wechat.China2:中国国内备用 + // wechat.SoutheastAsia:东南亚 + // wechat.Other:其他国家 + client.SetCountry(wechat.China) + // 添加微信证书 Path 路径 + // certFilePath:apiclient_cert.pem 路径 + // keyFilePath:apiclient_key.pem 路径 + // pkcs12FilePath:apiclient_cert.p12 路径 + // 返回err + //client.AddCertFilePath() + + // 添加微信证书内容 Content + // certFileContent:apiclient_cert.pem 内容 + // keyFileContent:apiclient_key.pem 内容 + // pkcs12FileContent:apiclient_cert.p12 内容 + // 返回err + //client.AddCertFileContent() + return client +} + +// TradeAppPay is 微信APP支付 +func TradeAppPay(client *wechat.Client, subject, orderID, amount, notifyUrl string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_App). + Set("sign_type", wechat.SignType_MD5) + /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + //if !ok { + // return nil, errors.New("验签失败") + //} + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + res := map[string]string{ + "appid": client.AppId, + "partnerid": client.MchId, + "prepayid": wxRsp.PrepayId, + "sign": paySign, + "package": "Sign=WXPay", + "noncestr": wxRsp.NonceStr, + "timestamp": timeStamp, + } + return res, nil +} + +// TradeAppPay is 微信H5支付 +func TradeH5Pay(client *wechat.Client, subject, orderID, amount, notifyUrl string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "121.196.29.49"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_H5). + Set("sign_type", wechat.SignType_MD5). + SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("wap_url", "https://www.fumm.cc") + bm.Set("wap_name", "zyos") + }) + }) + /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + packages := "prepay_id=" + wxRsp.PrepayId + paySign := wechat.GetH5PaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey) + fmt.Println("paySign===", paySign) + r := map[string]string{ + "redirect_url": wxRsp.MwebUrl, + } + return r, nil +} + +// TradeMiniProgPay is 微信小程序支付 ☑️ +func TradeMiniProgPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("openid", openid). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_Mini). + Set("sign_type", wechat.SignType_MD5) + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + fmt.Println(wxRsp) + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + packages := "prepay_id=" + wxRsp.PrepayId + paySign := wechat.GetMiniPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey) + res := map[string]string{ + "appId": client.AppId, + "paySign": paySign, + "signType": wechat.SignType_MD5, + "package": packages, + "nonceStr": wxRsp.NonceStr, + "timeStamp": timeStamp, + } + return res, nil +} + +// TradeAppPayV3 is 微信APP支付v3 +func TradeAppPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (map[string]string, error) { + // 初始化 BodyMap + amountNew := zhios_pay_utils.AnyToFloat64(amount) * 100 + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amountNew). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_App). + Set("sign_type", wechat.SignType_MD5) + /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + //// 预下单 + //wxRsp, err := v3.UnifiedOrder(bm) + //if err != nil { + // _ = zhios_pay_logx.Warn(err) + // return nil, err + //} + //_, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + //if err != nil { + // _ = zhios_pay_logx.Warn(err) + // return nil, err + //} + ////if !ok { + //// return nil, errors.New("验签失败") + ////} + //timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + //paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + //res := map[string]string{ + // "appid": client.AppId, + // "partnerid": client.MchId, + // "prepayid": wxRsp.PrepayId, + // "sign": paySign, + // "package": "Sign=WXPay", + // "noncestr": wxRsp.NonceStr, + // "timestamp": timeStamp, + //} + //return res, nil + return nil, nil +} + +//// TradeJSAPIPay is 微信JSAPI支付 +func TradeJSAPIPay(client *wechat.Client, subject, orderID, amount, notifyUrl, openid string) (map[string]string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "121.196.29.49"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_JsApi). + Set("sign_type", wechat.SignType_MD5). + Set("openid", openid). + SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("wap_url", "https://www.fumm.cc") + bm.Set("wap_name", "zyos") + }) + }) + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + _, err = wechat.VerifySign(client.ApiKey, wechat.SignType_MD5, wxRsp) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return nil, err + } + //if !ok { + // return nil, errors.New("验签失败") + //} + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + //paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + packages := "prepay_id=" + wxRsp.PrepayId + paySign := wechat.GetJsapiPaySign(client.AppId, wxRsp.NonceStr, packages, wechat.SignType_MD5, timeStamp, client.ApiKey) + + zhios_pay_logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId) + zhios_pay_logx.Info("wxRsp.PrepayId:" + wxRsp.PrepayId) + zhios_pay_logx.Info("wxRsp.PrepayId:" + openid) + res := map[string]string{ + "appid": client.AppId, + "partnerid": client.MchId, + "prepayid": wxRsp.PrepayId, + "sign": paySign, + "package": "prepay_id=" + wxRsp.PrepayId, + "noncestr": wxRsp.NonceStr, + "timestamp": timeStamp, + } + return res, nil +} + +// TradeH5PayV3 is 微信H5支付v3 +func TradeH5PayV3(client *wechat.Client, subject, orderID, amount, notifyUrl string) (string, error) { + // 初始化 BodyMap + bm := make(gopay.BodyMap) + bm.Set("nonce_str", util.GetRandomString(32)). + Set("body", subject). + Set("out_trade_no", orderID). + Set("total_fee", amount). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", notifyUrl). + Set("trade_type", wechat.TradeType_App). + Set("device_info", "WEB"). + Set("sign_type", wechat.SignType_MD5). + SetBodyMap("scene_info", func(bm gopay.BodyMap) { + bm.SetBodyMap("h5_info", func(bm gopay.BodyMap) { + bm.Set("type", "Wap") + bm.Set("wap_url", "https://www.fumm.cc") + bm.Set("wap_name", "H5测试支付") + }) + }) /*.Set("openid", "o0Df70H2Q0fY8JXh1aFPIRyOBgu8")*/ + // 预下单 + wxRsp, err := client.UnifiedOrder(bm) + if err != nil { + _ = zhios_pay_logx.Warn(err) + return "", err + } + // ====APP支付 paySign==== + timeStamp := strconv.FormatInt(time.Now().Unix(), 10) + // 获取APP支付的 paySign + // 注意:package 参数因为是固定值,无需开发者再传入 + // appId:AppID + // partnerid:partnerid + // nonceStr:随机字符串 + // prepayId:统一下单成功后得到的值 + // signType:签名方式,务必与统一下单时用的签名方式一致 + // timeStamp:时间 + // apiKey:API秘钥值 + paySign := wechat.GetAppPaySign(client.AppId, client.MchId, wxRsp.NonceStr, wxRsp.PrepayId, wechat.SignType_MD5, timeStamp, client.ApiKey) + return paySign, nil +} + +// TradeMiniProgPayV3 is 微信小程序支付v3 +func TradeMiniProgPayV3(client *v3.ClientV3, subject, orderID, amount, notifyUrl string) (string, error) { + return "", nil +} diff --git a/md/alipay.go b/md/alipay.go new file mode 100644 index 0000000..17799ac --- /dev/null +++ b/md/alipay.go @@ -0,0 +1,17 @@ +package md + +type AliPayPayParams struct { + Subject string `json:"subject" binding:"required"` + Amount string `json:"amount" binding:"required"` + OrderType string `json:"order_type" binding:"required"` + OrdId string `json:"ord_id"` +} + +type PayData struct { + PayAppCertSn string `json:"pay_app_cert_sn"` + PayAlipayRootCertSn string `json:"pay_alipay_root_cert_sn"` + PayAlipayrsaPublicKey string `json:"pay_alipayrsa_public_key"` + PayAliUseType string `json:"pay_ali_use_type"` + PriKey string `json:"pay_ali_new_private_key"` + WxAppletFilepathUrl string `json:"wx_applet_filepath_url"` +} diff --git a/md/app_redis_key.go b/md/app_redis_key.go new file mode 100644 index 0000000..d7d5f33 --- /dev/null +++ b/md/app_redis_key.go @@ -0,0 +1,19 @@ +package md + +// 缓存key统一管理, %s格式化为masterId +const ( + AppCfgCacheKey = "%s:cfg_cache:%s" // 占位符: masterId, key的第一个字母 + VirtualCoinCfgCacheKey = "%s:virtual_coin_cfg" + PlanRewardCfgCacheKey = "%s:plan_reward_cfg" + UnionSetCacheCfg = "%s:union_set_cfg:%s" // 联盟设置缓存key + + UserFinValidUpdateLock = "%s:user_fin_valid_update_lock:%s" // 用户余额更新锁(能拿到锁才能更新余额) + + WithdrawApplyQueueListKey = "withdraw_apply_queue" // 提现队列 + + TplBottomNavRedisKey = "%s:tpl_nav_bottom_key:%s" // master_id platform + + SysModByIdRedisKey = "%s:sys_mod_tpl_by_id:%s" + + CfgCacheTime = 86400 +) diff --git a/md/basic.go b/md/basic.go new file mode 100644 index 0000000..8551066 --- /dev/null +++ b/md/basic.go @@ -0,0 +1,6 @@ +package md + +const ( + ZHIOS_PAY_URL_PRD = "http://pay.zhios.cn" + ZHIOS_PAY_URL_DEV = "http://pay.izhyin.com" +) diff --git a/md/cfg_app.go b/md/cfg_app.go new file mode 100644 index 0000000..c380303 --- /dev/null +++ b/md/cfg_app.go @@ -0,0 +1,16 @@ +package md + +import "time" + +//数据库配置结构体 +type DBCfg struct { + Host string `yaml:"host"` //ip及端口 + Name string `yaml:"name"` //库名 + User string `yaml:"user"` //用户 + Psw string `yaml:"psw"` //密码 + ShowLog bool `yaml:"show_log"` //是否显示SQL语句 + MaxLifetime time.Duration `yaml:"max_lifetime"` + MaxOpenConns int `yaml:"max_open_conns"` + MaxIdleConns int `yaml:"max_idle_conns"` + Path string `yaml:"path"` //日志文件存放路径 +} diff --git a/md/icbc.go b/md/icbc.go new file mode 100644 index 0000000..bdc966a --- /dev/null +++ b/md/icbc.go @@ -0,0 +1,12 @@ +package md + +type IcbcRes struct { + ResponseBizContent struct { + CusCredit int `json:"cusCredit"` + MsgID string `json:"msg_id"` + ReturnCode int64 `json:"return_code"` + ReturnMsg string `json:"return_msg"` + Success bool `json:"success"` + } `json:"response_biz_content"` + Sign string `json:"sign"` +} diff --git a/md/join.go b/md/join.go new file mode 100644 index 0000000..2b0547d --- /dev/null +++ b/md/join.go @@ -0,0 +1,102 @@ +package md + +const ( + JoinPayUrl = "https://www.joinpay.com/trade/uniPayApi.action" + PayTestAccount = "888118100005250" + PayTestSecretKey = "a12ceace4cc5423eae32b4626adf56c9" + Q1FrpCodeALIPAY = "ALIPAY_H5" //支付宝固定 + Q1FrpCodeWEIXINPAYAPP = "WEIXIN_APP3" //微信固定(应该) + //Q1FrpCodeWEIXINPAYH5 = "WEIXIN_H5_PLUS" + //Q1FrpCodeWEIXINPAYGZH = "WEIXIN_GZH" + //Q1FrpCodeWEIXINPAYXCX = "WEIXIN_XCX" + PayTestAliAccount = "777146200391877" + PayTestWeChatAccount = "777196100391173" + WEIXINPATH = "/pages/payIndex/payIndex?rc_result=" +) + +// +// JoinPayReq +// @Description: 汇聚支付-聚合支付-支付接口-请求参数信息体 +// @Url: https://www.joinpay.com/open-platform/pages/document.html?apiName=%E8%81%9A%E5%90%88%E6%94%AF%E4%BB%98&id=6 +// +type JoinPayReq struct { + P0Version string `json:"p0_Version" label:"版本号;目前版本号为:2.1;是"` + P1MerchantNo string `json:"p1_MerchantNo" label:"商户编号;商户在支付平台系统的唯一身份标识;是"` + P2OrderNo string `json:"p2_OrderNo" label:"商户订单号;商户系统提交的唯一订单号。仅支持由英文字母、阿拉伯数字、下划线(英文符号“_”)组成的订单号传输;是"` + P3Amount string `json:"p3_Amount" label:"订单金额;单位:元,精确到分,保留两位小数。例如:10.23。;是"` + P4Cur string `json:"p4_Cur" label:"交易币种;默认设置为1(代表人民币);是"` + P5ProductName string `json:"p5_ProductName" label:"商品名称;用于支付时显示在支付平台网关上的订单产品信息;是"` + P6ProductDesc string `json:"p6_ProductDesc" label:"商品描述;对商品信息进行描述;否"` + P7Mp string `json:"p7_Mp" label:"公用回传参数;如果商户请求时传递了该参数,则返回给商户时会原值传回;否"` + P9NotifyUrl string `json:"p9_NotifyUrl" label:"服务器异步通知地址;汇聚支付系统主动通知商户网站里指定的 http 地址。 +异步通知地址需按: +1.AB|notifyUrl 的规则上送才会返回用户标识和平台优惠金额; +2.云闪付 CD|notifyUrl 的规则上才会返回用户支付的卡类型;是"` + Q1FrpCode string `json:"q1_FrpCode" label:"交易类型;可见md模型里提供的;是"` + Q2MerchantBankCode string `json:"q2_MerchantBankCode" label:"银行商户编码;非必填;否"` + Q4IsShowPic string `json:"q4_IsShowPic" label:"是否展示图片;默认为空,不输出图片;填1表示输出图片,仅交易类型为主扫时可用(即当q1_FrpCode=ALIPAY_NATIVE、WEIXIN_NATIVE、JD_NATIVE、QQ_NATIVE、UNIONPAY_NATIVE、BAIDU_NATIVE、SUNING_NATIVE);否"` + Q5OpenId string `json:"q5_OpenId" label:"微信Openid; +公众号获取用户Openid,公众号支付商户及微信小程序商户必填(即当q1_FrpCode=WEIXIN_GZH、WEIXIN_XCX);否"` + Q6AuthCode string `json:"q6_AuthCode" label:" +付款码数字;付款码数字被扫支付必填(即当q1_FrpCode= ALIPAY_CARD、WEIXIN_CARD、JD_CARD、QQ_CARD、UNIONPAY_CARD;否"` + Q7AppId string `json:"q7_AppId" label:"APPID;微信公众号、微信小程序、微信App、微信app+支付必填(即当q1_FrpCode= WEIXIN_GZH、WEIXIN_XCX、WEIXIN_APP3、WEIXIN_H5_PLUS【对应报备小程序appid】);否"` + Q8TerminalNo string `json:"q8_TerminalNo" label:" +终端设备号; +通过终端信息采集接口获取,由汇聚生成。微信/支付宝被扫支付必填(即当q1_FrpCode= ALIPAY_CARD、WEIXIN_CARD);否"` + Q9TransactionModel string `json:"q9_TransactionModel" label:"支付宝H5模式;支付宝H5选填,默认为空 + +1.模式一:当 q9_TransactionModel = MODEL1或为空时,有应答参数返回,rc_Result中返回html,需进行重定向跳转;2.模式二:当 q9_TransactionModel = MODEL2时,直接跳转链接,不返回应答参数。;否"` + QATradeMerchantNo string `json:"qa_TradeMerchantNo" label:"报备商户号;请填写报备产品后返回的报备商户号;是"` + QBbuyerId string `json:"qb_buyerId" label:"买家的支付宝唯一用户号;支付宝服务窗支付必填(即当q1_FrpCode=ALIPAY_FWC )(买家的支付宝唯一用户号(2088开头16位纯数字));否"` + QJDJPlan string `json:"qj_DJPlan" label:"点金计划;该字段参数值为JSON串;否"` + QKDisablePayModel string `json:"qk_DisablePayModel" label:"禁用支付方式;目前仅支持使用微信和支付宝支付时可禁用某些方式,详细枚举值见附录;否"` + QLTerminalIp string `json:"ql_TerminalIp" label:"终端设备IP; +商户端终端设备 IP 地址。微信/支付宝被扫支付必填(即当q1_FrpCode= ALIPAY_CARD、WEIXIN_CARD);否"` + Hmac string `json:"hmac" label:"签名数据;参见签名机制;是"` +} + +type JoinPayResponse struct { + R7TrxNo string `json:"r7_TrxNo"` + RbCodeMsg string `json:"rb_CodeMsg"` + R2OrderNo string `json:"r2_OrderNo"` + R3Amount string `json:"r3_Amount"` + R6FrpCode string `json:"r6_FrpCode"` + RcResult string `json:"rc_Result"` + RaCode int `json:"ra_Code"` + Hmac string `json:"hmac"` + RdPic string `json:"rd_Pic"` + R4Cur string `json:"r4_Cur"` + R5Mp string `json:"r5_Mp"` + R8MerchantBankCode string `json:"r8_MerchantBankCode"` + R0Version string `json:"r0_Version"` + R1MerchantNo string `json:"r1_MerchantNo"` +} + +type JoinPayResponseWeChatRcResult struct { + OrderNo string `json:"order_no"` + TrxNo string `json:"trx_no"` + PayeeName string `json:"payee_name"` + OriginalID string `json:"original_id"` + AppID string `json:"app_id"` + ProductName string `json:"product_name"` + OrderAmout string `json:"order_amout"` +} + +type JoinPayCallBack struct { + R1MerchantNo string `json:"r1_MerchantNo" form:"r1_MerchantNo"` + R2OrderNo string `json:"r2_OrderNo" form:"r2_OrderNo"` + R3Amount string `json:"r3_Amount" form:"r3_Amount"` + R4Cur string `json:"r4_Cur" form:"r4_Cur"` + R5Mp string `json:"r5_Mp" form:"r5_Mp"` + R6Status int `json:"r6_Status" form:"r6_Status"` + R7TrxNo string `json:"r7_TrxNo" form:"r7_TrxNo"` + R8BankOrderNo string `json:"r8_BankOrderNo" form:"r8_BankOrderNo"` + R9BankTrxNo string `json:"r9_BankTrxNo" form:"r9_BankTrxNo"` + RaPayTime string `json:"ra_PayTime" form:"ra_PayTime"` + RbDealTime string `json:"rb_DealTime" form:"rb_DealTime"` + RcBankCode string `json:"rc_BankCode" form:"rc_BankCode"` + RdOpenId string `json:"rd_OpenId" form:"rd_OpenId"` + ReDiscountAmount string `json:"re_DiscountAmount" form:"re_DiscountAmount"` + RhcardType string `json:"rh_cardType" form:"rh_cardType"` + Hmac string `json:"hmac" form:"hmac"` +} diff --git a/md/join_bank_card_pay.go b/md/join_bank_card_pay.go new file mode 100644 index 0000000..4971859 --- /dev/null +++ b/md/join_bank_card_pay.go @@ -0,0 +1,11 @@ +package md + +type JoinBankCardPayCallBack struct { + BizCode string `json:"biz_code"` + BizMsg string `json:"biz_msg"` + Data string `json:"data"` + MchNo string `json:"mch_no"` + RandStr string `json:"rand_str"` + Sign string `json:"sign"` + SignType string `json:"sign_type"` +} diff --git a/md/kudian.go b/md/kudian.go new file mode 100644 index 0000000..b4e04b8 --- /dev/null +++ b/md/kudian.go @@ -0,0 +1,69 @@ +package md + +type KuDianWxPayParams struct { + AppId string `json:"app_id"` + OpenId string `json:"open_id"` + OutTradeNo string `json:"out_trade_no"` + Description string `json:"description"` + NotifyUrl string `json:"notify_url"` + TotalAmount string `json:"total_amount" binding:"required"` + Attach string `json:"attach" binding:"required"` + MustParams struct { + ShopId string `json:"shop_id"` + KuDianMchId string `json:"ku_dian_mch_id"` + KuDianSecretKey string `json:"ku_dian_secret_key"` + } `json:"must_params"` +} + +type KuDianAliAppPayParams struct { + OutTradeNo string `json:"out_trade_no"` + Description string `json:"description"` + NotifyUrl string `json:"notify_url"` + UserId string `json:"user_id"` + TotalAmount string `json:"total_amount" binding:"required"` + Attach string `json:"attach" binding:"required"` + TradeType string `json:"trade_type"` + SenceInfo struct { + WapUrl string `json:"wap_url"` + WapName string `json:"wap_name"` + ReturnUrl string `json:"return_url"` + } `json:"sence_info"` + MustParams struct { + ShopId string `json:"shop_id"` + KuDianMchId string `json:"ku_dian_mch_id"` + KuDianSecretKey string `json:"ku_dian_secret_key"` + } `json:"must_params"` +} + +type MakePayParamsByKuDian struct { + RequestUrl string `json:"request_url"` + Params interface{} `json:"params"` +} + +const ( + WX_APPLET_PAY_URL = "zy-base-widget-sub/pages/cashier/cashier?unique_identifier=" + WX_APP_TO_APPLET_PAY_UNIQUE_IDENTIFIER = "%s:wx_app_to_applet_pay_unique_identifier:%s:%s" // 酷点支付 - App跳转小程序支付 (mid + store_id + 纳秒时间戳) + WX_APP_TO_APPLET_PAY_UNIQUE_IDENTIFIER_CACHE_TIME = 60 * 10 //缓存10分钟 +) + +// ########################################### 酷点支付 - 回调结构体 ############################################### + +type KuDianPayCallback struct { + Code int `json:"code"` + Msg string `json:"msg"` + Result string `json:"result"` +} + +type KuDianPayCallbackResp struct { + MchId string `json:"mch_id"` //商户id + PayNo string `json:"pay_no"` //聚合支付订单号 + OutTradeNo string `json:"out_trade_no"` //业务系统订单号 + TransactionId string `json:"transaction_id"` //支付平台交易流水号。微信、支付宝、云闪付等用户支付使用的平台返回流水号 + Fee int `json:"fee"` //交易总额,单位:分 + ReceivedFee int `json:"received_fee"` //商户实收金额,单位:分 + PayTime string `json:"pay_time"` //支付时间,格式"yyyy-MM-dd HH:mm:ss" + Payment string `json:"payment"` //支付方式 + Body string `json:"body"` //支付简要描述 + Status int `json:"status"` //支付状态 + Attach string `json:"attach"` //附加信息 +} diff --git a/md/platform.go b/md/platform.go new file mode 100644 index 0000000..b479ab5 --- /dev/null +++ b/md/platform.go @@ -0,0 +1,40 @@ +package md + +const ( + /*********** DEVICE ***********/ + PLATFORM_WX_APPLET = "wx_applet" // 小程序 + PLATFORM_TOUTIAO_APPLET = "toutiao_applet" + PLATFORM_TIKTOK_APPLET = "tiktok_applet" + PLATFORM_BAIDU_APPLET = "baidu_applet" + PLATFORM_ALIPAY_APPLET = "alipay_applet" + PLATFORM_WAP = "wap" //h5 + PLATFORM_ANDROID = "android" + PLATFORM_IOS = "ios" + PLATFORM_PC = "pc" + PLATFORM_JSAPI = "jsapi" // 公众号 +) + +const WX_PAY_BROWSER = "wx_pay_browser" // 用于判断显示支付方式 + +var PlatformList = map[string]struct{}{ + PLATFORM_WX_APPLET: {}, + PLATFORM_TOUTIAO_APPLET: {}, + PLATFORM_TIKTOK_APPLET: {}, + PLATFORM_BAIDU_APPLET: {}, + PLATFORM_ALIPAY_APPLET: {}, + PLATFORM_WAP: {}, + PLATFORM_ANDROID: {}, + PLATFORM_IOS: {}, + PLATFORM_PC: {}, +} + +var PlatformMap = map[string]string{ + "android": "2", + "ios": "2", + "wap": "4", // 和小程序公用模板 + "wx_applet": "4", //微信小程序 + "tiktok_applet": "4", + "baidu_applet": "4", + "alipay_applet": "4", + "toutiao_applet": "4", +} diff --git a/md/wxpay.go b/md/wxpay.go new file mode 100644 index 0000000..5526176 --- /dev/null +++ b/md/wxpay.go @@ -0,0 +1,40 @@ +package md + +type WxPayParams struct { + Subject string `json:"subject" binding:"required"` + Amount string `json:"amount" binding:"required"` + OrderType string `json:"order_type" binding:"required"` + OrdId string `json:"ord_id"` + PayWxAppid string `json:"pay_wx_appid"` + PayWxMchId string `json:"pay_wx_mch_id"` + PayWxApiKey string `json:"pay_wx_api_key"` + NotifyUrl string `json:"notify_url"` + ThirdPartyWechatOpenid string `json:"third_party_wechat_openid"` + Platform string `json:"platform"` + PayType string `json:"pay_type"` + PayThirdPartyId string `json:"pay_third_party_id"` + KuDianNotifyUrl string `json:"ku_dian_notify_url"` + +} + +type WxPayCallback struct { + AppId string `json:"appid"` + BankType string `json:"bank_type"` + CashFee string `json:"cash_fee"` + FeeType string `json:"fee_type"` + IsSubscribe string `json:"is_subscribe"` + MasterID string `json:"master_id"` + MchID string `json:"mch_id"` + NonceStr string `json:"nonce_str"` + Openid string `json:"openid"` + OrderType string `json:"order_type"` + OutTradeNo string `json:"out_trade_no"` + PayMethod string `json:"pay_method"` + ResultCode string `json:"result_code"` + ReturnCode string `json:"return_code"` + Sign string `json:"sign"` + TimeEnd string `json:"time_end"` + TotalFee string `json:"total_fee"` + TradeType string `json:"trade_type"` + TransactionID string `json:"transaction_id"` +} diff --git a/pay/init.go b/pay/init.go new file mode 100644 index 0000000..ae8d935 --- /dev/null +++ b/pay/init.go @@ -0,0 +1,34 @@ +package pay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/db" + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" +) + +var cfg = md.DBCfg{ + Host: "", + Name: "", + User: "", + Psw: "", + ShowLog: false, + MaxLifetime: 0, + MaxOpenConns: 0, + MaxIdleConns: 0, + Path: "", +} + +func Init(host, name, user, psw string) error { + cfg.Host = host + cfg.Name = name + cfg.User = user + cfg.Psw = psw + cfg.ShowLog = true + cfg.MaxLifetime = 30 + cfg.MaxOpenConns = 100 + cfg.MaxIdleConns = 100 + cfg.Path = "./tmp/%s.logs" + if err := db.InitDB(&cfg); err != nil { + return err + } + return nil +} diff --git a/pay/logs/1.txt b/pay/logs/1.txt new file mode 100644 index 0000000..e69de29 diff --git a/pay/logs/zyos_website.logs b/pay/logs/zyos_website.logs new file mode 100644 index 0000000..504dd55 --- /dev/null +++ b/pay/logs/zyos_website.logs @@ -0,0 +1,2 @@ +[xorm] [info] 2022/05/31 10:40:31.829104 [SQL] SELECT `id`, `channel_id`, `identifier`, `master_id`, `master_nickname`, `app_id` FROM `pay_channel_master` WHERE (app_id = ?) AND (identifier = ?) LIMIT 1 [35618318 mall] - 28.9565ms +[xorm] [info] 2022/05/31 10:41:41.231871 [SQL] SELECT `id`, `channel_id`, `identifier`, `master_id`, `master_nickname`, `app_id` FROM `pay_channel_master` WHERE (app_id = ?) AND (identifier = ?) LIMIT 1 [35618318 mall] - 24.9118ms diff --git a/pay/pay_by_icbcmo.go b/pay/pay_by_icbcmo.go new file mode 100644 index 0000000..097e4b7 --- /dev/null +++ b/pay/pay_by_icbcmo.go @@ -0,0 +1,26 @@ +package pay + +import "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/lib/icbc" + +//澳门工行积分 +func ShowIntegral(appId, privateKeyStr string, param map[string]string) (int, error) { + cusCredit, err := icbc.ShowIntegral(appId, privateKeyStr, param) + return cusCredit, err +} + +func DeductIntegral(appId, privateKeyStr string, param map[string]string) error { + err := icbc.DeductIntegral(appId, privateKeyStr, param) + return err +} + +//澳门工行积分短信 +func ShowIntegralSms(appId, privateKeyStr string, param map[string]string) (int, error) { + cusCredit, err := icbc.ShowIntegral(appId, privateKeyStr, param) + return cusCredit, err +} + +//澳门工行积分短信 +func DeductIntegralSms(appId, privateKeyStr string, param map[string]string) error { + err := icbc.DeductIntegral(appId, privateKeyStr, param) + return err +} diff --git a/pay/pay_by_join.go b/pay/pay_by_join.go new file mode 100644 index 0000000..490c96f --- /dev/null +++ b/pay/pay_by_join.go @@ -0,0 +1,146 @@ +package pay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + zhios_pay_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils" + "crypto/tls" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +func UniPayByJoin(payParams *md.JoinPayReq, paySecretKey string, isPrd bool) (*md.JoinPayResponse, error) { + payParams.P0Version = "2.1" + payParams.P4Cur = "1" + //可注释行: + //payParams.P1MerchantNo = md.PayTestAccount + //payParams.P2OrderNo = "95271" + //payParams.P3Amount = "0.1" + //payParams.P5ProductName = "测试商品" + //payParams.P9NotifyUrl = "AB|https://www.baidu.com" + //--交易类型 + //payParams.Q1FrpCode = md.Q1FrpCodeWEIXINPAYAPP + //payParams.Q1FrpCode = md.Q1FrpCodeALIPAY + //原始微信AppId + //payParams.Q7AppId = "gh_5a236de9850a" + //微信AppId + //payParams.Q7AppId = "wx5bb10bb79cb5da1c" + //商家报备编号(分微信和支付宝的);支付宝的: + //payParams.QATradeMerchantNo = md.PayTestAliAccount + //商家报备编号(分微信和支付宝的);微信的: + //payParams.QATradeMerchantNo = md.PayTestWeChatAccount + //不可注释行: + postData := url.Values{} + creatHmac(payParams, paySecretKey, &postData) + zhios_pay_utils.FilePutContents("UniPayByJoin", zhios_pay_utils.SerializeStr(map[string]interface{}{ + "data": payParams, + })) + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{Transport: tr} + response, err := client.Post(md.JoinPayUrl, "application/x-www-form-urlencoded", strings.NewReader(postData.Encode())) + if err != nil { + return nil, err + } + defer response.Body.Close() + body, err := ioutil.ReadAll(response.Body) + resp := md.JoinPayResponse{} + zhios_pay_utils.Unserialize(body, &resp) + return &resp, err +} + +func creatHmac(payParams *md.JoinPayReq, paySecretKey string, postData *url.Values) { + hmac := "" + if payParams.P0Version != "" { + postData.Add("p0_Version", payParams.P0Version) + hmac = hmac + payParams.P0Version + } + if payParams.P1MerchantNo != "" { + postData.Add("p1_MerchantNo", payParams.P1MerchantNo) + hmac = hmac + payParams.P1MerchantNo + } + if payParams.P2OrderNo != "" { + postData.Add("p2_OrderNo", payParams.P2OrderNo) + hmac = hmac + payParams.P2OrderNo + } + if payParams.P3Amount != "" { + postData.Add("p3_Amount", payParams.P3Amount) + hmac = hmac + payParams.P3Amount + } + if payParams.P4Cur != "" { + postData.Add("p4_Cur", payParams.P4Cur) + hmac = hmac + payParams.P4Cur + } + if payParams.P5ProductName != "" { + postData.Add("p5_ProductName", payParams.P5ProductName) + hmac = hmac + payParams.P5ProductName + } + if payParams.P6ProductDesc != "" { + postData.Add("p6_ProductDesc", payParams.P6ProductDesc) + hmac = hmac + payParams.P6ProductDesc + } + if payParams.P7Mp != "" { + postData.Add("p7_Mp", payParams.P7Mp) + hmac = hmac + payParams.P7Mp + } + if payParams.P9NotifyUrl != "" { + postData.Add("p9_NotifyUrl", payParams.P9NotifyUrl) + hmac = hmac + payParams.P9NotifyUrl + } + if payParams.Q1FrpCode != "" { + postData.Add("q1_FrpCode", payParams.Q1FrpCode) + hmac = hmac + payParams.Q1FrpCode + } + if payParams.Q2MerchantBankCode != "" { + postData.Add("q2_MerchantBankCode", payParams.Q2MerchantBankCode) + hmac = hmac + payParams.Q2MerchantBankCode + } + if payParams.Q4IsShowPic != "" { + postData.Add("q4_IsShowPic", payParams.Q4IsShowPic) + hmac = hmac + payParams.Q4IsShowPic + } + if payParams.Q5OpenId != "" { + postData.Add("q5_OpenId", payParams.Q5OpenId) + hmac = hmac + payParams.Q5OpenId + } + if payParams.Q6AuthCode != "" { + postData.Add("q6_AuthCode", payParams.Q6AuthCode) + hmac = hmac + payParams.Q6AuthCode + } + if payParams.Q7AppId != "" { + postData.Add("q7_AppId", payParams.Q7AppId) + hmac = hmac + payParams.Q7AppId + } + if payParams.Q8TerminalNo != "" { + postData.Add("q8_TerminalNo", payParams.Q8TerminalNo) + hmac = hmac + payParams.Q8TerminalNo + } + if payParams.Q9TransactionModel != "" { + postData.Add("q9_TransactionModel", payParams.Q9TransactionModel) + hmac = hmac + payParams.Q9TransactionModel + } + if payParams.QATradeMerchantNo != "" { + postData.Add("qa_TradeMerchantNo", payParams.QATradeMerchantNo) + hmac = hmac + payParams.QATradeMerchantNo + } + if payParams.QBbuyerId != "" { + postData.Add("qb_buyerId", payParams.QBbuyerId) + hmac = hmac + payParams.QBbuyerId + } + if payParams.QJDJPlan != "" { + postData.Add("qj_DJPlan", payParams.QJDJPlan) + hmac = hmac + payParams.QJDJPlan + } + if payParams.QKDisablePayModel != "" { + postData.Add("qk_DisablePayModel", payParams.QKDisablePayModel) + hmac = hmac + payParams.QKDisablePayModel + } + if payParams.QLTerminalIp != "" { + postData.Add("ql_TerminalIp", payParams.QLTerminalIp) + hmac = hmac + payParams.QLTerminalIp + } + payParams.Hmac = zhios_pay_utils.Md5(hmac + paySecretKey) + postData.Add("hmac", payParams.Hmac) +} diff --git a/pay/pay_by_join_bank_card.go b/pay/pay_by_join_bank_card.go new file mode 100644 index 0000000..3edf8e2 --- /dev/null +++ b/pay/pay_by_join_bank_card.go @@ -0,0 +1,203 @@ +package pay + +import ( + zhios_pay_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils" + "crypto" + "crypto/md5" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "github.com/forgoer/openssl" + "github.com/syyongx/php2go" + "golang.org/x/crypto/ssh" + mathRand "math/rand" + "sort" + "time" +) + +/*** +method fastPay.agreement.signSms + +data 请求交易的数据,具体参数见下表 + +mch_no 商户在支付平台系统的唯一身份标识。(注:商户编号和商户密钥在汇聚商户后台获取) +pub_key 公钥 + +pri_key 私钥 +*/ + +func GetSms(pubKey, platformKey, priKey, mch_no string, args map[string]string) (string, error) { + param := map[string]string{ + "mch_no": mch_no, + } + param["sec_key"] = randomString(16) + args["payer_name"] = AesEncryptECB([]byte(args["payer_name"]), []byte(param["sec_key"])) + args["id_no"] = AesEncryptECB([]byte(args["id_no"]), []byte(param["sec_key"])) + args["bank_card_no"] = AesEncryptECB([]byte(args["bank_card_no"]), []byte(param["sec_key"])) + args["mobile_no"] = AesEncryptECB([]byte(args["mobile_no"]), []byte(param["sec_key"])) + args["expire_date"] = AesEncryptECB([]byte(args["expire_date"]), []byte(param["sec_key"])) + data, _ := json.Marshal(args) + param["data"] = string(data) + send, err := Send("fastPay.agreement.signSms", platformKey, priKey, param) + return send, err +} + +func SignContract(pubKey, platformKey, priKey, mch_no string, args map[string]string) (string, error) { + param := map[string]string{ + "mch_no": mch_no, + } + param["sec_key"] = randomString(16) + data, _ := json.Marshal(args) + param["data"] = string(data) + send, err := Send("fastPay.agreement.smsSign", platformKey, priKey, param) + return send, err + +} + +func UnSignContract(pubKey, platformKey, priKey, mch_no string, args map[string]string) (string, error) { + param := map[string]string{ + "mch_no": mch_no, + } + param["sec_key"] = randomString(16) + args["sign_no"] = AesEncryptECB([]byte(args["sign_no"]), []byte(param["sec_key"])) + data, _ := json.Marshal(args) + param["data"] = string(data) + send, err := Send("fastPay.agreement.unSign", platformKey, priKey, param) + return send, err +} +func GetPaySms(pubKey, platformKey, priKey, mchNo string, args map[string]string) (string, error) { + param := map[string]string{ + "mch_no": mchNo, + } + param["sec_key"] = randomString(16) + args["sign_no"] = AesEncryptECB([]byte(args["sign_no"]), []byte(param["sec_key"])) + data, _ := json.Marshal(args) + param["data"] = string(data) + send, err := Send("fastPay.agreement.paySms", platformKey, priKey, param) + return send, err +} +func PayDoing(pubKey, platformKey, priKey, mchNo string, args map[string]string) (string, error) { + param := map[string]string{ + "mch_no": mchNo, + } + param["sec_key"] = randomString(16) + data, _ := json.Marshal(args) + param["data"] = string(data) + send, err := Send("fastPay.agreement.smsPay", platformKey, priKey, param) + return send, err +} + +func Send(method, platformKey, priKey string, param map[string]string) (string, error) { + //priKey = strings.ReplaceAll(priKey, "+", "\n") + //priKey = "-----BEGIN PRIVATE KEY-----\n" + priKey + "\n-----END PRIVATE KEY-----" + router := "https://api.joinpay.com/fastpay" + param["method"] = method + param["version"] = "1.0" + param["rand_str"] = randomString(32) + param["sign_type"] = "2" + sign, err := GetSign(priKey, param) + if err != nil { + return "", err + } + param["sign"] = sign + if param["sec_key"] != "" { //rsa加密 + encrypts, err := RsaEncrypts([]byte(param["sec_key"]), []byte(platformKey)) + if err != nil { + return "", err + } + param["sec_key"] = string(encrypts) + } + post, err := zhios_pay_utils.CurlPost(router, param, nil) + fmt.Println(string(post)) + fmt.Println(err) + return string(post), nil +} + +func GetSign(privateKeyStr string, param map[string]string) (string, error) { + privateKey, err := ssh.ParseRawPrivateKey([]byte(privateKeyStr)) + if err != nil { + return "", err + } + keys := make([]string, 0, len(param)) + for k := range param { + keys = append(keys, k) + } + sort.Strings(keys) + data := "" + for _, v := range keys { + if v == "sign" || v == "sec_key" { + continue + } + if data != "" { + data += "&" + v + "=" + param[v] + } else { + data += v + "=" + param[v] + } + } + + fmt.Println("md5 before2:>>>>", data) + hashMd5 := md5.Sum([]byte(data)) + fmt.Println("md5 before2:>>>>", hashMd5) + hashed := hashMd5[:] + fmt.Println("hashed:>>>>", hashed) + fmt.Println("hashed_string:>>>>", string(hashed)) + sign, err := rsa.SignPKCS1v15(rand.Reader, privateKey.(*rsa.PrivateKey), crypto.MD5, hashed) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(sign), nil +} + +// 公钥加密 +func RsaEncrypts(data, keyBytes []byte) (string, error) { + var ciphertext string + //解密pem格式的公钥 + block, _ := pem.Decode(keyBytes) + if block == nil { + return ciphertext, errors.New("公钥错误") + } + // 解析公钥 + pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return ciphertext, err + } + // 类型断言 + pub := pubInterface.(*rsa.PublicKey) + //加密 + ciphertext1, err := rsa.EncryptPKCS1v15(rand.Reader, pub, data) + if err != nil { + return ciphertext, err + } + return base64.StdEncoding.EncodeToString(ciphertext1), nil +} +func randomString(ln int) string { + letters := []rune("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]rune, ln) + r := mathRand.New(mathRand.NewSource(time.Now().UnixNano())) + for i := range b { + b[i] = letters[r.Intn(len(letters))] + } + return string(b) +} + +func AesEncryptECB(origData []byte, key []byte) string { + str, _ := openssl.AesECBEncrypt(origData, key, openssl.PKCS7_PADDING) + value := php2go.Base64Encode(string(str)) + + return value +} +func generateKey(key []byte) (genKey []byte) { + genKey = make([]byte, 16) + copy(genKey, key) + for i := 16; i < len(key); { + for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { + genKey[j] ^= key[i] + } + } + return genKey +} diff --git a/pay/pay_by_kudian.go b/pay/pay_by_kudian.go new file mode 100644 index 0000000..212c615 --- /dev/null +++ b/pay/pay_by_kudian.go @@ -0,0 +1,208 @@ +package pay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + zhios_pay_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils" + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils/cache" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" + "time" +) + +//酷点 - 支付宝APP支付 +func AliAppPayByKuDian(payParams *md.KuDianAliAppPayParams, isPrd bool) (string, error) { + var url string + if isPrd == true { + url = md.ZHIOS_PAY_URL_PRD + "/kuDianPay/Pay/aliAppPay" + } else { + url = md.ZHIOS_PAY_URL_DEV + "/kuDianPay/Pay/aliAppPay" + } + zhios_pay_utils.FilePutContents("AliAppPayByKuDian", zhios_pay_utils.SerializeStr(map[string]interface{}{ + "data": payParams, + })) + bytes, err := zhios_pay_utils.CurlPost(url, zhios_pay_utils.Serialize(payParams), nil) + if err != nil { + return "", err + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data map[string]string `json:"data"` + } + zhios_pay_utils.FilePutContents("AliAppPayByKuDian", zhios_pay_utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return "", err + } + if result.Code != 0 { + return "", errors.New(result.Msg) + } + zhios_pay_utils.FilePutContents("AliAppPayByKuDian", zhios_pay_utils.SerializeStr(result.Data["data"])) + return result.Data["url"], nil +} + +// 酷点 - 支付宝JsAPi支付 +func AliJsApiPayByKuDian(payParams *md.KuDianAliAppPayParams, isPrd bool) (string, error) { + var url string + if isPrd == true { + url = md.ZHIOS_PAY_URL_PRD + "/kuDianPay/Pay/aliJsApiPay" + } else { + url = md.ZHIOS_PAY_URL_DEV + "/kuDianPay/Pay/aliJsApiPay" + } + zhios_pay_utils.FilePutContents("AliAppPayByKuDian", zhios_pay_utils.SerializeStr(map[string]interface{}{ + "data": payParams, + })) + bytes, err := zhios_pay_utils.CurlPost(url, zhios_pay_utils.Serialize(payParams), nil) + if err != nil { + return "", err + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data map[string]string `json:"data"` + } + zhios_pay_utils.FilePutContents("AliAppPayByKuDian", zhios_pay_utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return "", err + } + if result.Code != 0 { + return "", errors.New(result.Msg) + } + zhios_pay_utils.FilePutContents("AliAppPayByKuDian", zhios_pay_utils.SerializeStr(result.Data["data"])) + return result.Data["trade_no"], nil +} + +// 酷点 - 微信小程序支付 +func WxAppletPayByKuDian(payParams *md.KuDianWxPayParams, isPrd bool) (interface{}, error) { + var url string + if isPrd == true { + url = md.ZHIOS_PAY_URL_PRD + "/kuDianPay/Pay/wxJsAPi" + } else { + url = md.ZHIOS_PAY_URL_DEV + "/kuDianPay/Pay/wxJsAPi" + } + zhios_pay_utils.FilePutContents("WxAppletPayByKuDian", zhios_pay_utils.SerializeStr(map[string]interface{}{ + "data": payParams, + })) + bytes, err := zhios_pay_utils.CurlPost(url, zhios_pay_utils.Serialize(payParams), nil) + if err != nil { + return nil, err + } + var result struct { + Code int `json:"code"` + Msg string `json:"msg"` + Data map[string]interface{} `json:"data"` + } + zhios_pay_utils.FilePutContents("WxAppletPayByKuDian", zhios_pay_utils.SerializeStr(result)) + err = json.Unmarshal(bytes, &result) + if err != nil { + return nil, err + } + if result.Code != 0 { + return nil, errors.New(result.Msg) + } + zhios_pay_utils.FilePutContents("WxAppletPayByKuDian", zhios_pay_utils.SerializeStr(result.Data["data"])) + return result.Data["data"], nil +} + +// 酷点 - 检查返回参数 +func CheckAllCallbackParamsByKuDian(c *gin.Context) (md.KuDianPayCallbackResp, error) { + var dataWx md.KuDianPayCallback + var dataPay md.KuDianPayCallbackResp + if err := c.ShouldBindJSON(&dataWx); err != nil { + fmt.Println(">>>>>>>>>CheckAllCallbackParamsByKuDian>>>>>>>>>>>>>>", err) + return dataPay, err + } + if err := json.Unmarshal([]byte(dataWx.Result), &dataPay); err != nil { + fmt.Println(">>>>>>>>>CheckAllCallbackParamsByKuDian>>>>>>>>>>>>>>", err) + return dataPay, err + } + zhios_pay_utils.FilePutContents("CheckAllCallbackParamsByKuDian", zhios_pay_utils.SerializeStr(dataPay)) + return dataPay, nil +} + +/** + * MakePayParamsByKuDian(构造酷点支付参数[APP跳转小程序支付]) + * @param url string 请求URL + * @param uniqueIdentifierOne string 标识占位符-第一个 (示例: 如 masterId) + * @param uniqueIdentifierTwo string 标识占位符-第二个 (示例: 如 uid) + * @param appletOriginalId string 小程序原始id + * @param queryParams string url上参数(示例: "&ord_id=123131313434343&state_zh=待支付&total=6.66") + * @param params map 请求参数 + */ +func MakePayParamsByKuDian(url, uniqueIdentifierOne, uniqueIdentifierTwo, appletOriginalId, queryParams string, params map[string]interface{}) (map[string]string, error) { + if params == nil { + return nil, errors.New("参数解析错误") + } + data := md.MakePayParamsByKuDian{ + RequestUrl: url, + Params: params, + } + redisKey := fmt.Sprintf(md.WX_APP_TO_APPLET_PAY_UNIQUE_IDENTIFIER, uniqueIdentifierOne, uniqueIdentifierTwo, zhios_pay_utils.AnyToString(time.Now().UnixNano())) + cache.SetJson(redisKey, data, md.WX_APP_TO_APPLET_PAY_UNIQUE_IDENTIFIER_CACHE_TIME) + var r = map[string]string{} + r["use_pay_method"] = "third_party" + r["applet_original_id"] = appletOriginalId + r["wx_url"] = md.WX_APPLET_PAY_URL + redisKey + "&" + queryParams + return r, nil +} + +func KuDianAlipayConfig(req, payConfig map[string]string) (*md.KuDianAliAppPayParams, error) { + var payParams = &md.KuDianAliAppPayParams{ + OutTradeNo: payConfig["ord_id"], + NotifyUrl: payConfig["notify_url"], + Description: payConfig["subject"], + TotalAmount: payConfig["amount"], + Attach: req["mid"], + MustParams: struct { + ShopId string `json:"shop_id"` + KuDianMchId string `json:"ku_dian_mch_id"` + KuDianSecretKey string `json:"ku_dian_secret_key"` + }{ + ShopId: payConfig["pay_third_party_id"], + KuDianMchId: req["ku_dian_mch_id"], + KuDianSecretKey: req["ku_dian_secret_key"], + }, + } + if payConfig["trade_type"] != "" { + payParams.TradeType = payConfig["trade_type"] + } + if payConfig["wap_mame"] != "" { + payParams.SenceInfo.WapName = payConfig["wap_mame"] + } + if payConfig["return_url"] != "" { + payParams.SenceInfo.ReturnUrl = payConfig["return_url"] + } + if payConfig["wap_url"] != "" { + payParams.SenceInfo.WapUrl = payConfig["wap_url"] + } + if payConfig["trade_type"] != "" { + payParams.TradeType = payConfig["trade_type"] + } + + return payParams, nil +} + +func KuDianWxConfig(req, payConfig map[string]string) (*md.KuDianWxPayParams, error) { + var payParams = &md.KuDianWxPayParams{ + OutTradeNo: payConfig["ord_id"], + NotifyUrl: payConfig["notify_url"], + Description: payConfig["subject"], + TotalAmount: payConfig["amount"], + AppId: req["wx_app_id"], + OpenId: req["wx_open_id"], + Attach: req["mid"], + MustParams: struct { + ShopId string `json:"shop_id"` + KuDianMchId string `json:"ku_dian_mch_id"` + KuDianSecretKey string `json:"ku_dian_secret_key"` + }{ + ShopId: payConfig["pay_third_party_id"], + KuDianMchId: req["ku_dian_mch_id"], + KuDianSecretKey: req["ku_dian_secret_key"], + }, + } + return payParams, nil +} diff --git a/pay/pay_by_own.go b/pay/pay_by_own.go new file mode 100644 index 0000000..7f6b174 --- /dev/null +++ b/pay/pay_by_own.go @@ -0,0 +1,117 @@ +package pay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/lib/local_alipay" + local_wxpay "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/lib/local_wechat" + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + zhios_pay_utils "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/utils" + "fmt" + "github.com/pkg/errors" +) + +/** +自有支付 支付宝 +req 传参 + +ord_id //订单号 +amount //总金额 +subject //描述 +order_type //订单类型 +notify_url //自有支付回调地址 +platform //平台 由前端header传来的 +pay_app_cert_sn //应用公钥证书 值 +pay_alipayrsa_public_key //支付宝公钥证书 值 +pay_ali_use_type //支付宝选择版本 0旧版 1新版 +private_key //支付宝商户私钥 数据库字段 旧版 pay_ali_private_key 新版 pay_ali_new_private_key +app_id //支付宝appId 数据库字段 旧版 pay_ali_app_id 新版 pay_ali_new_app_id +rsa //支付宝密钥长度 旧版使用 数据库字段 pay_ali_key_len_type +pkcs //支付宝密钥格式 旧版使用 数据库字段 pay_ali_key_format_type +page_url //支付宝wap支付成功跳转链接 +*/ + +func AlipayCofing(args map[string]string) *md.PayData { + var paySet = &md.PayData{ + PayAppCertSn: args["pay_app_cert_sn"], + PayAlipayrsaPublicKey: args["pay_alipayrsa_public_key"], + PayAliUseType: args["pay_ali_use_type"], + } + return paySet +} +func AlipayApp(args map[string]string) (string, error) { + paySet := AlipayCofing(args) + if args["private_key"] == "" || args["app_id"] == "" { + return "", errors.New("请在后台正确配置支付宝") + } + param, err := local_alipay.TradeAppPay(args["app_id"], args["private_key"], args["subject"], args["ord_id"], args["amount"], args["notify_url"], args["rsa"], args["pkcs"], paySet) + if err != nil { + fmt.Println("支付宝错误日志") + fmt.Println(param) + fmt.Println(err) + return "", errors.New("支付宝订单创建失败") + } + return zhios_pay_utils.AnyToString(param), nil +} + +func AlipayWap(args map[string]string) (string, error) { + paySet := AlipayCofing(args) + if args["private_key"] == "" || args["app_id"] == "" { + return "", errors.New("请在后台正确配置支付宝") + } + param, err := local_alipay.TradeWapPay(args["app_id"], args["private_key"], args["subject"], args["ord_id"], args["amount"], args["notify_url"], args["rsa"], args["pkcs"], args["page_url"], paySet) + if err != nil { + fmt.Println("支付宝错误日志") + fmt.Println(param) + fmt.Println(err) + return "", errors.New("支付宝订单创建失败") + } + return zhios_pay_utils.AnyToString(param), nil +} + +func AlipayApplet(args map[string]string) (string, error) { + paySet := AlipayCofing(args) + if args["private_key"] == "" || args["app_id"] == "" { + return "", errors.New("请在后台正确配置支付宝") + } + param, err := local_alipay.TradeCreate(args["app_id"], args["private_key"], args["subject"], args["ord_id"], args["amount"], args["notify_url"], args["rsa"], args["pkcs"], paySet) + if err != nil { + fmt.Println("支付宝错误日志") + fmt.Println(param) + fmt.Println(err) + return "", errors.New("支付宝订单创建失败") + } + return zhios_pay_utils.AnyToString(param), nil +} + +// 微信app支付v2 +func WxAppPay(params map[string]string) (map[string]string, error) { + client := local_wxpay.NewClient(params["pay_wx_appid"], params["pay_wx_mch_id"], params["pay_wx_api_key"], true) + r, err := local_wxpay.TradeAppPay(client, params["subject"], params["ord_id"], params["amount"], params["notify_url"]) + return r, err +} + +// 微信H5支付v2 +func WxH5Pay(params map[string]string) (map[string]string, error) { + client := local_wxpay.NewClient(params["pay_wx_appid"], params["pay_wx_mch_id"], params["pay_wx_api_key"], true) + r, err := local_wxpay.TradeH5Pay(client, params["subject"], params["ord_id"], params["amount"], params["notify_url"]) + return r, err +} + +// 微信小程序v2 +func WxMiniProgPay(params map[string]string) (map[string]string, error) { + client := local_wxpay.NewClient(params["pay_wx_appid"], params["pay_wx_mch_id"], params["pay_wx_api_key"], true) + if params["third_party_wechat_openid"] == "" { + return nil, errors.New("请先授权微信") + } + r, err := local_wxpay.TradeMiniProgPay(client, params["subject"], params["ord_id"], params["amount"], params["notify_url"], params["third_party_wechat_openid"]) + return r, err +} + +// 微信JSAPI支付 +func WxAppJSAPIPay(params map[string]string) (map[string]string, error) { + client := local_wxpay.NewClient(params["pay_wx_appid"], params["pay_wx_mch_id"], params["pay_wx_api_key"], true) + if params["third_party_wechat_openid"] == "" { + return nil, errors.New("请先授权微信") + } + r, err := local_wxpay.TradeJSAPIPay(client, params["subject"], params["ord_id"], params["amount"], params["notify_url"], params["third_party_wechat_openid"]) + return r, err +} diff --git a/pay/pay_config.go b/pay/pay_config.go new file mode 100644 index 0000000..b3ebef0 --- /dev/null +++ b/pay/pay_config.go @@ -0,0 +1,36 @@ +package pay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/db" + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/db/model" + "errors" +) + +//支付渠道获取 +func JudgePayChannel(mid, paySceneIdentifier string) (model.PayChannelMaster, error) { + var payChannelMaster = model.PayChannelMaster{} + res, err := db.Db.Where("app_id = ?", mid).And("identifier = ?", paySceneIdentifier).Get(&payChannelMaster) + if err != nil { + return payChannelMaster, err + } + if !res { + return payChannelMaster, errors.New("查询数据失败") + } + if payChannelMaster.ChannelId == 0 { + return payChannelMaster, errors.New("请联系服务商绑定支付渠道") + } + return payChannelMaster, nil +} + +//支付方式获取 +func JudgePayChannelPayType(channelId string) (model.PayChannel, error) { + var payChannel = model.PayChannel{} + res, err := db.Db.Where("id = ? and is_use=?", channelId, 1).Get(&payChannel) + if err != nil { + return payChannel, err + } + if !res { + return payChannel, errors.New("查询数据失败") + } + return payChannel, nil +} diff --git a/pay/pay_test.go b/pay/pay_test.go new file mode 100644 index 0000000..916c42d --- /dev/null +++ b/pay/pay_test.go @@ -0,0 +1,41 @@ +package pay + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/lib/icbc" + "fmt" + "testing" + "time" +) + +func TestIcbc(t1 *testing.T) { + t := time.Now() + stime := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(), 0, 0, 0, t.Location()).Format("2006-1-02 15:04:05") + fmt.Println(stime) +} +func TestIcbcShow(t *testing.T) { + param := map[string]string{ + "name": "象揪巒", //客户姓名 + "mobile": "66732397", //手机号码 + "mobileArea": "853", //手机国际代码 853、852、86 *- + "optFlag": "1", // + "otp": "921507", + "cardNo": "5305068200048153", + "cardType": "C", + } + integral, err := icbc.ShowIntegral("011904180095001", "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFaVbTHQF1WNlz\n0ZFgioxApDFBtwlVsoJ8Ov6WdfKLO5aikZjZJNcR2dhtgFSCvpLx/L6p/v+Jv6kl\nnJ6ZGdH1e8Yy7zcrOZd20KNawfsVYfbMFZ0yqygAfif96Uw0k2qwpXLZ6KSlV/PT\nA900571+yv2o4mu7fyHTimojwcrfVIKu+6kgidlHd7usciEvvcplV7xafZmzpG+h\n+qlF8iQ9hxou85dvF9HBox8ZkFay0xMy/QqXRYxzIaZgfAF0io/tuthol+6WUZ/M\nLzzkAwn9E4/YM1GE3qzXXE5TZmyEOV/pE+VKzfhj5sMrO1EnzLAsWGWXNd4ISKsV\noeGmtnFpAgMBAAECggEBAL7fOQnqc6yIecgYCt4zV6RwnQeS+D2Z2bxQNjQ4Wojw\n1pbc+9KIYkJqRW6qtAuuQ9ohrCAl4+HGJL+vDVGuu2kFgf9heBAdISvKUwe/owtr\nqvpss494GbdQQ06nsfzrxC6D6eZzoC4WOjP++nTa1Eex8UzHj668H3Kv1Cw6hfMj\nymxsjvV6X6a0Dp2IXiqzDlWRYPzrj3TLxz1AOLyNJP1VaVHaizTL45AT5+9dtkNc\n0kF4KTCfYLMZK9BjlgtgL4dTVun/MVuVeYb2xYUUdJ/lZ501xnwc/OG1zRPmmnIe\nPC8d6kr1VqvODL2xsPZFjha+oIhkVQPvOyjqfNLtBRkCgYEA49TsSdj8wC8iGXs7\nhXTCylH28Fomx1DITujYabhYtfmDD0IIBUU3drOtl8VHDby7wX7pDgCT8JEvbPLQ\n3FlxEgw6YWfhkOo/64MMK6hE9f0VQh2vnWL3S/5zpll5r2ZHkib4SkjWSC2q9qNA\n30lsdCAIJk3xuv6plv+9Q/0M6WsCgYEA3dGWk/o2MU589j4UspWjPSBAMyI/thzX\nYGn73jvk+RAPOxwXkAA0A9azqwvE8YfoQSNe8128OpSvcnmreXc4HphLCeR/Fbls\nDxI/f8C3Qn/LmGK8P795kQaZRDzHw1gBRpshGjEuQy7m7psR14o8gM7cof73d71W\nMzvGEPeioXsCgYEAmQpzOtv2VEaU+lbsUaZwHqXWYfe86tBZ7Z+9l66McJjNr9ck\nOh4XtrXVmwiaxSCNYtoURSKgPc3Y9K9bG1k9eeka4kl8nPKtw08BQuZChJbfy8zT\nJb7P7Y1cNFcQRYFH/GYPDR9GSa/bktdxAUx5/Ltznf9nrLvaQaNCiMQYA0cCgYA0\nomoHnQTE+GngiGSi56z/cSqqt4LEtN1KbHuuhSnVC8GBdi4FJncxPf4OJVHLOkOb\nSUhbmS9z+xoSoNpWjTqMz/hmVYjuX7xLazoUF2ek6Ownsa4XtlijWByAO6fvK2/K\nED9vlqHyoSt9DODT82Jd8jgaXITeLdhJefKKzigSBwKBgH7miTS77XP1n5nMMlbp\nNVPWF40CB7bt4VBslrJA/u0dmEfEKAhRBh6Cg1+l88Q4o/eWR/RjzqTR1Q3XaS3d\nQcwMzAiA+4aqtywStD8v6iRsA/p9A5QYjPsKze3B6urNgzGz8hMqsRLL3NyeYS+3\nCtK4zuKHZKPLivbxALqfJKkH\n-----END PRIVATE KEY-----", param) + fmt.Println(integral) + fmt.Println(err) +} +func TestIcbcDeduct(t *testing.T) { + param := map[string]string{ + "name": "象揪巒", //客户姓名 + "mobile": "66732397", //手机号码 + "mobileArea": "853", //手机国际代码 853、852、86 *- + "optFlag": "1", // + "otp": "179305", + "cardNo": "5305068200048153", + "cardType": "C", + "cusCredit": "1", + } + icbc.DeductIntegral("10000000000000203505", "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCZBYj5SOjewCZ+\nGx2dAj/Lvp/TlDeVseoUl7V1X4K0jzAyo8kwYzMip5SPKj64zbkydyxqUOdEI5vk\nhfUtnZcb8PI7+lhgoBc6nv7Po+pZuP+0OkxhRvgbL+b0O/e2I3dQJOmfafQNLU1x\n3Nf3uqHtLbFuzrGKmG7zrP6to+tqY7+a+mq/o59ZSkDTWFH094jsrLtngu85hKSf\n0N7moxak+LWJF0Czqd4xI8kXfIW5O3t/jaWJPomm1fZSjlbf4VvPXVahkLOV4+Tk\naTlbMrc7ouGPVTGQo0UtWX4CVy30/QvDMfUuhTSw7S/xMO+u7r4wWrNxkEjxIu/m\nzwDFC3Q1AgMBAAECggEBAI2M4Tg4ibIEbZcZ3jrvabQ/kflWklSXT8Hc1K7Ou0nr\natptSB7KpPLZskL9AShSpY5ZT2zJiiUuC3dJs5OOw8nFCU8SNc6sSNChY3UQXCjD\nSUae5yjVTY1YsnwbPPnvtN8eRj6A9JF75rf9Yop2ynNsYUXvTay3dWtUDAL0L0Ep\nsyiU7tcbQ2NjpgVQhTpoP5cPF1ao7kwD2Ky5KmicLSjU7eLUqSvvV+O6Dw8Y5HaA\n96b57zW0J7WL7Jdo4XrfPqJ8zpQP+o7hjAulekNfOtep33TsR4MO8f9nphSUgCb0\nvlJw94nwfzvKfYGaGxRulAr4FXNBSab+H2cYsP6v88ECgYEA4cyBV+XYqEDWOWQS\nxn+zArrfUpk4J4jRnDFyAUdohhC1IKvGjhACmCg3+n8g857+HPixjGdGg0ukh2da\npHJ9FbmEmz+te/fnA0IkQTweUHQP9pJGR3DLn0WQzqBK2Ifp1cQAGWOyFRChqPb4\nemIeL80YubYW8Mgl9+EWF5Tw4G8CgYEArX0Xetk1rAmgg5RWMESh2v2Dbv9XqfDx\nk9AyCmjP21nyizzXCCw7W5MmGl0Av+nXSa78b2nXvXsz/d6/PyW0BtaO8pQX/ZWg\nVxJrUhRMA6JTg0ho/g793jF0nMBSpdivDwy1529PId8WpyeGGm7CMmSOy0SZuHiu\nTB4UJ9sr/5sCgYEAwjOPszeeReet2CJ3+31tSxXKaVIad/Y2wa3NLchq35OTFQxo\nc1rVwGQm8S1R+/Zs/CHxIp7YiKgQU9zSmslVR9ANezEjVpH6gFTstaatM+j+qcVY\n6KzclJnSV8SJWv5aYV53715Nlcx+HcJ8J1YoPFibwW4ZAPJDpZRT2ehh28UCgYBv\nBeKEx4RFeFO0YLVXZlR+JEZ8PgQHyR2QDmg3qSN7qHBDEsg5frTtwZlcva/BIRCZ\nItaBJibeJ336NRYGf0ZaKeyI6KUBr0NQ9YSgnkSW/saa+TfXfhiQnO2XcqxxXd2J\nsASo+t6VOoLwraZwO/ki7p9DDb+iAfjo1UboyqW5iQKBgQCzXyhRfacrW0kgmEp6\nCWR0oQgD54aXj1JjUkEe+gE6E7LY9Cl+yAdHBYmyCzS014jNX93+Bpg1XhoR1knO\n78A8Q7sPCrP908ycKul668daDXHp55CGpScxsx3z9jJs5ori+ZncSDAnHf0T0OjX\n+nDJPGN/gp9/WqApT1L4SLlDxg==\n-----END PRIVATE KEY-----", param) +} diff --git a/pay/tmp/1.txt b/pay/tmp/1.txt new file mode 100644 index 0000000..e69de29 diff --git a/pay/tmp/zyos_website.logs b/pay/tmp/zyos_website.logs new file mode 100644 index 0000000..610ab51 --- /dev/null +++ b/pay/tmp/zyos_website.logs @@ -0,0 +1 @@ +[xorm] [info] 2022/07/07 10:46:11.171900 [SQL] SELECT `id`, `channel_id`, `identifier`, `master_id`, `master_nickname`, `third_party_id`, `master_app_name`, `app_id` FROM `pay_channel_master` WHERE (app_id = ?) AND (identifier = ?) LIMIT 1 [35618318 mall] - 26.2547ms diff --git a/test/join_pay_test.go b/test/join_pay_test.go new file mode 100644 index 0000000..dd95946 --- /dev/null +++ b/test/join_pay_test.go @@ -0,0 +1,11 @@ +package test + +import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/md" + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/pay" + "testing" +) + +func TestUniPayByJoin(t *testing.T) { + pay.UniPayByJoin(&md.JoinPayReq{},"a12ceace4cc5423eae32b4626adf56c9" ,false) +} diff --git a/utils/cache/base.go b/utils/cache/base.go new file mode 100644 index 0000000..64648dd --- /dev/null +++ b/utils/cache/base.go @@ -0,0 +1,421 @@ +package cache + +import ( + "errors" + "fmt" + "strconv" + "time" +) + +const ( + redisDialTTL = 10 * time.Second + redisReadTTL = 3 * time.Second + redisWriteTTL = 3 * time.Second + redisIdleTTL = 10 * time.Second + redisPoolTTL = 10 * time.Second + redisPoolSize int = 512 + redisMaxIdleConn int = 64 + redisMaxActive int = 512 +) + +var ( + ErrNil = errors.New("nil return") + ErrWrongArgsNum = errors.New("args num error") + ErrNegativeInt = errors.New("redis cluster: unexpected value for Uint64") +) + +// 以下为提供类型转换 + +func Int(reply interface{}, err error) (int, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return reply, nil + case int8: + return int(reply), nil + case int16: + return int(reply), nil + case int32: + return int(reply), nil + case int64: + x := int(reply) + if int64(x) != reply { + return 0, strconv.ErrRange + } + return x, nil + case uint: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int(reply), nil + case uint16: + return int(reply), nil + case uint32: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint64: + n := int(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 0) + return int(n), err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 0) + return int(n), err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int, got type %T", reply) +} + +func Int64(reply interface{}, err error) (int64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case int: + return int64(reply), nil + case int8: + return int64(reply), nil + case int16: + return int64(reply), nil + case int32: + return int64(reply), nil + case int64: + return reply, nil + case uint: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case uint8: + return int64(reply), nil + case uint16: + return int64(reply), nil + case uint32: + return int64(reply), nil + case uint64: + n := int64(reply) + if n < 0 { + return 0, strconv.ErrRange + } + return n, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseInt(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Int64, got type %T", reply) +} + +func Uint64(reply interface{}, err error) (uint64, error) { + if err != nil { + return 0, err + } + switch reply := reply.(type) { + case uint: + return uint64(reply), nil + case uint8: + return uint64(reply), nil + case uint16: + return uint64(reply), nil + case uint32: + return uint64(reply), nil + case uint64: + return reply, nil + case int: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int8: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int16: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int32: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case int64: + if reply < 0 { + return 0, ErrNegativeInt + } + return uint64(reply), nil + case []byte: + data := string(reply) + if len(data) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(data, 10, 64) + return n, err + case string: + if len(reply) == 0 { + return 0, ErrNil + } + + n, err := strconv.ParseUint(reply, 10, 64) + return n, err + case nil: + return 0, ErrNil + case error: + return 0, reply + } + return 0, fmt.Errorf("redis cluster: unexpected type for Uint64, got type %T", reply) +} + +func Float64(reply interface{}, err error) (float64, error) { + if err != nil { + return 0, err + } + + var value float64 + err = nil + switch v := reply.(type) { + case float32: + value = float64(v) + case float64: + value = v + case int: + value = float64(v) + case int8: + value = float64(v) + case int16: + value = float64(v) + case int32: + value = float64(v) + case int64: + value = float64(v) + case uint: + value = float64(v) + case uint8: + value = float64(v) + case uint16: + value = float64(v) + case uint32: + value = float64(v) + case uint64: + value = float64(v) + case []byte: + data := string(v) + if len(data) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(string(v), 64) + case string: + if len(v) == 0 { + return 0, ErrNil + } + value, err = strconv.ParseFloat(v, 64) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for Float64, got type %T", v) + } + + return value, err +} + +func Bool(reply interface{}, err error) (bool, error) { + if err != nil { + return false, err + } + switch reply := reply.(type) { + case bool: + return reply, nil + case int64: + return reply != 0, nil + case []byte: + data := string(reply) + if len(data) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(data) + case string: + if len(reply) == 0 { + return false, ErrNil + } + + return strconv.ParseBool(reply) + case nil: + return false, ErrNil + case error: + return false, reply + } + return false, fmt.Errorf("redis cluster: unexpected type for Bool, got type %T", reply) +} + +func Bytes(reply interface{}, err error) ([]byte, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []byte: + if len(reply) == 0 { + return nil, ErrNil + } + return reply, nil + case string: + data := []byte(reply) + if len(data) == 0 { + return nil, ErrNil + } + return data, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Bytes, got type %T", reply) +} + +func String(reply interface{}, err error) (string, error) { + if err != nil { + return "", err + } + + value := "" + err = nil + switch v := reply.(type) { + case string: + if len(v) == 0 { + return "", ErrNil + } + + value = v + case []byte: + if len(v) == 0 { + return "", ErrNil + } + + value = string(v) + case int: + value = strconv.FormatInt(int64(v), 10) + case int8: + value = strconv.FormatInt(int64(v), 10) + case int16: + value = strconv.FormatInt(int64(v), 10) + case int32: + value = strconv.FormatInt(int64(v), 10) + case int64: + value = strconv.FormatInt(v, 10) + case uint: + value = strconv.FormatUint(uint64(v), 10) + case uint8: + value = strconv.FormatUint(uint64(v), 10) + case uint16: + value = strconv.FormatUint(uint64(v), 10) + case uint32: + value = strconv.FormatUint(uint64(v), 10) + case uint64: + value = strconv.FormatUint(v, 10) + case float32: + value = strconv.FormatFloat(float64(v), 'f', -1, 32) + case float64: + value = strconv.FormatFloat(v, 'f', -1, 64) + case bool: + value = strconv.FormatBool(v) + case nil: + err = ErrNil + case error: + err = v + default: + err = fmt.Errorf("redis cluster: unexpected type for String, got type %T", v) + } + + return value, err +} + +func Strings(reply interface{}, err error) ([]string, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + result := make([]string, len(reply)) + for i := range reply { + if reply[i] == nil { + continue + } + switch subReply := reply[i].(type) { + case string: + result[i] = subReply + case []byte: + result[i] = string(subReply) + default: + return nil, fmt.Errorf("redis cluster: unexpected element type for String, got type %T", reply[i]) + } + } + return result, nil + case []string: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Strings, got type %T", reply) +} + +func Values(reply interface{}, err error) ([]interface{}, error) { + if err != nil { + return nil, err + } + switch reply := reply.(type) { + case []interface{}: + return reply, nil + case nil: + return nil, ErrNil + case error: + return nil, reply + } + return nil, fmt.Errorf("redis cluster: unexpected type for Values, got type %T", reply) +} diff --git a/utils/cache/redis.go b/utils/cache/redis.go new file mode 100644 index 0000000..4e5f047 --- /dev/null +++ b/utils/cache/redis.go @@ -0,0 +1,403 @@ +package cache + +import ( + "encoding/json" + "errors" + "log" + "strings" + "time" + + redigo "github.com/gomodule/redigo/redis" +) + +// configuration +type Config struct { + Server string + Password string + MaxIdle int // Maximum number of idle connections in the pool. + + // Maximum number of connections allocated by the pool at a given time. + // When zero, there is no limit on the number of connections in the pool. + MaxActive int + + // Close connections after remaining idle for this duration. If the value + // is zero, then idle connections are not closed. Applications should set + // the timeout to a value less than the server's timeout. + IdleTimeout time.Duration + + // If Wait is true and the pool is at the MaxActive limit, then Get() waits + // for a connection to be returned to the pool before returning. + Wait bool + KeyPrefix string // prefix to all keys; example is "dev environment name" + KeyDelimiter string // delimiter to be used while appending keys; example is ":" + KeyPlaceholder string // placeholder to be parsed using given arguments to obtain a final key; example is "?" +} + +var pool *redigo.Pool +var conf *Config + +func NewRedis(addr string) { + if addr == "" { + panic("\nredis connect string cannot be empty\n") + } + pool = &redigo.Pool{ + MaxIdle: redisMaxIdleConn, + IdleTimeout: redisIdleTTL, + MaxActive: redisMaxActive, + // MaxConnLifetime: redisDialTTL, + Wait: true, + Dial: func() (redigo.Conn, error) { + c, err := redigo.Dial("tcp", addr, + redigo.DialConnectTimeout(redisDialTTL), + redigo.DialReadTimeout(redisReadTTL), + redigo.DialWriteTimeout(redisWriteTTL), + ) + if err != nil { + log.Println("Redis Dial failed: ", err) + return nil, err + } + return c, err + }, + TestOnBorrow: func(c redigo.Conn, t time.Time) error { + _, err := c.Do("PING") + if err != nil { + log.Println("Unable to ping to redis server:", err) + } + return err + }, + } + conn := pool.Get() + defer conn.Close() + if conn.Err() != nil { + println("\nredis connect " + addr + " error: " + conn.Err().Error()) + } else { + println("\nredis connect " + addr + " success!\n") + } +} + +func Do(cmd string, args ...interface{}) (reply interface{}, err error) { + conn := pool.Get() + defer conn.Close() + return conn.Do(cmd, args...) +} + +func GetPool() *redigo.Pool { + return pool +} + +func ParseKey(key string, vars []string) (string, error) { + arr := strings.Split(key, conf.KeyPlaceholder) + actualKey := "" + if len(arr) != len(vars)+1 { + return "", errors.New("redis/connection.go: Insufficient arguments to parse key") + } else { + for index, val := range arr { + if index == 0 { + actualKey = arr[index] + } else { + actualKey += vars[index-1] + val + } + } + } + return getPrefixedKey(actualKey), nil +} + +func getPrefixedKey(key string) string { + return conf.KeyPrefix + conf.KeyDelimiter + key +} +func StripEnvKey(key string) string { + return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) +} +func SplitKey(key string) []string { + return strings.Split(key, conf.KeyDelimiter) +} +func Expire(key string, ttl int) (interface{}, error) { + return Do("EXPIRE", key, ttl) +} +func Persist(key string) (interface{}, error) { + return Do("PERSIST", key) +} + +func Del(key string) (interface{}, error) { + return Do("DEL", key) +} +func Set(key string, data interface{}) (interface{}, error) { + // set + return Do("SET", key, data) +} +func SetNX(key string, data interface{}) (interface{}, error) { + return Do("SETNX", key, data) +} +func SetEx(key string, data interface{}, ttl int) (interface{}, error) { + return Do("SETEX", key, ttl, data) +} + +func SetJson(key string, data interface{}, ttl int) bool { + c, err := json.Marshal(data) + if err != nil { + return false + } + if ttl < 1 { + _, err = Set(key, c) + } else { + _, err = SetEx(key, c, ttl) + } + if err != nil { + return false + } + return true +} + +func GetJson(key string, dst interface{}) error { + b, err := GetBytes(key) + if err != nil { + return err + } + if err = json.Unmarshal(b, dst); err != nil { + return err + } + return nil +} + +func Get(key string) (interface{}, error) { + // get + return Do("GET", key) +} +func GetTTL(key string) (time.Duration, error) { + ttl, err := redigo.Int64(Do("TTL", key)) + return time.Duration(ttl) * time.Second, err +} +func GetBytes(key string) ([]byte, error) { + return redigo.Bytes(Do("GET", key)) +} +func GetString(key string) (string, error) { + return redigo.String(Do("GET", key)) +} +func GetStringMap(key string) (map[string]string, error) { + return redigo.StringMap(Do("GET", key)) +} +func GetInt(key string) (int, error) { + return redigo.Int(Do("GET", key)) +} +func GetInt64(key string) (int64, error) { + return redigo.Int64(Do("GET", key)) +} +func GetStringLength(key string) (int, error) { + return redigo.Int(Do("STRLEN", key)) +} +func ZAdd(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, score, data) +} +func ZAddNX(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, "NX", score, data) +} +func ZRem(key string, data interface{}) (interface{}, error) { + return Do("ZREM", key, data) +} +func ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { + if withScores { + return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) + } + return redigo.Values(Do("ZRANGE", key, start, end)) +} +func ZRemRangeByScore(key string, start int64, end int64) ([]interface{}, error) { + return redigo.Values(Do("ZREMRANGEBYSCORE", key, start, end)) +} +func ZCard(setName string) (int64, error) { + return redigo.Int64(Do("ZCARD", setName)) +} +func ZScan(setName string) (int64, error) { + return redigo.Int64(Do("ZCARD", setName)) +} +func SAdd(setName string, data interface{}) (interface{}, error) { + return Do("SADD", setName, data) +} +func SCard(setName string) (int64, error) { + return redigo.Int64(Do("SCARD", setName)) +} +func SIsMember(setName string, data interface{}) (bool, error) { + return redigo.Bool(Do("SISMEMBER", setName, data)) +} +func SMembers(setName string) ([]string, error) { + return redigo.Strings(Do("SMEMBERS", setName)) +} +func SRem(setName string, data interface{}) (interface{}, error) { + return Do("SREM", setName, data) +} +func HSet(key string, HKey string, data interface{}) (interface{}, error) { + return Do("HSET", key, HKey, data) +} + +func HGet(key string, HKey string) (interface{}, error) { + return Do("HGET", key, HKey) +} + +func HMGet(key string, hashKeys ...string) ([]interface{}, error) { + ret, err := Do("HMGET", key, hashKeys) + if err != nil { + return nil, err + } + reta, ok := ret.([]interface{}) + if !ok { + return nil, errors.New("result not an array") + } + return reta, nil +} + +func HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { + if len(hashKeys) == 0 || len(hashKeys) != len(vals) { + var ret interface{} + return ret, errors.New("bad length") + } + input := []interface{}{key} + for i, v := range hashKeys { + input = append(input, v, vals[i]) + } + return Do("HMSET", input...) +} + +func HGetString(key string, HKey string) (string, error) { + return redigo.String(Do("HGET", key, HKey)) +} +func HGetFloat(key string, HKey string) (float64, error) { + f, err := redigo.Float64(Do("HGET", key, HKey)) + return f, err +} +func HGetInt(key string, HKey string) (int, error) { + return redigo.Int(Do("HGET", key, HKey)) +} +func HGetInt64(key string, HKey string) (int64, error) { + return redigo.Int64(Do("HGET", key, HKey)) +} +func HGetBool(key string, HKey string) (bool, error) { + return redigo.Bool(Do("HGET", key, HKey)) +} +func HDel(key string, HKey string) (interface{}, error) { + return Do("HDEL", key, HKey) +} + +func HGetAll(key string) (map[string]interface{}, error) { + vals, err := redigo.Values(Do("HGETALL", key)) + if err != nil { + return nil, err + } + num := len(vals) / 2 + result := make(map[string]interface{}, num) + for i := 0; i < num; i++ { + key, _ := redigo.String(vals[2*i], nil) + result[key] = vals[2*i+1] + } + return result, nil +} + +func FlushAll() bool { + res, _ := redigo.String(Do("FLUSHALL")) + if res == "" { + return false + } + return true +} + +// NOTE: Use this in production environment with extreme care. +// Read more here:https://redigo.io/commands/keys +func Keys(pattern string) ([]string, error) { + return redigo.Strings(Do("KEYS", pattern)) +} + +func HKeys(key string) ([]string, error) { + return redigo.Strings(Do("HKEYS", key)) +} + +func Exists(key string) bool { + count, err := redigo.Int(Do("EXISTS", key)) + if count == 0 || err != nil { + return false + } + return true +} + +func Incr(key string) (int64, error) { + return redigo.Int64(Do("INCR", key)) +} + +func Decr(key string) (int64, error) { + return redigo.Int64(Do("DECR", key)) +} + +func IncrBy(key string, incBy int64) (int64, error) { + return redigo.Int64(Do("INCRBY", key, incBy)) +} + +func DecrBy(key string, decrBy int64) (int64, error) { + return redigo.Int64(Do("DECRBY", key)) +} + +func IncrByFloat(key string, incBy float64) (float64, error) { + return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) +} + +func DecrByFloat(key string, decrBy float64) (float64, error) { + return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) +} + +// use for message queue +func LPush(key string, data interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} + +func LPop(key string) (interface{}, error) { + return Do("LPOP", key) +} + +func LPopString(key string) (string, error) { + return redigo.String(Do("LPOP", key)) +} +func LPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("LPOP", key)) + return f, err +} +func LPopInt(key string) (int, error) { + return redigo.Int(Do("LPOP", key)) +} +func LPopInt64(key string) (int64, error) { + return redigo.Int64(Do("LPOP", key)) +} + +func RPush(key string, data interface{}) (interface{}, error) { + // set + return Do("RPUSH", key, data) +} + +func RPop(key string) (interface{}, error) { + return Do("RPOP", key) +} + +func RPopString(key string) (string, error) { + return redigo.String(Do("RPOP", key)) +} +func RPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("RPOP", key)) + return f, err +} +func RPopInt(key string) (int, error) { + return redigo.Int(Do("RPOP", key)) +} +func RPopInt64(key string) (int64, error) { + return redigo.Int64(Do("RPOP", key)) +} + +func Scan(cursor int64, pattern string, count int64) (int64, []string, error) { + var items []string + var newCursor int64 + + values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) + if err != nil { + return 0, nil, err + } + values, err = redigo.Scan(values, &newCursor, &items) + if err != nil { + return 0, nil, err + } + return newCursor, items, nil +} diff --git a/utils/cache/redis_cluster.go b/utils/cache/redis_cluster.go new file mode 100644 index 0000000..901f30c --- /dev/null +++ b/utils/cache/redis_cluster.go @@ -0,0 +1,622 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/go-redis/redis" +) + +var pools *redis.ClusterClient + +func NewRedisCluster(addrs []string) error { + opt := &redis.ClusterOptions{ + Addrs: addrs, + PoolSize: redisPoolSize, + PoolTimeout: redisPoolTTL, + IdleTimeout: redisIdleTTL, + DialTimeout: redisDialTTL, + ReadTimeout: redisReadTTL, + WriteTimeout: redisWriteTTL, + } + pools = redis.NewClusterClient(opt) + if err := pools.Ping().Err(); err != nil { + return err + } + return nil +} + +func RCGet(key string) (interface{}, error) { + res, err := pools.Get(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSet(key string, value interface{}) error { + err := pools.Set(key, value, 0).Err() + return convertError(err) +} +func RCGetSet(key string, value interface{}) (interface{}, error) { + res, err := pools.GetSet(key, value).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSetNx(key string, value interface{}) (int64, error) { + res, err := pools.SetNX(key, value, 0).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCSetEx(key string, value interface{}, timeout int64) error { + _, err := pools.Set(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + return nil +} + +// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 +func RCSetNxEx(key string, value interface{}, timeout int64) error { + res, err := pools.SetNX(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + if res { + return nil + } + return ErrNil +} +func RCMGet(keys ...string) ([]interface{}, error) { + res, err := pools.MGet(keys...).Result() + return res, convertError(err) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func RCMSet(kvs map[string]interface{}) error { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return err + } + pairs = append(pairs, k, val) + } + return convertError(pools.MSet(pairs).Err()) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func RCMSetNX(kvs map[string]interface{}) (bool, error) { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return false, err + } + pairs = append(pairs, k, val) + } + res, err := pools.MSetNX(pairs).Result() + return res, convertError(err) +} +func RCExpireAt(key string, timestamp int64) (int64, error) { + res, err := pools.ExpireAt(key, time.Unix(timestamp, 0)).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCDel(keys ...string) (int64, error) { + args := make([]interface{}, 0, len(keys)) + for _, key := range keys { + args = append(args, key) + } + res, err := pools.Del(keys...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCIncr(key string) (int64, error) { + res, err := pools.Incr(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCIncrBy(key string, delta int64) (int64, error) { + res, err := pools.IncrBy(key, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCExpire(key string, duration int64) (int64, error) { + res, err := pools.Expire(key, time.Duration(duration)*time.Second).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func RCExists(key string) (bool, error) { + res, err := pools.Exists(key).Result() + if err != nil { + return false, convertError(err) + } + if res > 0 { + return true, nil + } + return false, nil +} +func RCHGet(key string, field string) (interface{}, error) { + res, err := pools.HGet(key, field).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCHLen(key string) (int64, error) { + res, err := pools.HLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCHSet(key string, field string, val interface{}) error { + value, err := String(val, nil) + if err != nil && err != ErrNil { + return err + } + _, err = pools.HSet(key, field, value).Result() + if err != nil { + return convertError(err) + } + return nil +} +func RCHDel(key string, fields ...string) (int64, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + res, err := pools.HDel(key, fields...).Result() + if err != nil { + return 0, convertError(err) + } + return res, nil +} + +func RCHMGet(key string, fields ...string) (interface{}, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + if len(fields) == 0 { + return nil, ErrNil + } + res, err := pools.HMGet(key, fields...).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCHMSet(key string, kvs ...interface{}) error { + if len(kvs) == 0 { + return nil + } + if len(kvs)%2 != 0 { + return ErrWrongArgsNum + } + var err error + v := map[string]interface{}{} // todo change + v["field"], err = String(kvs[0], nil) + if err != nil && err != ErrNil { + return err + } + v["value"], err = String(kvs[1], nil) + if err != nil && err != ErrNil { + return err + } + pairs := make([]string, 0, len(kvs)-2) + if len(kvs) > 2 { + for _, kv := range kvs[2:] { + kvString, err := String(kv, nil) + if err != nil && err != ErrNil { + return err + } + pairs = append(pairs, kvString) + } + } + v["paris"] = pairs + _, err = pools.HMSet(key, v).Result() + if err != nil { + return convertError(err) + } + return nil +} + +func RCHKeys(key string) ([]string, error) { + res, err := pools.HKeys(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCHVals(key string) ([]interface{}, error) { + res, err := pools.HVals(key).Result() + if err != nil { + return nil, convertError(err) + } + rs := make([]interface{}, 0, len(res)) + for _, res := range res { + rs = append(rs, res) + } + return rs, nil +} +func RCHGetAll(key string) (map[string]string, error) { + vals, err := pools.HGetAll(key).Result() + if err != nil { + return nil, convertError(err) + } + return vals, nil +} +func RCHIncrBy(key, field string, delta int64) (int64, error) { + res, err := pools.HIncrBy(key, field, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCZAdd(key string, kvs ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(kvs)+1) + args = append(args, key) + args = append(args, kvs...) + if len(kvs) == 0 { + return 0, nil + } + if len(kvs)%2 != 0 { + return 0, ErrWrongArgsNum + } + zs := make([]redis.Z, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + idx := i / 2 + score, err := Float64(kvs[i], nil) + if err != nil && err != ErrNil { + return 0, err + } + zs[idx].Score = score + zs[idx].Member = kvs[i+1] + } + res, err := pools.ZAdd(key, zs...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCZRem(key string, members ...string) (int64, error) { + args := make([]interface{}, 0, len(members)) + args = append(args, key) + for _, member := range members { + args = append(args, member) + } + res, err := pools.ZRem(key, members).Result() + if err != nil { + return res, convertError(err) + } + return res, err +} + +func RCZRange(key string, min, max int64, withScores bool) (interface{}, error) { + res := make([]interface{}, 0) + if withScores { + zs, err := pools.ZRangeWithScores(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, z := range zs { + res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) + } + } else { + ms, err := pools.ZRange(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, m := range ms { + res = append(res, m) + } + } + return res, nil +} +func RCZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { + opt := new(redis.ZRangeBy) + opt.Min = strconv.FormatInt(int64(min), 10) + opt.Max = strconv.FormatInt(int64(max), 10) + opt.Count = -1 + opt.Offset = 0 + vals, err := pools.ZRangeByScoreWithScores(key, *opt).Result() + if err != nil { + return nil, convertError(err) + } + res := make(map[string]int64, len(vals)) + for _, val := range vals { + key, err := String(val.Member, nil) + if err != nil && err != ErrNil { + return nil, err + } + res[key] = int64(val.Score) + } + return res, nil +} +func RCLRange(key string, start, stop int64) (interface{}, error) { + res, err := pools.LRange(key, start, stop).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCLSet(key string, index int, value interface{}) error { + err := pools.LSet(key, int64(index), value).Err() + return convertError(err) +} +func RCLLen(key string) (int64, error) { + res, err := pools.LLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCLRem(key string, count int, value interface{}) (int, error) { + val, _ := value.(string) + res, err := pools.LRem(key, int64(count), val).Result() + if err != nil { + return int(res), convertError(err) + } + return int(res), nil +} +func RCTTl(key string) (int64, error) { + duration, err := pools.TTL(key).Result() + if err != nil { + return int64(duration.Seconds()), convertError(err) + } + return int64(duration.Seconds()), nil +} +func RCLPop(key string) (interface{}, error) { + res, err := pools.LPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCRPop(key string) (interface{}, error) { + res, err := pools.RPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCBLPop(key string, timeout int) (interface{}, error) { + res, err := pools.BLPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, err + } + return res[1], nil +} +func RCBRPop(key string, timeout int) (interface{}, error) { + res, err := pools.BRPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, convertError(err) + } + return res[1], nil +} +func RCLPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + return err + } + vals = append(vals, val) + } + _, err := pools.LPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} +func RCRPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + if err == ErrNil { + continue + } + return err + } + if val == "" { + continue + } + vals = append(vals, val) + } + _, err := pools.RPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func RCBRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { + res, err := pools.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func RCRPopLPush(srcKey string, destKey string) (interface{}, error) { + res, err := pools.RPopLPush(srcKey, destKey).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCSAdd(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := pools.SAdd(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSPop(key string) ([]byte, error) { + res, err := pools.SPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func RCSIsMember(key string, member interface{}) (bool, error) { + m, _ := member.(string) + res, err := pools.SIsMember(key, m).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSRem(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := pools.SRem(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSMembers(key string) ([]string, error) { + res, err := pools.SMembers(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCScriptLoad(luaScript string) (interface{}, error) { + res, err := pools.ScriptLoad(luaScript).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCEvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, sha1, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := pools.EvalSha(sha1, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCEval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, luaScript, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := pools.Eval(luaScript, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func RCGetBit(key string, offset int64) (int64, error) { + res, err := pools.GetBit(key, offset).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func RCSetBit(key string, offset uint32, value int) (int, error) { + res, err := pools.SetBit(key, int64(offset), value).Result() + return int(res), convertError(err) +} +func RCGetClient() *redis.ClusterClient { + return pools +} +func convertError(err error) error { + if err == redis.Nil { + // 为了兼容redis 2.x,这里不返回 ErrNil,ErrNil在调用redis_cluster_reply函数时才返回 + return nil + } + return err +} diff --git a/utils/cache/redis_pool.go b/utils/cache/redis_pool.go new file mode 100644 index 0000000..ca38b3f --- /dev/null +++ b/utils/cache/redis_pool.go @@ -0,0 +1,324 @@ +package cache + +import ( + "errors" + "log" + "strings" + "time" + + redigo "github.com/gomodule/redigo/redis" +) + +type RedisPool struct { + *redigo.Pool +} + +func NewRedisPool(cfg *Config) *RedisPool { + return &RedisPool{&redigo.Pool{ + MaxIdle: cfg.MaxIdle, + IdleTimeout: cfg.IdleTimeout, + MaxActive: cfg.MaxActive, + Wait: cfg.Wait, + Dial: func() (redigo.Conn, error) { + c, err := redigo.Dial("tcp", cfg.Server) + if err != nil { + log.Println("Redis Dial failed: ", err) + return nil, err + } + if cfg.Password != "" { + if _, err := c.Do("AUTH", cfg.Password); err != nil { + c.Close() + log.Println("Redis AUTH failed: ", err) + return nil, err + } + } + return c, err + }, + TestOnBorrow: func(c redigo.Conn, t time.Time) error { + _, err := c.Do("PING") + if err != nil { + log.Println("Unable to ping to redis server:", err) + } + return err + }, + }} +} + +func (p *RedisPool) Do(cmd string, args ...interface{}) (reply interface{}, err error) { + conn := pool.Get() + defer conn.Close() + return conn.Do(cmd, args...) +} + +func (p *RedisPool) GetPool() *redigo.Pool { + return pool +} + +func (p *RedisPool) ParseKey(key string, vars []string) (string, error) { + arr := strings.Split(key, conf.KeyPlaceholder) + actualKey := "" + if len(arr) != len(vars)+1 { + return "", errors.New("redis/connection.go: Insufficient arguments to parse key") + } else { + for index, val := range arr { + if index == 0 { + actualKey = arr[index] + } else { + actualKey += vars[index-1] + val + } + } + } + return getPrefixedKey(actualKey), nil +} + +func (p *RedisPool) getPrefixedKey(key string) string { + return conf.KeyPrefix + conf.KeyDelimiter + key +} +func (p *RedisPool) StripEnvKey(key string) string { + return strings.TrimLeft(key, conf.KeyPrefix+conf.KeyDelimiter) +} +func (p *RedisPool) SplitKey(key string) []string { + return strings.Split(key, conf.KeyDelimiter) +} +func (p *RedisPool) Expire(key string, ttl int) (interface{}, error) { + return Do("EXPIRE", key, ttl) +} +func (p *RedisPool) Persist(key string) (interface{}, error) { + return Do("PERSIST", key) +} + +func (p *RedisPool) Del(key string) (interface{}, error) { + return Do("DEL", key) +} +func (p *RedisPool) Set(key string, data interface{}) (interface{}, error) { + // set + return Do("SET", key, data) +} +func (p *RedisPool) SetNX(key string, data interface{}) (interface{}, error) { + return Do("SETNX", key, data) +} +func (p *RedisPool) SetEx(key string, data interface{}, ttl int) (interface{}, error) { + return Do("SETEX", key, ttl, data) +} +func (p *RedisPool) Get(key string) (interface{}, error) { + // get + return Do("GET", key) +} +func (p *RedisPool) GetStringMap(key string) (map[string]string, error) { + // get + return redigo.StringMap(Do("GET", key)) +} + +func (p *RedisPool) GetTTL(key string) (time.Duration, error) { + ttl, err := redigo.Int64(Do("TTL", key)) + return time.Duration(ttl) * time.Second, err +} +func (p *RedisPool) GetBytes(key string) ([]byte, error) { + return redigo.Bytes(Do("GET", key)) +} +func (p *RedisPool) GetString(key string) (string, error) { + return redigo.String(Do("GET", key)) +} +func (p *RedisPool) GetInt(key string) (int, error) { + return redigo.Int(Do("GET", key)) +} +func (p *RedisPool) GetStringLength(key string) (int, error) { + return redigo.Int(Do("STRLEN", key)) +} +func (p *RedisPool) ZAdd(key string, score float64, data interface{}) (interface{}, error) { + return Do("ZADD", key, score, data) +} +func (p *RedisPool) ZRem(key string, data interface{}) (interface{}, error) { + return Do("ZREM", key, data) +} +func (p *RedisPool) ZRange(key string, start int, end int, withScores bool) ([]interface{}, error) { + if withScores { + return redigo.Values(Do("ZRANGE", key, start, end, "WITHSCORES")) + } + return redigo.Values(Do("ZRANGE", key, start, end)) +} +func (p *RedisPool) SAdd(setName string, data interface{}) (interface{}, error) { + return Do("SADD", setName, data) +} +func (p *RedisPool) SCard(setName string) (int64, error) { + return redigo.Int64(Do("SCARD", setName)) +} +func (p *RedisPool) SIsMember(setName string, data interface{}) (bool, error) { + return redigo.Bool(Do("SISMEMBER", setName, data)) +} +func (p *RedisPool) SMembers(setName string) ([]string, error) { + return redigo.Strings(Do("SMEMBERS", setName)) +} +func (p *RedisPool) SRem(setName string, data interface{}) (interface{}, error) { + return Do("SREM", setName, data) +} +func (p *RedisPool) HSet(key string, HKey string, data interface{}) (interface{}, error) { + return Do("HSET", key, HKey, data) +} + +func (p *RedisPool) HGet(key string, HKey string) (interface{}, error) { + return Do("HGET", key, HKey) +} + +func (p *RedisPool) HMGet(key string, hashKeys ...string) ([]interface{}, error) { + ret, err := Do("HMGET", key, hashKeys) + if err != nil { + return nil, err + } + reta, ok := ret.([]interface{}) + if !ok { + return nil, errors.New("result not an array") + } + return reta, nil +} + +func (p *RedisPool) HMSet(key string, hashKeys []string, vals []interface{}) (interface{}, error) { + if len(hashKeys) == 0 || len(hashKeys) != len(vals) { + var ret interface{} + return ret, errors.New("bad length") + } + input := []interface{}{key} + for i, v := range hashKeys { + input = append(input, v, vals[i]) + } + return Do("HMSET", input...) +} + +func (p *RedisPool) HGetString(key string, HKey string) (string, error) { + return redigo.String(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetFloat(key string, HKey string) (float64, error) { + f, err := redigo.Float64(Do("HGET", key, HKey)) + return float64(f), err +} +func (p *RedisPool) HGetInt(key string, HKey string) (int, error) { + return redigo.Int(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetInt64(key string, HKey string) (int64, error) { + return redigo.Int64(Do("HGET", key, HKey)) +} +func (p *RedisPool) HGetBool(key string, HKey string) (bool, error) { + return redigo.Bool(Do("HGET", key, HKey)) +} +func (p *RedisPool) HDel(key string, HKey string) (interface{}, error) { + return Do("HDEL", key, HKey) +} +func (p *RedisPool) HGetAll(key string) (map[string]interface{}, error) { + vals, err := redigo.Values(Do("HGETALL", key)) + if err != nil { + return nil, err + } + num := len(vals) / 2 + result := make(map[string]interface{}, num) + for i := 0; i < num; i++ { + key, _ := redigo.String(vals[2*i], nil) + result[key] = vals[2*i+1] + } + return result, nil +} + +// NOTE: Use this in production environment with extreme care. +// Read more here:https://redigo.io/commands/keys +func (p *RedisPool) Keys(pattern string) ([]string, error) { + return redigo.Strings(Do("KEYS", pattern)) +} + +func (p *RedisPool) HKeys(key string) ([]string, error) { + return redigo.Strings(Do("HKEYS", key)) +} + +func (p *RedisPool) Exists(key string) (bool, error) { + count, err := redigo.Int(Do("EXISTS", key)) + if count == 0 { + return false, err + } else { + return true, err + } +} + +func (p *RedisPool) Incr(key string) (int64, error) { + return redigo.Int64(Do("INCR", key)) +} + +func (p *RedisPool) Decr(key string) (int64, error) { + return redigo.Int64(Do("DECR", key)) +} + +func (p *RedisPool) IncrBy(key string, incBy int64) (int64, error) { + return redigo.Int64(Do("INCRBY", key, incBy)) +} + +func (p *RedisPool) DecrBy(key string, decrBy int64) (int64, error) { + return redigo.Int64(Do("DECRBY", key)) +} + +func (p *RedisPool) IncrByFloat(key string, incBy float64) (float64, error) { + return redigo.Float64(Do("INCRBYFLOAT", key, incBy)) +} + +func (p *RedisPool) DecrByFloat(key string, decrBy float64) (float64, error) { + return redigo.Float64(Do("DECRBYFLOAT", key, decrBy)) +} + +// use for message queue +func (p *RedisPool) LPush(key string, data interface{}) (interface{}, error) { + // set + return Do("LPUSH", key, data) +} + +func (p *RedisPool) LPop(key string) (interface{}, error) { + return Do("LPOP", key) +} + +func (p *RedisPool) LPopString(key string) (string, error) { + return redigo.String(Do("LPOP", key)) +} +func (p *RedisPool) LPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("LPOP", key)) + return float64(f), err +} +func (p *RedisPool) LPopInt(key string) (int, error) { + return redigo.Int(Do("LPOP", key)) +} +func (p *RedisPool) LPopInt64(key string) (int64, error) { + return redigo.Int64(Do("LPOP", key)) +} + +func (p *RedisPool) RPush(key string, data interface{}) (interface{}, error) { + // set + return Do("RPUSH", key, data) +} + +func (p *RedisPool) RPop(key string) (interface{}, error) { + return Do("RPOP", key) +} + +func (p *RedisPool) RPopString(key string) (string, error) { + return redigo.String(Do("RPOP", key)) +} +func (p *RedisPool) RPopFloat(key string) (float64, error) { + f, err := redigo.Float64(Do("RPOP", key)) + return float64(f), err +} +func (p *RedisPool) RPopInt(key string) (int, error) { + return redigo.Int(Do("RPOP", key)) +} +func (p *RedisPool) RPopInt64(key string) (int64, error) { + return redigo.Int64(Do("RPOP", key)) +} + +func (p *RedisPool) Scan(cursor int64, pattern string, count int64) (int64, []string, error) { + var items []string + var newCursor int64 + + values, err := redigo.Values(Do("SCAN", cursor, "MATCH", pattern, "COUNT", count)) + if err != nil { + return 0, nil, err + } + values, err = redigo.Scan(values, &newCursor, &items) + if err != nil { + return 0, nil, err + } + + return newCursor, items, nil +} diff --git a/utils/cache/redis_pool_cluster.go b/utils/cache/redis_pool_cluster.go new file mode 100644 index 0000000..cd1911b --- /dev/null +++ b/utils/cache/redis_pool_cluster.go @@ -0,0 +1,617 @@ +package cache + +import ( + "strconv" + "time" + + "github.com/go-redis/redis" +) + +type RedisClusterPool struct { + client *redis.ClusterClient +} + +func NewRedisClusterPool(addrs []string) (*RedisClusterPool, error) { + opt := &redis.ClusterOptions{ + Addrs: addrs, + PoolSize: 512, + PoolTimeout: 10 * time.Second, + IdleTimeout: 10 * time.Second, + DialTimeout: 10 * time.Second, + ReadTimeout: 3 * time.Second, + WriteTimeout: 3 * time.Second, + } + c := redis.NewClusterClient(opt) + if err := c.Ping().Err(); err != nil { + return nil, err + } + return &RedisClusterPool{client: c}, nil +} + +func (p *RedisClusterPool) Get(key string) (interface{}, error) { + res, err := p.client.Get(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) Set(key string, value interface{}) error { + err := p.client.Set(key, value, 0).Err() + return convertError(err) +} +func (p *RedisClusterPool) GetSet(key string, value interface{}) (interface{}, error) { + res, err := p.client.GetSet(key, value).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) SetNx(key string, value interface{}) (int64, error) { + res, err := p.client.SetNX(key, value, 0).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) SetEx(key string, value interface{}, timeout int64) error { + _, err := p.client.Set(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + return nil +} + +// nil表示成功,ErrNil表示数据库内已经存在这个key,其他表示数据库发生错误 +func (p *RedisClusterPool) SetNxEx(key string, value interface{}, timeout int64) error { + res, err := p.client.SetNX(key, value, time.Duration(timeout)*time.Second).Result() + if err != nil { + return convertError(err) + } + if res { + return nil + } + return ErrNil +} +func (p *RedisClusterPool) MGet(keys ...string) ([]interface{}, error) { + res, err := p.client.MGet(keys...).Result() + return res, convertError(err) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func (p *RedisClusterPool) MSet(kvs map[string]interface{}) error { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return err + } + pairs = append(pairs, k, val) + } + return convertError(p.client.MSet(pairs).Err()) +} + +// 为确保多个key映射到同一个slot,每个key最好加上hash tag,如:{test} +func (p *RedisClusterPool) MSetNX(kvs map[string]interface{}) (bool, error) { + pairs := make([]string, 0, len(kvs)*2) + for k, v := range kvs { + val, err := String(v, nil) + if err != nil { + return false, err + } + pairs = append(pairs, k, val) + } + res, err := p.client.MSetNX(pairs).Result() + return res, convertError(err) +} +func (p *RedisClusterPool) ExpireAt(key string, timestamp int64) (int64, error) { + res, err := p.client.ExpireAt(key, time.Unix(timestamp, 0)).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) Del(keys ...string) (int64, error) { + args := make([]interface{}, 0, len(keys)) + for _, key := range keys { + args = append(args, key) + } + res, err := p.client.Del(keys...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Incr(key string) (int64, error) { + res, err := p.client.Incr(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) IncrBy(key string, delta int64) (int64, error) { + res, err := p.client.IncrBy(key, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Expire(key string, duration int64) (int64, error) { + res, err := p.client.Expire(key, time.Duration(duration)*time.Second).Result() + if err != nil { + return 0, convertError(err) + } + if res { + return 1, nil + } + return 0, nil +} +func (p *RedisClusterPool) Exists(key string) (bool, error) { // todo (bool, error) + res, err := p.client.Exists(key).Result() + if err != nil { + return false, convertError(err) + } + if res > 0 { + return true, nil + } + return false, nil +} +func (p *RedisClusterPool) HGet(key string, field string) (interface{}, error) { + res, err := p.client.HGet(key, field).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) HLen(key string) (int64, error) { + res, err := p.client.HLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HSet(key string, field string, val interface{}) error { + value, err := String(val, nil) + if err != nil && err != ErrNil { + return err + } + _, err = p.client.HSet(key, field, value).Result() + if err != nil { + return convertError(err) + } + return nil +} +func (p *RedisClusterPool) HDel(key string, fields ...string) (int64, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + res, err := p.client.HDel(key, fields...).Result() + if err != nil { + return 0, convertError(err) + } + return res, nil +} + +func (p *RedisClusterPool) HMGet(key string, fields ...string) (interface{}, error) { + args := make([]interface{}, 0, len(fields)+1) + args = append(args, key) + for _, field := range fields { + args = append(args, field) + } + if len(fields) == 0 { + return nil, ErrNil + } + res, err := p.client.HMGet(key, fields...).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HMSet(key string, kvs ...interface{}) error { + if len(kvs) == 0 { + return nil + } + if len(kvs)%2 != 0 { + return ErrWrongArgsNum + } + var err error + v := map[string]interface{}{} // todo change + v["field"], err = String(kvs[0], nil) + if err != nil && err != ErrNil { + return err + } + v["value"], err = String(kvs[1], nil) + if err != nil && err != ErrNil { + return err + } + pairs := make([]string, 0, len(kvs)-2) + if len(kvs) > 2 { + for _, kv := range kvs[2:] { + kvString, err := String(kv, nil) + if err != nil && err != ErrNil { + return err + } + pairs = append(pairs, kvString) + } + } + v["paris"] = pairs + _, err = p.client.HMSet(key, v).Result() + if err != nil { + return convertError(err) + } + return nil +} + +func (p *RedisClusterPool) HKeys(key string) ([]string, error) { + res, err := p.client.HKeys(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) HVals(key string) ([]interface{}, error) { + res, err := p.client.HVals(key).Result() + if err != nil { + return nil, convertError(err) + } + rs := make([]interface{}, 0, len(res)) + for _, res := range res { + rs = append(rs, res) + } + return rs, nil +} +func (p *RedisClusterPool) HGetAll(key string) (map[string]string, error) { + vals, err := p.client.HGetAll(key).Result() + if err != nil { + return nil, convertError(err) + } + return vals, nil +} +func (p *RedisClusterPool) HIncrBy(key, field string, delta int64) (int64, error) { + res, err := p.client.HIncrBy(key, field, delta).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ZAdd(key string, kvs ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(kvs)+1) + args = append(args, key) + args = append(args, kvs...) + if len(kvs) == 0 { + return 0, nil + } + if len(kvs)%2 != 0 { + return 0, ErrWrongArgsNum + } + zs := make([]redis.Z, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + idx := i / 2 + score, err := Float64(kvs[i], nil) + if err != nil && err != ErrNil { + return 0, err + } + zs[idx].Score = score + zs[idx].Member = kvs[i+1] + } + res, err := p.client.ZAdd(key, zs...).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ZRem(key string, members ...string) (int64, error) { + args := make([]interface{}, 0, len(members)) + args = append(args, key) + for _, member := range members { + args = append(args, member) + } + res, err := p.client.ZRem(key, members).Result() + if err != nil { + return res, convertError(err) + } + return res, err +} + +func (p *RedisClusterPool) ZRange(key string, min, max int64, withScores bool) (interface{}, error) { + res := make([]interface{}, 0) + if withScores { + zs, err := p.client.ZRangeWithScores(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, z := range zs { + res = append(res, z.Member, strconv.FormatFloat(z.Score, 'f', -1, 64)) + } + } else { + ms, err := p.client.ZRange(key, min, max).Result() + if err != nil { + return nil, convertError(err) + } + for _, m := range ms { + res = append(res, m) + } + } + return res, nil +} +func (p *RedisClusterPool) ZRangeByScoreWithScore(key string, min, max int64) (map[string]int64, error) { + opt := new(redis.ZRangeBy) + opt.Min = strconv.FormatInt(int64(min), 10) + opt.Max = strconv.FormatInt(int64(max), 10) + opt.Count = -1 + opt.Offset = 0 + vals, err := p.client.ZRangeByScoreWithScores(key, *opt).Result() + if err != nil { + return nil, convertError(err) + } + res := make(map[string]int64, len(vals)) + for _, val := range vals { + key, err := String(val.Member, nil) + if err != nil && err != ErrNil { + return nil, err + } + res[key] = int64(val.Score) + } + return res, nil +} +func (p *RedisClusterPool) LRange(key string, start, stop int64) (interface{}, error) { + res, err := p.client.LRange(key, start, stop).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) LSet(key string, index int, value interface{}) error { + err := p.client.LSet(key, int64(index), value).Err() + return convertError(err) +} +func (p *RedisClusterPool) LLen(key string) (int64, error) { + res, err := p.client.LLen(key).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) LRem(key string, count int, value interface{}) (int, error) { + val, _ := value.(string) + res, err := p.client.LRem(key, int64(count), val).Result() + if err != nil { + return int(res), convertError(err) + } + return int(res), nil +} +func (p *RedisClusterPool) TTl(key string) (int64, error) { + duration, err := p.client.TTL(key).Result() + if err != nil { + return int64(duration.Seconds()), convertError(err) + } + return int64(duration.Seconds()), nil +} +func (p *RedisClusterPool) LPop(key string) (interface{}, error) { + res, err := p.client.LPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) RPop(key string) (interface{}, error) { + res, err := p.client.RPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) BLPop(key string, timeout int) (interface{}, error) { + res, err := p.client.BLPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, err + } + return res[1], nil +} +func (p *RedisClusterPool) BRPop(key string, timeout int) (interface{}, error) { + res, err := p.client.BRPop(time.Duration(timeout)*time.Second, key).Result() + if err != nil { + // 兼容redis 2.x + if err == redis.Nil { + return nil, ErrNil + } + return nil, convertError(err) + } + return res[1], nil +} +func (p *RedisClusterPool) LPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + return err + } + vals = append(vals, val) + } + _, err := p.client.LPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} +func (p *RedisClusterPool) RPush(key string, value ...interface{}) error { + args := make([]interface{}, 0, len(value)+1) + args = append(args, key) + args = append(args, value...) + vals := make([]string, 0, len(value)) + for _, v := range value { + val, err := String(v, nil) + if err != nil && err != ErrNil { + if err == ErrNil { + continue + } + return err + } + if val == "" { + continue + } + vals = append(vals, val) + } + _, err := p.client.RPush(key, vals).Result() // todo ... + if err != nil { + return convertError(err) + } + return nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func (p *RedisClusterPool) BRPopLPush(srcKey string, destKey string, timeout int) (interface{}, error) { + res, err := p.client.BRPopLPush(srcKey, destKey, time.Duration(timeout)*time.Second).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} + +// 为确保srcKey跟destKey映射到同一个slot,srcKey和destKey需要加上hash tag,如:{test} +func (p *RedisClusterPool) RPopLPush(srcKey string, destKey string) (interface{}, error) { + res, err := p.client.RPopLPush(srcKey, destKey).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SAdd(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := p.client.SAdd(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SPop(key string) ([]byte, error) { + res, err := p.client.SPop(key).Result() + if err != nil { + return nil, convertError(err) + } + return []byte(res), nil +} +func (p *RedisClusterPool) SIsMember(key string, member interface{}) (bool, error) { + m, _ := member.(string) + res, err := p.client.SIsMember(key, m).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SRem(key string, members ...interface{}) (int64, error) { + args := make([]interface{}, 0, len(members)+1) + args = append(args, key) + args = append(args, members...) + ms := make([]string, 0, len(members)) + for _, member := range members { + m, err := String(member, nil) + if err != nil && err != ErrNil { + return 0, err + } + ms = append(ms, m) + } + res, err := p.client.SRem(key, ms).Result() // todo ... + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SMembers(key string) ([]string, error) { + res, err := p.client.SMembers(key).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) ScriptLoad(luaScript string) (interface{}, error) { + res, err := p.client.ScriptLoad(luaScript).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) EvalSha(sha1 string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, sha1, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := p.client.EvalSha(sha1, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) Eval(luaScript string, numberKeys int, keysArgs ...interface{}) (interface{}, error) { + vals := make([]interface{}, 0, len(keysArgs)+2) + vals = append(vals, luaScript, numberKeys) + vals = append(vals, keysArgs...) + keys := make([]string, 0, numberKeys) + args := make([]string, 0, len(keysArgs)-numberKeys) + for i, value := range keysArgs { + val, err := String(value, nil) + if err != nil && err != ErrNil { + return nil, err + } + if i < numberKeys { + keys = append(keys, val) + } else { + args = append(args, val) + } + } + res, err := p.client.Eval(luaScript, keys, args).Result() + if err != nil { + return nil, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) GetBit(key string, offset int64) (int64, error) { + res, err := p.client.GetBit(key, offset).Result() + if err != nil { + return res, convertError(err) + } + return res, nil +} +func (p *RedisClusterPool) SetBit(key string, offset uint32, value int) (int, error) { + res, err := p.client.SetBit(key, int64(offset), value).Result() + return int(res), convertError(err) +} +func (p *RedisClusterPool) GetClient() *redis.ClusterClient { + return pools +} diff --git a/utils/convert.go b/utils/convert.go new file mode 100644 index 0000000..ded0d90 --- /dev/null +++ b/utils/convert.go @@ -0,0 +1,366 @@ +package zhios_pay_utils + +import ( + "encoding/binary" + "encoding/json" + "fmt" + "math" + "strconv" + "strings" +) + +func ToString(raw interface{}, e error) (res string) { + if e != nil { + return "" + } + return AnyToString(raw) +} + +func ToInt64(raw interface{}, e error) int64 { + if e != nil { + return 0 + } + return AnyToInt64(raw) +} + +func AnyToBool(raw interface{}) bool { + switch i := raw.(type) { + case float32, float64, int, int64, uint, uint8, uint16, uint32, uint64, int8, int16, int32: + return i != 0 + case []byte: + return i != nil + case string: + if i == "false" { + return false + } + return i != "" + case error: + return false + case nil: + return true + } + val := fmt.Sprint(raw) + val = strings.TrimLeft(val, "&") + if strings.TrimLeft(val, "{}") == "" { + return false + } + if strings.TrimLeft(val, "[]") == "" { + return false + } + // ptr type + b, err := json.Marshal(raw) + if err != nil { + return false + } + if strings.TrimLeft(string(b), "\"\"") == "" { + return false + } + if strings.TrimLeft(string(b), "{}") == "" { + return false + } + return true +} + +func AnyToInt64(raw interface{}) int64 { + switch i := raw.(type) { + case string: + res, _ := strconv.ParseInt(i, 10, 64) + return res + case []byte: + return BytesToInt64(i) + case int: + return int64(i) + case int64: + return i + case uint: + return int64(i) + case uint8: + return int64(i) + case uint16: + return int64(i) + case uint32: + return int64(i) + case uint64: + return int64(i) + case int8: + return int64(i) + case int16: + return int64(i) + case int32: + return int64(i) + case float32: + return int64(i) + case float64: + return int64(i) + case error: + return 0 + case bool: + if i { + return 1 + } + return 0 + } + return 0 +} + +func AnyToString(raw interface{}) string { + switch i := raw.(type) { + case []byte: + return string(i) + case int: + return strconv.FormatInt(int64(i), 10) + case int64: + return strconv.FormatInt(i, 10) + case float32: + return Float64ToStr(float64(i)) + case float64: + return Float64ToStr(i) + case uint: + return strconv.FormatInt(int64(i), 10) + case uint8: + return strconv.FormatInt(int64(i), 10) + case uint16: + return strconv.FormatInt(int64(i), 10) + case uint32: + return strconv.FormatInt(int64(i), 10) + case uint64: + return strconv.FormatInt(int64(i), 10) + case int8: + return strconv.FormatInt(int64(i), 10) + case int16: + return strconv.FormatInt(int64(i), 10) + case int32: + return strconv.FormatInt(int64(i), 10) + case string: + return i + case error: + return i.Error() + case bool: + return strconv.FormatBool(i) + } + return fmt.Sprintf("%#v", raw) +} + +func AnyToFloat64(raw interface{}) float64 { + switch i := raw.(type) { + case []byte: + f, _ := strconv.ParseFloat(string(i), 64) + return f + case int: + return float64(i) + case int64: + return float64(i) + case float32: + return float64(i) + case float64: + return i + case uint: + return float64(i) + case uint8: + return float64(i) + case uint16: + return float64(i) + case uint32: + return float64(i) + case uint64: + return float64(i) + case int8: + return float64(i) + case int16: + return float64(i) + case int32: + return float64(i) + case string: + f, _ := strconv.ParseFloat(i, 64) + return f + case bool: + if i { + return 1 + } + } + return 0 +} + +func ToByte(raw interface{}, e error) []byte { + if e != nil { + return []byte{} + } + switch i := raw.(type) { + case string: + return []byte(i) + case int: + return Int64ToBytes(int64(i)) + case int64: + return Int64ToBytes(i) + case float32: + return Float32ToByte(i) + case float64: + return Float64ToByte(i) + case uint: + return Int64ToBytes(int64(i)) + case uint8: + return Int64ToBytes(int64(i)) + case uint16: + return Int64ToBytes(int64(i)) + case uint32: + return Int64ToBytes(int64(i)) + case uint64: + return Int64ToBytes(int64(i)) + case int8: + return Int64ToBytes(int64(i)) + case int16: + return Int64ToBytes(int64(i)) + case int32: + return Int64ToBytes(int64(i)) + case []byte: + return i + case error: + return []byte(i.Error()) + case bool: + if i { + return []byte("true") + } + return []byte("false") + } + return []byte(fmt.Sprintf("%#v", raw)) +} + +func Int64ToBytes(i int64) []byte { + var buf = make([]byte, 8) + binary.BigEndian.PutUint64(buf, uint64(i)) + return buf +} + +func BytesToInt64(buf []byte) int64 { + return int64(binary.BigEndian.Uint64(buf)) +} + +func StrToInt(s string) int { + res, _ := strconv.Atoi(s) + return res +} + +func StrToInt64(s string) int64 { + res, _ := strconv.ParseInt(s, 10, 64) + return res +} + +func Float32ToByte(float float32) []byte { + bits := math.Float32bits(float) + bytes := make([]byte, 4) + binary.LittleEndian.PutUint32(bytes, bits) + + return bytes +} + +func ByteToFloat32(bytes []byte) float32 { + bits := binary.LittleEndian.Uint32(bytes) + return math.Float32frombits(bits) +} + +func Float64ToByte(float float64) []byte { + bits := math.Float64bits(float) + bytes := make([]byte, 8) + binary.LittleEndian.PutUint64(bytes, bits) + return bytes +} + +func ByteToFloat64(bytes []byte) float64 { + bits := binary.LittleEndian.Uint64(bytes) + return math.Float64frombits(bits) +} + +func Float64ToStr(f float64) string { + return strconv.FormatFloat(f, 'f', 2, 64) +} +func Float64ToStrPrec1(f float64) string { + return strconv.FormatFloat(f, 'f', 1, 64) +} +func Float64ToStrByPrec(f float64, prec int) string { + return strconv.FormatFloat(f, 'f', prec, 64) +} + +func Float32ToStr(f float32) string { + return Float64ToStr(float64(f)) +} + +func StrToFloat64(s string) float64 { + res, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return res +} +func StrToFormat(s string, prec int) string { + ex := strings.Split(s, ".") + if len(ex) == 2 { + if StrToFloat64(ex[1]) == 0 { //小数点后面为空就是不要小数点了 + return ex[0] + } + //看取多少位 + str := ex[1] + str1 := str + if prec < len(str) { + str1 = str[0:prec] + } else { + for i := 0; i < prec-len(str); i++ { + str1 += "0" + } + } + if prec > 0 { + return ex[0] + "." + str1 + } else { + return ex[0] + } + } + return s +} + +func StrToFloat32(s string) float32 { + res, err := strconv.ParseFloat(s, 32) + if err != nil { + return 0 + } + return float32(res) +} + +func StrToBool(s string) bool { + b, _ := strconv.ParseBool(s) + return b +} + +func BoolToStr(b bool) string { + if b { + return "true" + } + return "false" +} + +func FloatToInt64(f float64) int64 { + return int64(f) +} + +func IntToStr(i int) string { + return strconv.Itoa(i) +} + +func Int64ToStr(i int64) string { + return strconv.FormatInt(i, 10) +} + +func IntToFloat64(i int) float64 { + s := strconv.Itoa(i) + res, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return res +} +func Int64ToFloat64(i int64) float64 { + s := strconv.FormatInt(i, 10) + res, err := strconv.ParseFloat(s, 64) + if err != nil { + return 0 + } + return res +} diff --git a/utils/curl.go b/utils/curl.go new file mode 100644 index 0000000..fb081a0 --- /dev/null +++ b/utils/curl.go @@ -0,0 +1,209 @@ +package zhios_pay_utils + +import ( + "bytes" + "crypto/tls" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strings" + "time" +) + +var CurlDebug bool + +func CurlGet(router string, header map[string]string) ([]byte, error) { + return curl(http.MethodGet, router, nil, header) +} +func CurlGetJson(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl_new(http.MethodGet, router, body, header) +} + +// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string +func CurlPost(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPost, router, body, header) +} + +func CurlPut(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPut, router, body, header) +} + +// 只支持form 与json 提交, 请留意body的类型, 支持string, []byte, map[string]string +func CurlPatch(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodPatch, router, body, header) +} + +// CurlDelete is curl delete +func CurlDelete(router string, body interface{}, header map[string]string) ([]byte, error) { + return curl(http.MethodDelete, router, body, header) +} + +func curl(method, router string, body interface{}, header map[string]string) ([]byte, error) { + var reqBody io.Reader + contentType := "application/json" + switch v := body.(type) { + case string: + reqBody = strings.NewReader(v) + case []byte: + reqBody = bytes.NewReader(v) + case map[string]string: + val := url.Values{} + for k, v := range v { + val.Set(k, v) + } + reqBody = strings.NewReader(val.Encode()) + contentType = "application/x-www-form-urlencoded" + case map[string]interface{}: + val := url.Values{} + for k, v := range v { + val.Set(k, v.(string)) + } + reqBody = strings.NewReader(val.Encode()) + contentType = "application/x-www-form-urlencoded" + } + if header == nil { + header = map[string]string{"Content-Type": contentType} + } + if _, ok := header["Content-Type"]; !ok { + header["Content-Type"] = contentType + } + resp, er := CurlReq(method, router, reqBody, header) + if er != nil { + return nil, er + } + res, err := ioutil.ReadAll(resp.Body) + if CurlDebug { + blob := SerializeStr(body) + if contentType != "application/json" { + blob = HttpBuild(body) + } + fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", + router, + time.Now().Format("2006-01-02 15:04:05.000"), + method, + contentType, + HttpBuildQuery(header), + blob, + err, + SerializeStr(resp.Header), + string(res), + ) + } + resp.Body.Close() + return res, err +} + +func curl_new(method, router string, body interface{}, header map[string]string) ([]byte, error) { + var reqBody io.Reader + contentType := "application/json" + + if header == nil { + header = map[string]string{"Content-Type": contentType} + } + if _, ok := header["Content-Type"]; !ok { + header["Content-Type"] = contentType + } + resp, er := CurlReq(method, router, reqBody, header) + if er != nil { + return nil, er + } + res, err := ioutil.ReadAll(resp.Body) + if CurlDebug { + blob := SerializeStr(body) + if contentType != "application/json" { + blob = HttpBuild(body) + } + fmt.Printf("\n\n=====================\n[url]: %s\n[time]: %s\n[method]: %s\n[content-type]: %v\n[req_header]: %s\n[req_body]: %#v\n[resp_err]: %v\n[resp_header]: %v\n[resp_body]: %v\n=====================\n\n", + router, + time.Now().Format("2006-01-02 15:04:05.000"), + method, + contentType, + HttpBuildQuery(header), + blob, + err, + SerializeStr(resp.Header), + string(res), + ) + } + resp.Body.Close() + return res, err +} + +func CurlReq(method, router string, reqBody io.Reader, header map[string]string) (*http.Response, error) { + req, _ := http.NewRequest(method, router, reqBody) + if header != nil { + for k, v := range header { + req.Header.Set(k, v) + } + } + // 绕过github等可能因为特征码返回503问题 + // https://www.imwzk.com/posts/2021-03-14-why-i-always-get-503-with-golang/ + defaultCipherSuites := []uint16{0xc02f, 0xc030, 0xc02b, 0xc02c, 0xcca8, 0xcca9, 0xc013, 0xc009, + 0xc014, 0xc00a, 0x009c, 0x009d, 0x002f, 0x0035, 0xc012, 0x000a} + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + CipherSuites: append(defaultCipherSuites[8:], defaultCipherSuites[:8]...), + }, + }, + // 获取301重定向 + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + return client.Do(req) +} + +// 组建get请求参数,sortAsc true为小到大,false为大到小,nil不排序 a=123&b=321 +func HttpBuildQuery(args map[string]string, sortAsc ...bool) string { + str := "" + if len(args) == 0 { + return str + } + if len(sortAsc) > 0 { + keys := make([]string, 0, len(args)) + for k := range args { + keys = append(keys, k) + } + if sortAsc[0] { + sort.Strings(keys) + } else { + sort.Sort(sort.Reverse(sort.StringSlice(keys))) + } + for _, k := range keys { + str += "&" + k + "=" + args[k] + } + } else { + for k, v := range args { + str += "&" + k + "=" + v + } + } + return str[1:] +} + +func HttpBuild(body interface{}, sortAsc ...bool) string { + params := map[string]string{} + if args, ok := body.(map[string]interface{}); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + if args, ok := body.(map[string]string); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + if args, ok := body.(map[string]int); ok { + for k, v := range args { + params[k] = AnyToString(v) + } + return HttpBuildQuery(params, sortAsc...) + } + return AnyToString(body) +} diff --git a/utils/file.go b/utils/file.go new file mode 100644 index 0000000..a1dafed --- /dev/null +++ b/utils/file.go @@ -0,0 +1,22 @@ +package zhios_pay_utils + +import ( + "os" + "path" + "strings" + "time" +) + +// 获取文件后缀 +func FileExt(fname string) string { + return strings.ToLower(strings.TrimLeft(path.Ext(fname), ".")) +} + +func FilePutContents(fileName string, content string) { + fd, _ := os.OpenFile("./tmp/"+fileName+".logs", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + fd_time := time.Now().Format("2006-01-02 15:04:05") + fd_content := strings.Join([]string{"[", fd_time, "] ", content, "\n"}, "") + buf := []byte(fd_content) + fd.Write(buf) + fd.Close() +} diff --git a/utils/logx/log.go b/utils/logx/log.go new file mode 100644 index 0000000..ebfb498 --- /dev/null +++ b/utils/logx/log.go @@ -0,0 +1,245 @@ +package zhios_pay_logx + +import ( + "os" + "strings" + "time" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type LogConfig struct { + AppName string `yaml:"app_name" json:"app_name" toml:"app_name"` + Level string `yaml:"level" json:"level" toml:"level"` + StacktraceLevel string `yaml:"stacktrace_level" json:"stacktrace_level" toml:"stacktrace_level"` + IsStdOut bool `yaml:"is_stdout" json:"is_stdout" toml:"is_stdout"` + TimeFormat string `yaml:"time_format" json:"time_format" toml:"time_format"` // second, milli, nano, standard, iso, + Encoding string `yaml:"encoding" json:"encoding" toml:"encoding"` // console, json + Skip int `yaml:"skip" json:"skip" toml:"skip"` + + IsFileOut bool `yaml:"is_file_out" json:"is_file_out" toml:"is_file_out"` + FileDir string `yaml:"file_dir" json:"file_dir" toml:"file_dir"` + FileName string `yaml:"file_name" json:"file_name" toml:"file_name"` + FileMaxSize int `yaml:"file_max_size" json:"file_max_size" toml:"file_max_size"` + FileMaxAge int `yaml:"file_max_age" json:"file_max_age" toml:"file_max_age"` +} + +var ( + l *LogX = defaultLogger() + conf *LogConfig +) + +// default logger setting +func defaultLogger() *LogX { + conf = &LogConfig{ + Level: "debug", + StacktraceLevel: "error", + IsStdOut: true, + TimeFormat: "standard", + Encoding: "console", + Skip: 2, + } + writers := []zapcore.WriteSyncer{os.Stdout} + lg, lv := newZapLogger(setLogLevel(conf.Level), setLogLevel(conf.StacktraceLevel), conf.Encoding, conf.TimeFormat, conf.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + return &LogX{logger: lg, atomLevel: lv} +} + +// initial standard log, if you don't init, it will use default logger setting +func InitDefaultLogger(cfg *LogConfig) { + var writers []zapcore.WriteSyncer + if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { + writers = append(writers, os.Stdout) + } + if cfg.IsFileOut { + writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) + } + + lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + if cfg.AppName != "" { + lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 + } + l = &LogX{logger: lg, atomLevel: lv} +} + +// create a new logger +func NewLogger(cfg *LogConfig) *LogX { + var writers []zapcore.WriteSyncer + if cfg.IsStdOut || (!cfg.IsStdOut && !cfg.IsFileOut) { + writers = append(writers, os.Stdout) + } + if cfg.IsFileOut { + writers = append(writers, NewRollingFile(cfg.FileDir, cfg.FileName, cfg.FileMaxSize, cfg.FileMaxAge)) + } + + lg, lv := newZapLogger(setLogLevel(cfg.Level), setLogLevel(cfg.StacktraceLevel), cfg.Encoding, cfg.TimeFormat, cfg.Skip, zapcore.NewMultiWriteSyncer(writers...)) + zap.RedirectStdLog(lg) + if cfg.AppName != "" { + lg = lg.With(zap.String("app", cfg.AppName)) // 加上应用名称 + } + return &LogX{logger: lg, atomLevel: lv} +} + +// create a new zaplog logger +func newZapLogger(level, stacktrace zapcore.Level, encoding, timeType string, skip int, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) { + encCfg := zapcore.EncoderConfig{ + TimeKey: "T", + LevelKey: "L", + NameKey: "N", + CallerKey: "C", + MessageKey: "M", + StacktraceKey: "S", + LineEnding: zapcore.DefaultLineEnding, + EncodeCaller: zapcore.ShortCallerEncoder, + EncodeDuration: zapcore.NanosDurationEncoder, + EncodeLevel: zapcore.LowercaseLevelEncoder, + } + setTimeFormat(timeType, &encCfg) // set time type + atmLvl := zap.NewAtomicLevel() // set level + atmLvl.SetLevel(level) + encoder := zapcore.NewJSONEncoder(encCfg) // 确定encoder格式 + if encoding == "console" { + encoder = zapcore.NewConsoleEncoder(encCfg) + } + return zap.New(zapcore.NewCore(encoder, output, atmLvl), zap.AddCaller(), zap.AddStacktrace(stacktrace), zap.AddCallerSkip(skip)), &atmLvl +} + +// set log level +func setLogLevel(lvl string) zapcore.Level { + switch strings.ToLower(lvl) { + case "panic": + return zapcore.PanicLevel + case "fatal": + return zapcore.FatalLevel + case "error": + return zapcore.ErrorLevel + case "warn", "warning": + return zapcore.WarnLevel + case "info": + return zapcore.InfoLevel + default: + return zapcore.DebugLevel + } +} + +// set time format +func setTimeFormat(timeType string, z *zapcore.EncoderConfig) { + switch strings.ToLower(timeType) { + case "iso": // iso8601 standard + z.EncodeTime = zapcore.ISO8601TimeEncoder + case "sec": // only for unix second, without millisecond + z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendInt64(t.Unix()) + } + case "second": // unix second, with millisecond + z.EncodeTime = zapcore.EpochTimeEncoder + case "milli", "millisecond": // millisecond + z.EncodeTime = zapcore.EpochMillisTimeEncoder + case "nano", "nanosecond": // nanosecond + z.EncodeTime = zapcore.EpochNanosTimeEncoder + default: // standard format + z.EncodeTime = func(t time.Time, enc zapcore.PrimitiveArrayEncoder) { + enc.AppendString(t.Format("2006-01-02 15:04:05.000")) + } + } +} + +func GetLevel() string { + switch l.atomLevel.Level() { + case zapcore.PanicLevel: + return "panic" + case zapcore.FatalLevel: + return "fatal" + case zapcore.ErrorLevel: + return "error" + case zapcore.WarnLevel: + return "warn" + case zapcore.InfoLevel: + return "info" + default: + return "debug" + } +} + +func SetLevel(lvl string) { + l.atomLevel.SetLevel(setLogLevel(lvl)) +} + +// temporary add call skip +func AddCallerSkip(skip int) *LogX { + l.logger.WithOptions(zap.AddCallerSkip(skip)) + return l +} + +// permanent add call skip +func AddDepth(skip int) *LogX { + l.logger = l.logger.WithOptions(zap.AddCallerSkip(skip)) + return l +} + +// permanent add options +func AddOptions(opts ...zap.Option) *LogX { + l.logger = l.logger.WithOptions(opts...) + return l +} + +func AddField(k string, v interface{}) { + l.logger.With(zap.Any(k, v)) +} + +func AddFields(fields map[string]interface{}) *LogX { + for k, v := range fields { + l.logger.With(zap.Any(k, v)) + } + return l +} + +// Normal log +func Debug(e interface{}, args ...interface{}) error { + return l.Debug(e, args...) +} +func Info(e interface{}, args ...interface{}) error { + return l.Info(e, args...) +} +func Warn(e interface{}, args ...interface{}) error { + return l.Warn(e, args...) +} +func Error(e interface{}, args ...interface{}) error { + return l.Error(e, args...) +} +func Panic(e interface{}, args ...interface{}) error { + return l.Panic(e, args...) +} +func Fatal(e interface{}, args ...interface{}) error { + return l.Fatal(e, args...) +} + +// Format logs +func Debugf(format string, args ...interface{}) error { + return l.Debugf(format, args...) +} +func Infof(format string, args ...interface{}) error { + return l.Infof(format, args...) +} +func Warnf(format string, args ...interface{}) error { + return l.Warnf(format, args...) +} +func Errorf(format string, args ...interface{}) error { + return l.Errorf(format, args...) +} +func Panicf(format string, args ...interface{}) error { + return l.Panicf(format, args...) +} +func Fatalf(format string, args ...interface{}) error { + return l.Fatalf(format, args...) +} + +func formatFieldMap(m FieldMap) []Field { + var res []Field + for k, v := range m { + res = append(res, zap.Any(k, v)) + } + return res +} diff --git a/utils/logx/output.go b/utils/logx/output.go new file mode 100644 index 0000000..2d6aa6a --- /dev/null +++ b/utils/logx/output.go @@ -0,0 +1,105 @@ +package zhios_pay_logx + +import ( + "bytes" + "io" + "os" + "path/filepath" + "time" + + "gopkg.in/natefinch/lumberjack.v2" +) + +// output interface +type WriteSyncer interface { + io.Writer + Sync() error +} + +// split writer +func NewRollingFile(dir, filename string, maxSize, MaxAge int) WriteSyncer { + s, err := os.Stat(dir) + if err != nil || !s.IsDir() { + os.RemoveAll(dir) + if err := os.MkdirAll(dir, 0766); err != nil { + panic(err) + } + } + return newLumberjackWriteSyncer(&lumberjack.Logger{ + Filename: filepath.Join(dir, filename), + MaxSize: maxSize, // megabytes, MB + MaxAge: MaxAge, // days + LocalTime: true, + Compress: false, + }) +} + +type lumberjackWriteSyncer struct { + *lumberjack.Logger + buf *bytes.Buffer + logChan chan []byte + closeChan chan interface{} + maxSize int +} + +func newLumberjackWriteSyncer(l *lumberjack.Logger) *lumberjackWriteSyncer { + ws := &lumberjackWriteSyncer{ + Logger: l, + buf: bytes.NewBuffer([]byte{}), + logChan: make(chan []byte, 5000), + closeChan: make(chan interface{}), + maxSize: 1024, + } + go ws.run() + return ws +} + +func (l *lumberjackWriteSyncer) run() { + ticker := time.NewTicker(1 * time.Second) + + for { + select { + case <-ticker.C: + if l.buf.Len() > 0 { + l.sync() + } + case bs := <-l.logChan: + _, err := l.buf.Write(bs) + if err != nil { + continue + } + if l.buf.Len() > l.maxSize { + l.sync() + } + case <-l.closeChan: + l.sync() + return + } + } +} + +func (l *lumberjackWriteSyncer) Stop() { + close(l.closeChan) +} + +func (l *lumberjackWriteSyncer) Write(bs []byte) (int, error) { + b := make([]byte, len(bs)) + for i, c := range bs { + b[i] = c + } + l.logChan <- b + return 0, nil +} + +func (l *lumberjackWriteSyncer) Sync() error { + return nil +} + +func (l *lumberjackWriteSyncer) sync() error { + defer l.buf.Reset() + _, err := l.Logger.Write(l.buf.Bytes()) + if err != nil { + return err + } + return nil +} diff --git a/utils/logx/sugar.go b/utils/logx/sugar.go new file mode 100644 index 0000000..736a1be --- /dev/null +++ b/utils/logx/sugar.go @@ -0,0 +1,192 @@ +package zhios_pay_logx + +import ( + "errors" + "fmt" + "strconv" + + "go.uber.org/zap" +) + +type LogX struct { + logger *zap.Logger + atomLevel *zap.AtomicLevel +} + +type Field = zap.Field +type FieldMap map[string]interface{} + +// 判断其他类型--start +func getFields(msg string, format bool, args ...interface{}) (string, []Field) { + var str []interface{} + var fields []zap.Field + if len(args) > 0 { + for _, v := range args { + if f, ok := v.(Field); ok { + fields = append(fields, f) + } else if f, ok := v.(FieldMap); ok { + fields = append(fields, formatFieldMap(f)...) + } else { + str = append(str, AnyToString(v)) + } + } + if format { + return fmt.Sprintf(msg, str...), fields + } + str = append([]interface{}{msg}, str...) + return fmt.Sprintln(str...), fields + } + return msg, []Field{} +} + +func (l *LogX) Debug(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Debug(msg, field...) + } + return e +} +func (l *LogX) Info(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Info(msg, field...) + } + return e +} +func (l *LogX) Warn(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Warn(msg, field...) + } + return e +} +func (l *LogX) Error(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Error(msg, field...) + } + return e +} +func (l *LogX) DPanic(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.DPanic(msg, field...) + } + return e +} +func (l *LogX) Panic(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Panic(msg, field...) + } + return e +} +func (l *LogX) Fatal(s interface{}, args ...interface{}) error { + es, e := checkErr(s) + if es != "" { + msg, field := getFields(es, false, args...) + l.logger.Fatal(msg, field...) + } + return e +} + +func checkErr(s interface{}) (string, error) { + switch e := s.(type) { + case error: + return e.Error(), e + case string: + return e, errors.New(e) + case []byte: + return string(e), nil + default: + return "", nil + } +} + +func (l *LogX) LogError(err error) error { + return l.Error(err.Error()) +} + +func (l *LogX) Debugf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Debug(s, f...) + return errors.New(s) +} + +func (l *LogX) Infof(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Info(s, f...) + return errors.New(s) +} + +func (l *LogX) Warnf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Warn(s, f...) + return errors.New(s) +} + +func (l *LogX) Errorf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Error(s, f...) + return errors.New(s) +} + +func (l *LogX) DPanicf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.DPanic(s, f...) + return errors.New(s) +} + +func (l *LogX) Panicf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Panic(s, f...) + return errors.New(s) +} + +func (l *LogX) Fatalf(msg string, args ...interface{}) error { + s, f := getFields(msg, true, args...) + l.logger.Fatal(s, f...) + return errors.New(s) +} + +func AnyToString(raw interface{}) string { + switch i := raw.(type) { + case []byte: + return string(i) + case int: + return strconv.FormatInt(int64(i), 10) + case int64: + return strconv.FormatInt(i, 10) + case float32: + return strconv.FormatFloat(float64(i), 'f', 2, 64) + case float64: + return strconv.FormatFloat(i, 'f', 2, 64) + case uint: + return strconv.FormatInt(int64(i), 10) + case uint8: + return strconv.FormatInt(int64(i), 10) + case uint16: + return strconv.FormatInt(int64(i), 10) + case uint32: + return strconv.FormatInt(int64(i), 10) + case uint64: + return strconv.FormatInt(int64(i), 10) + case int8: + return strconv.FormatInt(int64(i), 10) + case int16: + return strconv.FormatInt(int64(i), 10) + case int32: + return strconv.FormatInt(int64(i), 10) + case string: + return i + case error: + return i.Error() + } + return fmt.Sprintf("%#v", raw) +} diff --git a/utils/md5.go b/utils/md5.go new file mode 100644 index 0000000..6db0583 --- /dev/null +++ b/utils/md5.go @@ -0,0 +1,12 @@ +package zhios_pay_utils + +import ( + "crypto/md5" + "encoding/hex" +) + +func Md5(str string) string { + h := md5.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/utils/serialize.go b/utils/serialize.go new file mode 100644 index 0000000..edd84ed --- /dev/null +++ b/utils/serialize.go @@ -0,0 +1,23 @@ +package zhios_pay_utils + +import ( + "encoding/json" +) + +func Serialize(data interface{}) []byte { + res, err := json.Marshal(data) + if err != nil { + return []byte{} + } + return res +} + +func Unserialize(b []byte, dst interface{}) { + if err := json.Unmarshal(b, dst); err != nil { + dst = nil + } +} + +func SerializeStr(data interface{}, arg ...interface{}) string { + return string(Serialize(data)) +}