diff --git a/lib/boc/api.go b/lib/boc/api.go new file mode 100644 index 0000000..9806cd3 --- /dev/null +++ b/lib/boc/api.go @@ -0,0 +1,28 @@ +package boc + +const Version = "2.0" +const SignType = "RSA2" + +//service类型 +const CreateBocCashier = "CreateBocCashier" //【中銀收銀台】創建支付交易 +const CreateAASAppTrade = "CreateAASAppTrade" //【中銀收银台】聚合APP支付統一下單 + +const CardBinding = "CardBinding " //【商戶收銀台】银行卡綁定 +const CardBindingQueryList = "CardBindingQueryList " //【商戶收銀台】查询绑定银行卡列表 +const RemoveBankCard = "removeBankCard " //【商戶收銀台】移除綁定銀行卡 +const CardBindingNotify = "CardBindingNotify " //【商戶收銀台】银行卡绑定结果后台通知 +const CreateEMVTrade = "CreateEMVTrade " //【商戶收銀台】創建EMV二维码支付交易 +const CreateCardTrade = "CreateCardTrade " //【商戶收銀台】創建银行卡交易 +const CreateQRTrade = "CreateQRTrade " //【商戶收銀台】創建聚合碼支付交易 +const CreateWeChatTrade = "CreateWeChatTrade" //【商户收银台】微信公众号/小程序支付统一下单 +const CreateBocPayAppTrade = "CreateBocPayAppTrade" //【商户收银台】手機銀行APP支付統一下單 +const CreateWeChatAppTrade = "CreateWeChatAppTrade" //【商户收银台】微信APP支付統一下單 +const CreateAlipayAppTrade = "CreateAlipayAppTrade" //【商户收银台】支付寶APP支付統一下單 + +const OrderRefund = "OrderRefund " //【基礎服務】退款 +const OrderQuery = "OrderQuery " //【基礎服務】訂單查詢 +const OfflineResult = "OfflineResult " //【基礎服務】支付結果後台通知 +const PageResult = "PageResult " //【基礎服務】支付結果頁面通知 +const Statement = "Statement " //【基礎服務】對賬文件下載 +const Settlement = "Settlement " //【基礎服務】結算單下載 +const OrderRelationQuery = "OrderRelationQuery " //【基礎服務】訂單关联查詢 diff --git a/lib/boc/api_macao.go b/lib/boc/api_macao.go index bd72e6c..b64d730 100644 --- a/lib/boc/api_macao.go +++ b/lib/boc/api_macao.go @@ -1,12 +1,26 @@ package boc 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" "fmt" + "github.com/syyongx/php2go" + "time" ) -func Send(prd bool, appId, privateKeyStr string, param map[string]string) (string, error) { - data := zhios_pay_utils.JoinStringsInASCII(param, "", "", false, false) +const ( + MerchantId = "888007050019400" + TerminalNo = "98036140" + PrivateKeyStr = "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSJk259Gd9IuvG\nRxYOma5vjo0+x2CTyP7m3uJ/epf+mwp/4XySbjVharBFmKk0XoSnZz/X/cYOgQXH\nPWPnBd0Y4qTl3xVf+DdZHF035KNG0LJPqvzqOSCuD5w32BHN0o9wFaR75PbWfsUl\nn2XqLVRFdv3IaKJYVWb8fM7VLT0aA+Rk81GXGfAbmXzNqX3lbOkqlTcBdzMHdE94\nqpAxtppVJ8JQqRJsWwzqJHo2koN+/kb3vL9IxeABJGUgU4Fy53MuC7JcvdYUoE7V\n92eD6A9g6ctSHP5y7olx9T7uMAaHk/TDv3+QNk6rI54cif7V7C5ykhtH5ZfHQ27C\nx929l7LZAgMBAAECggEAVZfVQajfyyvehbswMdDTlkYYdsHxz9uRXcj/QcWcquJG\nXerDOEcPQERpg/kKb79DVX9kpqzbh7cMmUlc7vpCKMbj6DSkohYO5EN2XLus3FZx\nd7MeeFaIyygdR59lnfNJSk2CjYTlA24vK/+UgneVuTEdJHgTTESIEitOPw9Fj6v+\n/+vRwuT6PVDm1FDUxFcYmJYzdMwJZoH0wDymofiEdn6beaHkqDXakrdY307cHOlq\ncrVQJKSybLwcBsbk9pgGrh6lCQLtQ8pHNtf6rE5FkEesO/VmdRFDGAK/9cuXEJ7u\nICdSESdfvnKl9BeN3MzSaeDSnh2IPoz13VVxXuJF+QKBgQDfHBVA/e1XFrLDM5IW\nO6nIxZeeZSufITDKHi7qRoS2mlOwjpMoIVAHCxlpnWd89YQ7NPdScEi6Z7l2sEET\n0vKfWsJ3ltowj5QUXS0XuJeAhkmK66lBNrICkatoAGeWxDrraCgkqLPNe40ydQ6h\nT2c3nR8tf++U2TzCpCXxzj6pgwKBgQCnsdTIWKwhRIltTgEzXTh3+JMQ36MH3sVt\nP14bxRN1hlR59yQvx0IEToXOHASSRU0fWxj3H8UewobYmE+2Ro+qNHFY7Do7Ypum\nIM7In00Tx4CKOVOkMZtNtR9hPk2/JUKpPSQBl7dSysBDngAwGhBhpwutWDpEiIfL\n+P4pAFuvcwKBgDrPUT7ncDdjkU1o+5oGshNtGjEzY9M3UYAFgsJYQkVL2sGQY3PR\njCG/KDyxtAS5RtjAdmzxkgooqzeA8DaEhVXB+77AMdVZE2RmxysYij7jpuEKi3vY\nKgYJVdA9QxD3urwwoVxAZ9zDM43YUznsDz4WmZBJx+JJfP2lfScHAXd3AoGAMpRR\noKgWKuaYKM7JT+vDF3RzLzi03K/VjnD5epjDmMQOgMcJNBv+jnv8ocETo+ahL17z\ntb3wX6m+sF+oU0sPFABDW9XMbpL8bWwh0qMxxVB8NbS6xKBtvk9uCo+XNZsQcylM\nzeor0h3zRbHHTrrdzAZtYDhnQrqHcv9D8av4+38CgYEAgyn1h+EompwBF4hKo6Dx\nVL/ZZYcE1E+XMzmHvP8C1sOVBSPZuCAnCx43cZiJZ94/5v8V3+4nKuWbeN1ar9BK\nXXSAVkk21qM8cv2IoDwi/DdBLXD302KMReAOWfJnQ+sbU1v2B2VPJoCwd4lSqauj\nVE4PCFhhrsou1VM1XbP8XsM=\n-----END PRIVATE KEY-----" +) + +func Send(prd bool, privateKeyStr string, param map[string]string) (string, error) { + for key, value := range param { + if value == "" { + delete(param, key) + } + } + data := zhios_pay_utils.JoinStringsInASCII(param, "", "", true, false) sign, err := zhios_pay_utils.GetSign(privateKeyStr, data) if err != nil { return "", err @@ -19,12 +33,68 @@ func Send(prd bool, appId, privateKeyStr string, param map[string]string) (strin "Content-Type": "application/json", } param["merchantSign"] = sign - fmt.Println(zhios_pay_utils.SerializeStr(param)) - fmt.Println(url) - res, err := zhios_pay_utils.CurlPost(url, param, headers) + fmt.Println("参数:", zhios_pay_utils.SerializeStr(param)) + zhios_pay_utils.CurlDebug = true + res, err := zhios_pay_utils.CurlPost(url, zhios_pay_utils.SerializeStr(param), headers) if err != nil { return "", err } fmt.Println(string(res)) return string(res), nil } + +func CreateBocPayAppTradeReq(prd bool, privateKeyStr, merchantId, terminalNo string, createBocPayAppTradeReq *md.CreateBocPayAppTradeReq) (string, error) { + //基础信息,必填 + createBocPayAppTradeReq.Version = Version + createBocPayAppTradeReq.Service = CreateBocPayAppTrade + createBocPayAppTradeReq.SignType = SignType + createBocPayAppTradeReq.MerchantId = merchantId + createBocPayAppTradeReq.TerminalNo = terminalNo + createBocPayAppTradeReq.RequestId = createBocPayAppTradeReq.MercOrderNo + zhios_pay_utils.AnyToString(time.Now().Unix()) + //按照接口类型选填信息 + //createBocPayAppTradeReq.Amount = "100" + //createBocPayAppTradeReq.OriginalAmount = "100" + //createBocPayAppTradeReq.MercOrderNo = "2022101401" + //createBocPayAppTradeReq.OrderDate = "20221013" + //createBocPayAppTradeReq.OrderTime = "175600" + //createBocPayAppTradeReq.NotifyUrl = "/api/v1/mall/pay/ada/sub_callback" + //createBocPayAppTradeReq.Reserved1 = zhios_pay_utils.SerializeStr(map[string]string{ + // "mid":"123456", + //}) + param := make(map[string]string) + zhios_pay_utils.Unserialize(zhios_pay_utils.Serialize(createBocPayAppTradeReq), ¶m) + send, err := Send(prd, privateKeyStr, param) + if err != nil { + return "", err + } + resp, _ := php2go.URLDecode(send) + return resp, nil +} + +func CreateWeChatTradeReq(prd bool, privateKeyStr, merchantId, terminalNo string, createWeChatTradeReq *md.CreateWeChatTradeReq) (string, error) { + //基础信息,必填 + createWeChatTradeReq.Version = Version + createWeChatTradeReq.Service = CreateWeChatTrade + createWeChatTradeReq.SignType = SignType + createWeChatTradeReq.MerchantId = merchantId + createWeChatTradeReq.TerminalNo = terminalNo + createWeChatTradeReq.RequestId = createWeChatTradeReq.MercOrderNo + zhios_pay_utils.AnyToString(time.Now().Unix()) + //按照接口类型选填信息 + //createBocPayAppTradeReq.Amount = "100" + //createBocPayAppTradeReq.OriginalAmount = "100" + //createBocPayAppTradeReq.MercOrderNo = "2022101401" + //createBocPayAppTradeReq.OrderDate = "20221013" + //createBocPayAppTradeReq.OrderTime = "175600" + //createBocPayAppTradeReq.NotifyUrl = "/api/v1/mall/pay/ada/sub_callback" + //createBocPayAppTradeReq.Reserved1 = zhios_pay_utils.SerializeStr(map[string]string{ + // "mid":"123456", + //}) + param := make(map[string]string) + zhios_pay_utils.Unserialize(zhios_pay_utils.Serialize(createWeChatTradeReq), ¶m) + send, err := Send(prd, privateKeyStr, param) + if err != nil { + return "", err + } + resp, _ := php2go.URLDecode(send) + return resp, nil +} diff --git a/md/boc.go b/md/boc.go index 1034ce3..7d35ff4 100644 --- a/md/boc.go +++ b/md/boc.go @@ -44,3 +44,24 @@ type CreateBocPayAppTradeReq struct { Reserved2 string `json:"reserved2" name:"商戶備用字段" label:"交易成功后原樣返回給商戶"` Reserved3 string `json:"reserved3" name:"商戶備用字段" label:"交易成功后原樣返回給商戶"` } + +type CreateWeChatTradeReq struct { + MacaoBOCPublicParameter + Amount string `json:"amount" name:"订单金額" label:"以分爲單位,如1元表示爲100"` + OriginalAmount string `json:"originalAmount" name:"原订单金额" label:"以分爲單位,如1元表示爲100若无优惠请跟订单金额【amount】一致"` + MerchantPreferentialCnName string `json:"merchantPreferentialCnName" name:"商户系统优惠活动中文名称" label:"非必传"` + MerchantPreferentialEnName string `json:"merchantPreferentialEnName" name:"商户系统优惠活动英文名称" label:"非必传"` + Subject string `json:"subject" name:"訂單標題" label:"非必传,用戶支付完成後顯示在手機上的訂單名稱"` + ProductDesc string `json:"productDesc" name:"商品描述" label:"非必传,對一筆交易的具體描述信息,如果是多種商品,請將商品描述字符串累加傳給body。特殊字符不支持"` + MercOrderNo string `json:"mercOrderNo" name:"商戶系統消費訂單號" label:"商戶系統必須確保該訂單號在商戶系統是唯一的不能包含單引號“’”、尖括號“<”“>”和逗號“,”"` + OrderDate string `json:"orderDate" name:"訂單創建日期" label:"格式:yyyyMMdd"` + OrderTime string `json:"orderTime" name:"訂單創建時間" label:"格式:HHmmss"` + ValidNumber string `json:"validNumber" name:"有效期" label:"單位:秒 默認值1200"` + NotifyUrl string `json:"notifyUrl" name:"支付結果後台通知地址" label:"支付通知地址(后缀),商户在商戶服務管理後台配置好通知地址(域名)后,聚合平台会将域名和后缀拼接组成完整的通知地址,并在订单支付成后向此通知地址发送支付结果通知。"` + SubAppId string `json:"subAppId" name:"子商戶AppId" label:"微信分配的子商户公众账号或小程序Id。"` + SubOpenId string `json:"subOpenId" name:"子商戶用戶唯一Id" label:"用戶在子商戶appId下的唯一標識。下單前商戶需要調用微信【網頁授權獲取用戶信息】接口獲取到用戶的OpenId。詳情查看附錄5.5.1章節"` + TransWay string `json:"transWay" name:"交易方式" label:"B3-公众号支付B4-小程序支付"` + Reserved1 string `json:"reserved1" name:"商戶備用字段" label:"交易成功后原樣返回給商戶"` + Reserved2 string `json:"reserved2" name:"商戶備用字段" label:"交易成功后原樣返回給商戶"` + Reserved3 string `json:"reserved3" name:"商戶備用字段" label:"交易成功后原樣返回給商戶"` +} diff --git a/test/ada_pay_test.go b/test/ada_pay_test.go index 6af5467..6f79c8b 100644 --- a/test/ada_pay_test.go +++ b/test/ada_pay_test.go @@ -157,3 +157,23 @@ func TestCardBindlist(t *testing.T) { } t.Log(zhios_pay_utils.SerializeStr(data)) } +func TestPaymentQueryList(t *testing.T) { + Adapay, err := adapay.GetAdapayByDBSysConfig("jiuhuicang", "app_a794b158-359b-4e5a-8464-5bd8f171bb6e", "api_live_419ec107-9854-45e8-af12-070efcef6ae9", "api_test_d6485653-c09e-405f-b386-8ef2936dfb97", "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwN6xgd6Ad8v2hIIsQVnbt8a3JituR8o4Tc3B5WlcFR55bz4OMqrG/356Ur3cPbc2Fe8ArNd/0gZbC9q56Eb16JTkVNA/fye4SXznWxdyBPR7+guuJZHc/VW2fKH2lfZ2P3Tt0QkKZZoawYOGSMdIvO+WqK44updyax0ikK6JlNQIDAQAB", + "MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMxVAwWM79+2Lx1TYA4muHy40AbwcBwXmvgNJGp2gq7bcDaNd5cmvXndwS6exV2hIGo8er0Qu3myGrwD/4BIoqCDSuqYfJkrlKzKM30+WAfwRulrFW+SOr6qCvGFF2bMdL884NMgd5PCpayASONq/blefQ3/Ic+c0gtZQxJzYzxLAgMBAAECgYEAyS+rGfv68zpeiXFeUpCtdaBCs5Jp0D8txq6p/GlTovdNNWl1Js5uIV/GpjCOA/JyCCgkcaPX86r5x8Xn/FeIRt/U1kkGs2zvKcxQntkOhmzMdDe7/FuwbRUUeM6LWGIc5AeQcBWYZeS8HAFMnlWho+knMqqgHC/fgUyon6YgMAECQQDyjJYSp+hdhTnvo88GPisQEPOoZRbrUnVFbwjD/OrQT7s6uSlbTAJRXGEYfdeaWsh2CoFl986pOqc/84E/Lr3LAkEA16nd1J3stk9XtC4i+iiXY0codiK24kU+pMcEm8NNyMV1nQT5iTcDA1ZeJCgamJZFeqcfyusimBNbCXdBKk2rgQJBAIymodAntkOlIjepEkBYhLhIXENme6fypTaicL7WR4SM99HR1f2vUhjELTn6n7BOvLhW1zq+PQU9kgcvud9dx4kCQFSMXtBFHZEXp/2WfNXv5fHg6sbtsx8gIH//GhpqxerpJsPpOF8H9yFu0beBFXQurYx5SqiF6GkQZYdffme0TYECQQDgOdQxysI1FEM/l8BDnQgmnO7sJqUtP9HsVcKElGdkJMZGqb8B5rt12y4dxpyVfyaTMZZZjqIx8zgQBhn+l0NJ") + if err != nil { + t.Error(err) + } + createPaymentParams := map[string]interface{}{ + "app_id": Adapay.AppId, + "page_index": "1", + "page_size": "10", + } + + data, apiError, err := Adapay.Payment().QueryList(createPaymentParams) + if err != nil || apiError != nil { // 网络或本应用异常 + t.Error(err) + t.Error(apiError) + return + } + t.Log(zhios_pay_utils.SerializeStr(data)) +} diff --git a/test/boc_pay_test.go b/test/boc_pay_test.go index 89d52d5..6b2d4f2 100644 --- a/test/boc_pay_test.go +++ b/test/boc_pay_test.go @@ -1,9 +1,13 @@ package test import ( + "code.fnuoos.com/go_rely_warehouse/zyos_go_pay.git/lib/boc" + "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/syyongx/php2go" "testing" + "time" ) func TestJoinStringsInASCII(t *testing.T) { @@ -11,3 +15,73 @@ func TestJoinStringsInASCII(t *testing.T) { "", "", false, false) fmt.Println(ascii) } + +func TestBocSend(t *testing.T) { + var createBocPayAppTradeReq md.CreateBocPayAppTradeReq + //基础信息,必填 + createBocPayAppTradeReq.Version = boc.Version + createBocPayAppTradeReq.Service = boc.CreateBocPayAppTrade + createBocPayAppTradeReq.SignType = boc.SignType + createBocPayAppTradeReq.MerchantId = boc.MerchantId + createBocPayAppTradeReq.TerminalNo = boc.TerminalNo + createBocPayAppTradeReq.RequestId = "2022101401" + zhios_pay_utils.AnyToString(time.Now().Unix()) + //按照接口类型选填信息 + createBocPayAppTradeReq.Amount = "100" + createBocPayAppTradeReq.OriginalAmount = "100" + createBocPayAppTradeReq.MercOrderNo = "2022101401" + createBocPayAppTradeReq.OrderDate = "20221013" + createBocPayAppTradeReq.OrderTime = "175600" + createBocPayAppTradeReq.NotifyUrl = "/api/v1/mall/pay/ada/sub_callback" + createBocPayAppTradeReq.Reserved1 = zhios_pay_utils.SerializeStr(map[string]string{ + "mid": "123456", + }) + param := make(map[string]string) + zhios_pay_utils.Unserialize(zhios_pay_utils.Serialize(createBocPayAppTradeReq), ¶m) + send, err := boc.Send(false, boc.PrivateKeyStr, param) + if err != nil { + t.Error(err) + } + t.Logf("回调信息1:%s", send) + resp, _ := php2go.URLDecode(send) + t.Logf("回调信息:%s", resp) +} +func TestCreateBocPayAppTradeReq(t *testing.T) { + var createBocPayAppTradeReq md.CreateBocPayAppTradeReq + //按照接口类型选填信息 + createBocPayAppTradeReq.Amount = "100" + createBocPayAppTradeReq.OriginalAmount = "100" + createBocPayAppTradeReq.MercOrderNo = "2022101402" + createBocPayAppTradeReq.OrderDate = "20221013" + createBocPayAppTradeReq.OrderTime = "175600" + createBocPayAppTradeReq.NotifyUrl = "/api/v1/mall/pay/ada/sub_callback" + createBocPayAppTradeReq.Reserved1 = zhios_pay_utils.SerializeStr(map[string]string{ + "mid": "123456", + }) + send, err := boc.CreateBocPayAppTradeReq(false, boc.PrivateKeyStr, boc.MerchantId, boc.TerminalNo, &createBocPayAppTradeReq) + if err != nil { + t.Error(err) + } + t.Logf("回调信息:%s", send) +} + +func TestCreateWeChatTradeReq(t *testing.T) { + var CreateWeChatTradeReqReq md.CreateWeChatTradeReq + //按照接口类型选填信息 + CreateWeChatTradeReqReq.Amount = "100" + CreateWeChatTradeReqReq.OriginalAmount = "100" + CreateWeChatTradeReqReq.MercOrderNo = "2022101403" + CreateWeChatTradeReqReq.OrderDate = "20221013" + CreateWeChatTradeReqReq.OrderTime = "175600" + CreateWeChatTradeReqReq.NotifyUrl = "/api/v1/mall/pay/ada/sub_callback" + CreateWeChatTradeReqReq.SubAppId = "wx987580a9437354d1" + CreateWeChatTradeReqReq.SubOpenId = "oEk7V5hNLxQXoTpOcvzRdGJQKnjM" + CreateWeChatTradeReqReq.TransWay = "B3" + CreateWeChatTradeReqReq.Reserved1 = zhios_pay_utils.SerializeStr(map[string]string{ + "mid": "123456", + }) + send, err := boc.CreateWeChatTradeReq(false, boc.PrivateKeyStr, boc.MerchantId, boc.TerminalNo, &CreateWeChatTradeReqReq) + if err != nil { + t.Error(err) + } + t.Logf("回调信息:%s", send) +} diff --git a/utils/map_and_struct.go b/utils/map_and_struct.go new file mode 100644 index 0000000..9d6ba6d --- /dev/null +++ b/utils/map_and_struct.go @@ -0,0 +1,341 @@ +package zhios_pay_utils + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" + "strings" +) + +func Map2Struct(vals map[string]interface{}, dst interface{}) (err error) { + return Map2StructByTag(vals, dst, "json") +} + +func Map2StructByTag(vals map[string]interface{}, dst interface{}, structTag string) (err error) { + defer func() { + e := recover() + if e != nil { + if v, ok := e.(error); ok { + err = fmt.Errorf("Panic: %v", v.Error()) + } else { + err = fmt.Errorf("Panic: %v", e) + } + } + }() + + pt := reflect.TypeOf(dst) + pv := reflect.ValueOf(dst) + + if pv.Kind() != reflect.Ptr || pv.Elem().Kind() != reflect.Struct { + return fmt.Errorf("not a pointer of struct") + } + + var f reflect.StructField + var ft reflect.Type + var fv reflect.Value + + for i := 0; i < pt.Elem().NumField(); i++ { + f = pt.Elem().Field(i) + fv = pv.Elem().Field(i) + ft = f.Type + + if f.Anonymous || !fv.CanSet() { + continue + } + + tag := f.Tag.Get(structTag) + + name, option := parseTag(tag) + + if name == "-" { + continue + } + + if name == "" { + name = strings.ToLower(f.Name) + } + val, ok := vals[name] + + if !ok { + if option == "required" { + return fmt.Errorf("'%v' not found", name) + } + if len(option) != 0 { + val = option // default value + } else { + //fv.Set(reflect.Zero(ft)) // TODO set zero value or just ignore it? + continue + } + } + + // convert or set value to field + vv := reflect.ValueOf(val) + vt := reflect.TypeOf(val) + + if vt.Kind() != reflect.String { + // try to assign and convert + if vt.AssignableTo(ft) { + fv.Set(vv) + continue + } + + if vt.ConvertibleTo(ft) { + fv.Set(vv.Convert(ft)) + continue + } + + return fmt.Errorf("value type not match: field=%v(%v) value=%v(%v)", f.Name, ft.Kind(), val, vt.Kind()) + } + s := strings.TrimSpace(vv.String()) + if len(s) == 0 && option == "required" { + return fmt.Errorf("value of required argument can't not be empty") + } + fk := ft.Kind() + + // convert string to value + if fk == reflect.Ptr && ft.Elem().Kind() == reflect.String { + fv.Set(reflect.ValueOf(&s)) + continue + } + if fk == reflect.Ptr || fk == reflect.Struct { + err = convertJsonValue(s, name, fv) + } else if fk == reflect.Slice { + err = convertSlice(s, f.Name, ft, fv) + } else { + err = convertValue(fk, s, f.Name, fv) + } + + if err != nil { + return err + } + continue + } + + return nil +} + +func Struct2Map(s interface{}) map[string]interface{} { + return Struct2MapByTag(s, "json") +} +func Struct2MapByTag(s interface{}, tagName string) map[string]interface{} { + t := reflect.TypeOf(s) + v := reflect.ValueOf(s) + + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct { + t = t.Elem() + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + return nil + } + + m := make(map[string]interface{}) + + for i := 0; i < t.NumField(); i++ { + fv := v.Field(i) + ft := t.Field(i) + + if !fv.CanInterface() { + continue + } + + if ft.PkgPath != "" { // unexported + continue + } + + var name string + var option string + tag := ft.Tag.Get(tagName) + if tag != "" { + ts := strings.Split(tag, ",") + if len(ts) == 1 { + name = ts[0] + } else if len(ts) > 1 { + name = ts[0] + option = ts[1] + } + if name == "-" { + continue // skip this field + } + if name == "" { + name = strings.ToLower(ft.Name) + } + if option == "omitempty" { + if isEmpty(&fv) { + continue // skip empty field + } + } + } else { + name = strings.ToLower(ft.Name) + } + + if ft.Anonymous && fv.Kind() == reflect.Ptr && fv.IsNil() { + continue + } + if (ft.Anonymous && fv.Kind() == reflect.Struct) || + (ft.Anonymous && fv.Kind() == reflect.Ptr && fv.Elem().Kind() == reflect.Struct) { + + // embedded struct + embedded := Struct2MapByTag(fv.Interface(), tagName) + for embName, embValue := range embedded { + m[embName] = embValue + } + } else if option == "string" { + kind := fv.Kind() + if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 { + m[name] = strconv.FormatInt(fv.Int(), 10) + } else if kind == reflect.Uint || kind == reflect.Uint8 || kind == reflect.Uint16 || kind == reflect.Uint32 || kind == reflect.Uint64 { + m[name] = strconv.FormatUint(fv.Uint(), 10) + } else if kind == reflect.Float32 || kind == reflect.Float64 { + m[name] = strconv.FormatFloat(fv.Float(), 'f', 2, 64) + } else { + m[name] = fv.Interface() + } + } else { + m[name] = fv.Interface() + } + } + + return m +} + +func isEmpty(v *reflect.Value) bool { + k := v.Kind() + if k == reflect.Bool { + return v.Bool() == false + } else if reflect.Int < k && k < reflect.Int64 { + return v.Int() == 0 + } else if reflect.Uint < k && k < reflect.Uintptr { + return v.Uint() == 0 + } else if k == reflect.Float32 || k == reflect.Float64 { + return v.Float() == 0 + } else if k == reflect.Array || k == reflect.Map || k == reflect.Slice || k == reflect.String { + return v.Len() == 0 + } else if k == reflect.Interface || k == reflect.Ptr { + return v.IsNil() + } + return false +} + +func convertSlice(s string, name string, ft reflect.Type, fv reflect.Value) error { + var err error + et := ft.Elem() + + if et.Kind() == reflect.Ptr || et.Kind() == reflect.Struct { + return convertJsonValue(s, name, fv) + } + + ss := strings.Split(s, ",") + + if len(s) == 0 || len(ss) == 0 { + return nil + } + + fs := reflect.MakeSlice(ft, 0, len(ss)) + + for _, si := range ss { + ev := reflect.New(et).Elem() + + err = convertValue(et.Kind(), si, name, ev) + if err != nil { + return err + } + fs = reflect.Append(fs, ev) + } + + fv.Set(fs) + + return nil +} + +func convertJsonValue(s string, name string, fv reflect.Value) error { + var err error + d := StringToSlice(s) + + if fv.Kind() == reflect.Ptr { + if fv.IsNil() { + fv.Set(reflect.New(fv.Type().Elem())) + } + } else { + fv = fv.Addr() + } + + err = json.Unmarshal(d, fv.Interface()) + + if err != nil { + return fmt.Errorf("invalid json '%v': %v, %v", name, err.Error(), s) + } + + return nil +} + +func convertValue(kind reflect.Kind, s string, name string, fv reflect.Value) error { + if !fv.CanAddr() { + return fmt.Errorf("can not addr: %v", name) + } + + if kind == reflect.String { + fv.SetString(s) + return nil + } + + if kind == reflect.Bool { + switch s { + case "true": + fv.SetBool(true) + case "false": + fv.SetBool(false) + case "1": + fv.SetBool(true) + case "0": + fv.SetBool(false) + default: + return fmt.Errorf("invalid bool: %v value=%v", name, s) + } + return nil + } + + if reflect.Int <= kind && kind <= reflect.Int64 { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return fmt.Errorf("invalid int: %v value=%v", name, s) + } + fv.SetInt(i) + + } else if reflect.Uint <= kind && kind <= reflect.Uint64 { + i, err := strconv.ParseUint(s, 10, 64) + if err != nil { + return fmt.Errorf("invalid int: %v value=%v", name, s) + } + fv.SetUint(i) + + } else if reflect.Float32 == kind || kind == reflect.Float64 { + i, err := strconv.ParseFloat(s, 64) + + if err != nil { + return fmt.Errorf("invalid float: %v value=%v", name, s) + } + + fv.SetFloat(i) + } else { + // not support or just ignore it? + // return fmt.Errorf("type not support: field=%v(%v) value=%v(%v)", name, ft.Kind(), val, vt.Kind()) + } + return nil +} + +func parseTag(tag string) (string, string) { + tags := strings.Split(tag, ",") + + if len(tags) <= 0 { + return "", "" + } + + if len(tags) == 1 { + return tags[0], "" + } + + return tags[0], tags[1] +} diff --git a/utils/slice_and_string.go b/utils/slice_and_string.go new file mode 100644 index 0000000..5ec3e86 --- /dev/null +++ b/utils/slice_and_string.go @@ -0,0 +1,47 @@ +package zhios_pay_utils + +import ( + "fmt" + "reflect" + "strings" + "unsafe" +) + +// string与slice互转,零copy省内存 + +// zero copy to change slice to string +func Slice2String(b []byte) (s string) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pString.Data = pBytes.Data + pString.Len = pBytes.Len + return +} + +// no copy to change string to slice +func StringToSlice(s string) (b []byte) { + pBytes := (*reflect.SliceHeader)(unsafe.Pointer(&b)) + pString := (*reflect.StringHeader)(unsafe.Pointer(&s)) + pBytes.Data = pString.Data + pBytes.Len = pString.Len + pBytes.Cap = pString.Len + return +} + +// 任意slice合并 +func SliceJoin(sep string, elems ...interface{}) string { + l := len(elems) + if l == 0 { + return "" + } + if l == 1 { + s := fmt.Sprint(elems[0]) + sLen := len(s) - 1 + if s[0] == '[' && s[sLen] == ']' { + return strings.Replace(s[1:sLen], " ", sep, -1) + } + return s + } + sep = strings.Replace(fmt.Sprint(elems), " ", sep, -1) + return sep[1 : len(sep)-1] +}