├── .gitignore ├── LICENSE ├── README.md ├── access_token.go ├── cache ├── cache.go ├── map_cache.go ├── map_cache_test.go ├── redis_cache.go └── redis_cache_test.go ├── cipher ├── aes.go ├── aes_test.go ├── bizmsg.go ├── bizmsg_test.go ├── cipher.go ├── ecb.go ├── errors.go ├── log.go ├── padding.go └── rsa.go ├── client.go ├── config.go ├── declare.go ├── declare_card.go ├── declare_message.go ├── declare_poi.go ├── declare_qrcode.go ├── declare_template.go ├── declare_userinfo.go ├── go.mod ├── jssdk.go ├── log.go ├── notify.go ├── official_account.go ├── official_account_button.go ├── official_account_merchant.go ├── official_account_user.go ├── option.go ├── payment.go ├── property.go ├── property_test.go ├── request.go ├── response.go ├── sandbox.go ├── sandbox_test.go ├── ticket.go ├── token.go ├── util ├── function.go ├── function_test.go ├── log.go ├── map.go ├── map_test.go ├── net.go ├── sign.go ├── url.go └── url_test.go └── wechat_user.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | .idea/ 16 | vendor/ 17 | config.toml 18 | cert/ 19 | /cert 20 | /config.toml 21 | /go.sum 22 | /go.sum 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 godcong 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WEGO a wechat interface for go(golang) 2 | 3 | [![GoDoc](https://godoc.org/github.com/godcong/wego?status.svg)](http://godoc.org/github.com/godcong/wego) 4 | [![license](https://img.shields.io/github/license/godcong/wego.svg)](https://github.com/godcong/wego/blob/master/LICENSE) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/godcong/wego)](https://goreportcard.com/report/github.com/godcong/wego) 6 | 7 | ## 世界上最好的Go微信开发SDK(时尚,简单,性能卓越) 8 | ### a fashion fast wechat api for golang 9 | 10 | ### WEGO借鉴了并参考了,当前许多流行的开源微信支付框架.并且为了使性能得到更大提高.全部使用golang进行重构 11 | 12 | 开发前,请务必仔细阅读并看懂 13 | 14 | 获取包: 15 | > go get github.com/godcong/wego 16 | 17 | 建议使用golang/dep添加包: 18 | > dep ensure --add github.com/godcong/wego 19 | 20 | 或者vgo添加: 21 | > vgo get github.com/godcong/wego@v0.1.0 22 | 23 | 接口说明可以参考godoc 24 | > godoc -http:6060 25 | 26 | ## 配置 27 | cfg := C(util.Map{ 28 | "app_id":"wx1ad61aeexxxxxxx", //AppId 29 | "mch_id":"1498xxxxx32", //商户ID 30 | "key":"O9aVVkxxxxxxxxxxxxxxxbZ2NQSJ", //支付key 31 | "notify_url":"https://host.address/uri", //支付回调地址 32 | 33 | //如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) 34 | "cert_path":"cert/apiclient_cert.pem", //支付证书地址 35 | "key_path":"cert/apiclient_key.pem", //支付证书地址 36 | 37 | //银行转账功能 38 | "rootca_path":"cert/rootca.pem", //(可不填) 39 | "pubkey_path":"cert/publickey.pem", //(可不填)部分支付使用(如:银行转账) 40 | "prikey_path":"cert/privatekey.pem", //(可不填)部分支付使用(如:银行转账) 41 | } 42 | 43 | 通过配置config.toml文件,具体参考config.toml.example 44 | 45 | //必要配置 46 | app_id ='wx1ad61aeexxxxxxx' //AppId 47 | mch_id = '1498xxxxx32' //商户ID 48 | key = 'O9aVVkxxxxxxxxxxxxxxxbZ2NQSJ' //支付key 49 | 50 | notify_url ='https://host.address/uri' //支付回调地址 51 | 52 | //如需使用敏感接口(如退款、发送红包等)需要配置 API 证书路径(登录商户平台下载 API 证书) 53 | cert_path = 'cert/apiclient_cert.pem' //支付证书地址 54 | key_path = 'cert/apiclient_key.pem' //支付证书地址 55 | 56 | //银行转账功能 57 | rootca_path = 'cert/rootca.pem' //(可不填) 58 | pubkey_path = "cert/publickey.pem" //(可不填)部分支付使用(如:银行转账) 59 | prikey_path = "cert/privatekey.pem" //(可不填)部分支付使用(如:银行转账) 60 | 61 | 62 | 63 | 64 | ## Readme 65 | 66 | > [微信支付](https://github.com/godcong/wego/blob/v1.1.7/app/payment/README.md) 67 | 68 | > [公众号](https://github.com/godcong/wego/blob/v1.1.7/app/official/README.md) 文档待更新 69 | 70 | > [小程序](https://github.com/godcong/wego/blob/v1.1.7/app/mini/README.md) 文档待更新 71 | 72 | > 开放平台 //TODO: 73 | 74 | > 企业微信 //TODO: 75 | 76 | > 企业微信开放平台 //TODO: 77 | 78 | ### 具体功能涵盖,微信模板,企业转账,微信红包,微信支付,微信客服,微信小程序等常用接口。 79 | -------------------------------------------------------------------------------- /access_token.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "github.com/godcong/wego/cache" 7 | "github.com/godcong/wego/util" 8 | "golang.org/x/xerrors" 9 | "strings" 10 | ) 11 | 12 | /*AccessToken GetToken */ 13 | type AccessToken struct { 14 | *AccessTokenProperty 15 | remoteURL string 16 | tokenKey string 17 | tokenURL string 18 | } 19 | 20 | /*AccessTokenSafeSeconds token安全时间 */ 21 | const AccessTokenSafeSeconds = 500 22 | 23 | // RemoteURL ... 24 | func (obj *AccessToken) RemoteURL() string { 25 | if obj != nil && obj.remoteURL != "" { 26 | return obj.remoteURL 27 | } 28 | return apiWeixin 29 | } 30 | 31 | // TokenURL ... 32 | func (obj *AccessToken) TokenURL() string { 33 | return util.URL(obj.RemoteURL(), tokenURL(obj)) 34 | } 35 | func tokenURL(obj *AccessToken) string { 36 | if obj != nil && obj.tokenURL != "" { 37 | return obj.tokenURL 38 | } 39 | return accessToken 40 | } 41 | 42 | /*NewAccessToken NewAccessToken*/ 43 | func NewAccessToken(property *AccessTokenProperty, options ...AccessTokenOption) *AccessToken { 44 | token := &AccessToken{ 45 | AccessTokenProperty: property, 46 | } 47 | token.parse(options...) 48 | return token 49 | } 50 | 51 | func (obj *AccessToken) parse(options ...AccessTokenOption) { 52 | if options == nil { 53 | return 54 | } 55 | for _, o := range options { 56 | o(obj) 57 | } 58 | } 59 | 60 | /*Refresh 刷新AccessToken */ 61 | func (obj *AccessToken) Refresh() *AccessToken { 62 | log.Debug("GetToken|Refresh") 63 | obj.getToken(true) 64 | return obj 65 | } 66 | 67 | /*GetRefreshToken 获取刷新token */ 68 | func (obj *AccessToken) GetRefreshToken() *Token { 69 | log.Debug("GetToken|GetRefreshedToken") 70 | return obj.getToken(true) 71 | } 72 | 73 | /*GetToken 获取token */ 74 | func (obj *AccessToken) GetToken() *Token { 75 | return obj.getToken(false) 76 | } 77 | 78 | // KeyMap ... 79 | func (obj *AccessToken) KeyMap() util.Map { 80 | return MustKeyMap(obj) 81 | } 82 | 83 | func (obj *AccessToken) getToken(refresh bool) *Token { 84 | key := obj.getCacheKey() 85 | log.Infof("cached key:%t,%s", cache.Has(key), key) 86 | if !refresh && cache.Has(key) { 87 | if v, b := cache.Get(key).(string); b { 88 | token, e := ParseToken(v) 89 | if e != nil { 90 | log.Error("parse token error") 91 | return nil 92 | } 93 | log.Infof("cached accessToken:%+v", v) 94 | return token 95 | } 96 | } 97 | 98 | token, e := requestToken(obj.TokenURL(), obj.AccessTokenProperty) 99 | if e != nil { 100 | log.Error(e) 101 | return nil 102 | } 103 | 104 | log.Infof("accessToken:%+v", *token) 105 | if v := token.ExpiresIn; v != 0 { 106 | obj.SetTokenWithLife(token.ToJSON(), v-AccessTokenSafeSeconds) 107 | } else { 108 | obj.SetToken(token.ToJSON()) 109 | } 110 | return token 111 | } 112 | 113 | func requestToken(url string, credentials *AccessTokenProperty) (*Token, error) { 114 | var t Token 115 | var e error 116 | token := Get(url, credentials.ToMap()) 117 | if e := token.Error(); e != nil { 118 | return nil, e 119 | } 120 | e = token.Unmarshal(&t) 121 | if e != nil { 122 | return nil, e 123 | } 124 | return &t, nil 125 | } 126 | 127 | /*SetTokenWithLife set string accessToken with life time */ 128 | func (obj *AccessToken) SetTokenWithLife(token string, lifeTime int64) *AccessToken { 129 | return obj.setToken(token, lifeTime) 130 | } 131 | 132 | /*SetToken set string accessToken */ 133 | func (obj *AccessToken) SetToken(token string) *AccessToken { 134 | return obj.setToken(token, 7200-AccessTokenSafeSeconds) 135 | } 136 | 137 | func (obj *AccessToken) setToken(token string, lifeTime int64) *AccessToken { 138 | cache.SetWithTTL(obj.getCacheKey(), token, lifeTime) 139 | return obj 140 | } 141 | 142 | func (obj *AccessToken) getCredentials() string { 143 | cred := strings.Join([]string{obj.GrantType, obj.AppID, obj.AppSecret}, ".") 144 | c := md5.Sum([]byte(cred)) 145 | return fmt.Sprintf("%x", c[:]) 146 | } 147 | 148 | func (obj *AccessToken) getCacheKey() string { 149 | return "godcong.wego.access_token." + obj.getCredentials() 150 | } 151 | 152 | const accessTokenNil = "nil point accessToken" 153 | const tokenNil = "nil point token" 154 | 155 | /*MustKeyMap get accessToken's key,value with map when nil or error return nil map */ 156 | func MustKeyMap(at *AccessToken) util.Map { 157 | if m, e := KeyMap(at); e == nil { 158 | return m 159 | } 160 | return util.Map{} 161 | } 162 | 163 | /*KeyMap get accessToken's key,value with map */ 164 | func KeyMap(at *AccessToken) (util.Map, error) { 165 | if at == nil { 166 | return nil, xerrors.New(accessTokenNil) 167 | } 168 | if token := at.GetToken(); token != nil { 169 | return token.KeyMap(), nil 170 | } 171 | return nil, xerrors.New(tokenNil) 172 | } 173 | 174 | func parseAccessToken(token interface{}) string { 175 | switch v := token.(type) { 176 | case Token: 177 | return v.AccessToken 178 | case *Token: 179 | return v.AccessToken 180 | case string: 181 | return v 182 | } 183 | return "" 184 | } 185 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | /*Cache define an cache interface */ 4 | type Cache interface { 5 | Get(key string) interface{} 6 | GetD(key string, v interface{}) interface{} 7 | Set(key string, val interface{}) Cache 8 | SetWithTTL(key string, val interface{}, ttl int64) Cache 9 | Has(key string) bool 10 | Delete(key string) Cache 11 | Clear() 12 | GetMultiple(keys ...string) map[string]interface{} 13 | SetMultiple(values map[string]interface{}) Cache 14 | DeleteMultiple(keys ...string) Cache 15 | } 16 | 17 | //var cache sync.Map 18 | var cache Cache 19 | 20 | func init() { 21 | RegisterCache(NewMapCache()) 22 | } 23 | 24 | /*RegisterCache register cache to map */ 25 | func RegisterCache(c Cache) { 26 | cache = c 27 | } 28 | 29 | /*DefaultCache get cache from map */ 30 | func DefaultCache() Cache { 31 | return cache 32 | } 33 | 34 | //Get get value 35 | func Get(key string) interface{} { 36 | return cache.Get(key) 37 | } 38 | 39 | //GetD get value ,if not found return a default value 40 | func GetD(key string, v interface{}) interface{} { 41 | return cache.GetD(key, v) 42 | } 43 | 44 | //Set set value 45 | func Set(key string, val interface{}) Cache { 46 | return cache.Set(key, val) 47 | } 48 | 49 | //SetWithTTL set value with time to life 50 | func SetWithTTL(key string, val interface{}, ttl int64) Cache { 51 | return cache.SetWithTTL(key, val, ttl) 52 | } 53 | 54 | //Has check value 55 | func Has(key string) bool { 56 | return cache.Has(key) 57 | } 58 | 59 | //Delete delete value 60 | func Delete(key string) Cache { 61 | return cache.Delete(key) 62 | } 63 | 64 | //Clear clear all 65 | func Clear() { 66 | cache.Clear() 67 | } 68 | 69 | //GetMultiple get multiple value 70 | func GetMultiple(keys ...string) map[string]interface{} { 71 | return cache.GetMultiple(keys...) 72 | } 73 | 74 | //SetMultiple set multiple value 75 | func SetMultiple(values map[string]interface{}) Cache { 76 | return cache.SetMultiple(values) 77 | } 78 | 79 | //DeleteMultiple delete multiple value 80 | func DeleteMultiple(keys ...string) Cache { 81 | return cache.DeleteMultiple(keys...) 82 | } 83 | -------------------------------------------------------------------------------- /cache/map_cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | /*MapCache MapCache */ 9 | type MapCache struct { 10 | cache sync.Map 11 | } 12 | 13 | type mapCacheData struct { 14 | value interface{} 15 | life *time.Time 16 | } 17 | 18 | // NewMapCache ... 19 | func NewMapCache() *MapCache { 20 | return &MapCache{} 21 | } 22 | 23 | /*Get check exist */ 24 | func (m *MapCache) Get(key string) interface{} { 25 | return m.GetD(key, nil) 26 | } 27 | 28 | /*Set check exist */ 29 | func (m *MapCache) Set(key string, val interface{}) Cache { 30 | return m.SetWithTTL(key, val, 0) 31 | } 32 | 33 | /*GetD get interface with default */ 34 | func (m *MapCache) GetD(key string, v0 interface{}) interface{} { 35 | if v, b := m.cache.Load(key); b { 36 | switch vv := v.(type) { 37 | case *mapCacheData: 38 | if vv.life != nil && vv.life.Before(time.Now()) { 39 | m.cache.Delete(key) 40 | return nil 41 | } 42 | return vv.value 43 | } 44 | } 45 | return nil 46 | } 47 | 48 | /*SetWithTTL set interface with ttl */ 49 | func (m *MapCache) SetWithTTL(key string, val interface{}, ttl int64) Cache { 50 | t := time.Now().Add(time.Duration(ttl)) 51 | m.cache.Store(key, &mapCacheData{ 52 | value: val, 53 | life: &t, 54 | }) 55 | return m 56 | } 57 | 58 | /*Has check exist */ 59 | func (m *MapCache) Has(key string) bool { 60 | if v, b := m.cache.Load(key); b { 61 | switch vv := v.(type) { 62 | case *mapCacheData: 63 | if vv.life != nil && vv.life.Before(time.Now()) { 64 | return false 65 | } 66 | return true 67 | } 68 | } 69 | return false 70 | } 71 | 72 | /*Delete one value */ 73 | func (m *MapCache) Delete(key string) Cache { 74 | m.Delete(key) 75 | return m 76 | } 77 | 78 | /*Clear delete all values */ 79 | func (m *MapCache) Clear() { 80 | *m = MapCache{} 81 | } 82 | 83 | /*GetMultiple get multiple values */ 84 | func (m *MapCache) GetMultiple(keys ...string) map[string]interface{} { 85 | c := make(map[string]interface{}) 86 | size := len(keys) 87 | for i := 0; i < size; i++ { 88 | if tmp := m.Get(keys[i]); tmp != nil { 89 | c[keys[i]] = tmp 90 | } 91 | //c[keys[i]] = nil 92 | } 93 | 94 | return c 95 | } 96 | 97 | /*SetMultiple set multiple values */ 98 | func (m *MapCache) SetMultiple(values map[string]interface{}) Cache { 99 | for k, v := range values { 100 | m.Set(k, v) 101 | } 102 | return m 103 | } 104 | 105 | /*DeleteMultiple delete multiple values */ 106 | func (m *MapCache) DeleteMultiple(keys ...string) Cache { 107 | size := len(keys) 108 | for i := 0; i < size; i++ { 109 | m.Delete(keys[i]) 110 | } 111 | 112 | return m 113 | } 114 | -------------------------------------------------------------------------------- /cache/map_cache_test.go: -------------------------------------------------------------------------------- 1 | package cache_test 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | "time" 7 | 8 | "github.com/godcong/wego/cache" 9 | ) 10 | 11 | // TestMapCache_SetWithTTL ... 12 | func TestMapCache_SetWithTTL(t *testing.T) { 13 | 14 | c := cache.DefaultCache().SetWithTTL("hello", "nihao", 100) 15 | c = cache.DefaultCache().SetWithTTL("hello1", "nihao1", 1000) 16 | log.Println(c.Get("hello")) 17 | time.Sleep(time.Duration(10) * time.Second) 18 | log.Println(c.Get("hello")) 19 | log.Println(c.Get("hello1")) 20 | } 21 | -------------------------------------------------------------------------------- /cache/redis_cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "time" 6 | ) 7 | 8 | // RedisCache ... 9 | type RedisCache struct { 10 | client *redis.Client 11 | } 12 | 13 | // Get ... 14 | func (r *RedisCache) Get(key string) interface{} { 15 | return r.GetD(key, nil) 16 | } 17 | 18 | // GetD ... 19 | func (r *RedisCache) GetD(key string, v interface{}) interface{} { 20 | s, e := r.client.Get(key).Result() 21 | if e != nil || s == "" { 22 | return v 23 | } 24 | return s 25 | } 26 | 27 | // Set ... 28 | func (r *RedisCache) Set(key string, val interface{}) Cache { 29 | r.client.Set(key, val, 0) 30 | return r 31 | } 32 | 33 | // SetWithTTL ... 34 | func (r *RedisCache) SetWithTTL(key string, val interface{}, ttl int64) Cache { 35 | r.client.Set(key, val, time.Duration(ttl)*time.Second) 36 | return r 37 | } 38 | 39 | // Has ... 40 | func (r *RedisCache) Has(key string) bool { 41 | _, err := r.client.Get(key).Result() 42 | if err != nil { 43 | return false 44 | } 45 | return true 46 | } 47 | 48 | // Delete ... 49 | func (r *RedisCache) Delete(key string) Cache { 50 | r.client.Del(key) 51 | return r 52 | } 53 | 54 | // Clear ... 55 | func (r *RedisCache) Clear() { 56 | r.client.FlushDB() 57 | } 58 | 59 | // GetMultiple ... 60 | func (r *RedisCache) GetMultiple(keys ...string) map[string]interface{} { 61 | if keys == nil { 62 | return nil 63 | } 64 | val := r.client.MGet(keys...).Val() 65 | size := len(keys) 66 | result := make(map[string]interface{}, size) 67 | for i := 0; i < size; i++ { 68 | result[keys[i]] = val[i] 69 | } 70 | return result 71 | } 72 | 73 | // SetMultiple ... 74 | func (r *RedisCache) SetMultiple(values map[string]interface{}) Cache { 75 | if values == nil { 76 | return cache 77 | } 78 | 79 | for key, value := range values { 80 | r.client.Set(key, value, 0) 81 | } 82 | return cache 83 | } 84 | 85 | // DeleteMultiple ... 86 | func (r *RedisCache) DeleteMultiple(keys ...string) Cache { 87 | r.client.Del(keys...) 88 | return r 89 | } 90 | 91 | // Options ... 92 | type Options struct { 93 | Addr string 94 | } 95 | 96 | // NewRedisCache ... 97 | func NewRedisCache(op *redis.Options) *RedisCache { 98 | client := redis.NewClient(op) 99 | _, e := client.Ping().Result() 100 | if e != nil { 101 | panic(e) 102 | } 103 | return &RedisCache{client: client} 104 | } 105 | -------------------------------------------------------------------------------- /cache/redis_cache_test.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "github.com/go-redis/redis" 5 | "github.com/godcong/wego/util" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | // TestRedisCache_Clear ... 11 | func TestRedisCache_Clear(t *testing.T) { 12 | rds := NewRedisCache(&redis.Options{ 13 | Addr: "localhost:6379", 14 | Password: "2rXfzaNKqX1b", 15 | DB: 1, 16 | }) 17 | RegisterCache(rds) 18 | for i := 0; i < 100; i++ { 19 | cache.Set(strconv.Itoa(i), util.GenerateRandomString(32)) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /cipher/aes.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "golang.org/x/xerrors" 7 | ) 8 | 9 | // CryptAES128CBC ... 10 | type cryptAES128CBC struct { 11 | iv, key []byte 12 | } 13 | 14 | // Encrypt ... 15 | func (c *cryptAES128CBC) Encrypt(data interface{}) ([]byte, error) { 16 | block, err := aes.NewCipher(c.key) //选择加密算法 17 | if err != nil { 18 | return nil, err 19 | } 20 | plantText := PKCS7Padding(parseBytes(data), block.BlockSize()) 21 | mode := cipher.NewCBCEncrypter(block, c.iv) 22 | cipherText := make([]byte, len(plantText)) 23 | mode.CryptBlocks(cipherText, plantText) 24 | 25 | return Base64Encode(cipherText), nil 26 | } 27 | 28 | // Decrypt ... 29 | func (c *cryptAES128CBC) Decrypt(data interface{}) ([]byte, error) { 30 | cipherText, e := Base64Decode(parseBytes(data)) 31 | if e != nil { 32 | return nil, xerrors.Errorf("wrong data:%w", e) 33 | } 34 | block, e := aes.NewCipher(c.key) 35 | if e != nil { 36 | return nil, e 37 | } 38 | mode := cipher.NewCBCDecrypter(block, c.iv) 39 | plantText := make([]byte, len(cipherText)) 40 | mode.CryptBlocks(plantText, cipherText) 41 | 42 | return PKCS7UnPadding(plantText), nil 43 | } 44 | 45 | // NewAES128CBC ... 46 | func NewAES128CBC(opts *Options) Cipher { 47 | key, e := Base64DecodeString(opts.Key) 48 | if e != nil { 49 | log.Error("wrong key:%w", e) 50 | return nil 51 | } 52 | iv, e := Base64DecodeString(opts.IV) 53 | if e != nil { 54 | log.Error("wrong iv:%w", e) 55 | return nil 56 | } 57 | return &cryptAES128CBC{ 58 | iv: iv, 59 | key: key, 60 | } 61 | } 62 | 63 | // Type ... 64 | func (c *cryptAES128CBC) Type() CryptType { 65 | return AES128CBC 66 | } 67 | 68 | type cryptAES256ECB struct { 69 | key []byte 70 | } 71 | 72 | // NewAES256ECB ... 73 | func NewAES256ECB(opts *Options) Cipher { 74 | return &cryptAES256ECB{ 75 | key: []byte(opts.Key), 76 | } 77 | } 78 | 79 | // Encrypt ... 80 | func (c *cryptAES256ECB) Encrypt(interface{}) ([]byte, error) { 81 | panic("aes 256 ecb encrypt was not support") 82 | } 83 | 84 | // Decrypt ... 85 | func (c *cryptAES256ECB) Decrypt(data interface{}) ([]byte, error) { 86 | decodeData, e := Base64Decode(parseBytes(data)) 87 | if e != nil { 88 | return nil, e 89 | } 90 | 91 | block, err := aes.NewCipher(c.key) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | mode := NewECBDecrypter(block) 97 | mode.CryptBlocks(decodeData, decodeData) 98 | return PKCS7UnPadding(decodeData), nil 99 | } 100 | 101 | // Type ... 102 | func (c *cryptAES256ECB) Type() CryptType { 103 | return AES256ECB 104 | } 105 | -------------------------------------------------------------------------------- /cipher/aes_test.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/xml" 7 | "github.com/godcong/wego/util" 8 | "strings" 9 | "testing" 10 | ) 11 | 12 | // TestNewDataCrypt ... 13 | func TestNewDataCrypt(t *testing.T) { 14 | //{"openid":"oE_gl0Yr54fUjBhU5nBlP4hS2efo","session_key":"v4Hn+ZHjpCD9wjU53cP0hw=="} 15 | //encrypted pLpcOaA1Z1nWaka2JkchrbAekCCWiU6+iSxHCFer8SM4nHEhOQMwinxx8lAOmg12tHu9hW1Ah3ghiT0ULjqU+/X2maXSiYUWBMyc36QX4BZB29JMnAzm2zycaGwmUX90WKv/ZmGh6UW4YU8/kj6WALNYlM7KpEJib6I3zSqP1irgCkKYoM1Bn7IWqJI+FNlCguPPXMoPoXDdycrfOR2CGDKN6gKCFAf4OHzv0lCggH12jCy0USRoAxZRcEGo2nhBmQBwi0jePRQEoBQ3H0Cn5sOQD5SGjZWsS/2pg0k6ABUXUZI/QRt1Gi5DSMG48W/Az75b3cui5lkxN4Tq0kwXs2UV0h3qR+66qlqJIQQjYKBbdKnZhw9CQnWg4k/3Ft28uiTa4LERRYHvMwzGBLsb6wQjGXXhkN9U8CR0XvpBbbQ9Jk2OYU0JDrkG8Jwx0KY/j2jYWPWu1I0ppDJXsRAyvrpUy4AqEOIst4gjpoMWvQ0= 16 | //iv u+SFMW4Rifsg3MwQ+KnNRA== 17 | //c := NewDataCrypt("wx1ad61aeef1903b93") 18 | //m, e := c.Decrypt("pLpcOaA1Z1nWaka2JkchrbAekCCWiU6+iSxHCFer8SM4nHEhOQMwinxx8lAOmg12tHu9hW1Ah3ghiT0ULjqU+/X2maXSiYUWBMyc36QX4BZB29JMnAzm2zycaGwmUX90WKv/ZmGh6UW4YU8/kj6WALNYlM7KpEJib6I3zSqP1irgCkKYoM1Bn7IWqJI+FNlCguPPXMoPoXDdycrfOR2CGDKN6gKCFAf4OHzv0lCggH12jCy0USRoAxZRcEGo2nhBmQBwi0jePRQEoBQ3H0Cn5sOQD5SGjZWsS/2pg0k6ABUXUZI/QRt1Gi5DSMG48W/Az75b3cui5lkxN4Tq0kwXs2UV0h3qR+66qlqJIQQjYKBbdKnZhw9CQnWg4k/3Ft28uiTa4LERRYHvMwzGBLsb6wQjGXXhkN9U8CR0XvpBbbQ9Jk2OYU0JDrkG8Jwx0KY/j2jYWPWu1I0ppDJXsRAyvrpUy4AqEOIst4gjpoMWvQ0=", "u+SFMW4Rifsg3MwQ+KnNRA==", "v4Hn+ZHjpCD9wjU53cP0hw==") 19 | //t.Log(string(m), e) 20 | return 21 | } 22 | 23 | // TestRefundedNotify_ServeHTTP ... 24 | func TestRefundedNotify_ServeHTTP(t *testing.T) { 25 | //xmlData := []byte(`SUCCESS`) 26 | maps := util.Map{} 27 | //err := xml.Unmarshal(xmlData, &maps) 28 | //t.Log(maps) 29 | //t.Log(err) 30 | reqInfo := `XXeD817ddbzJMC1Zsb10XjOnveEaVHqv99DZ/fOL9cOvaXuvp2ZjfJeFqkDBPbHlHbyGyRW7FKT2Hy7Zj8eGC/Zz4hgyX84rUJatxhpw+W9bJRyqm6xuo20dEMcLqa0CZ44J8jcSjp3bvmi9yDmnpETSorUojhnoOL3qAVOi8d8J4X5r9cOlm4MWbvDgseMBBD4c7sGrSUl9P7p1sDomr5C/p052JZjYkgWSYquQp1UzuYO0Ol6utJ9yPupX1d1OGcBnY3upVCSFsfaeXendvSD5Rzs+chyx/t2JcgohvvtqH4225lAiA+6rksMPFolSdHy4qS5kuJNEEZSKqW7igpf6hVTXkWKRW6SWuppf1MbyFYB3JwJueQdTXzUOuYBth4RMSmoKBFhdf0t60GBFpPqo1iBEsCHKDhzOAL5CKksUx5CiD8XimyyylBn5ULTnImK8qJAZCELdTsQnFcyWJfV1QtNkDXsYr/nA2AHHjvVMahPH3zKeXRPFCgEBk3JprP9zIvLVFDwWuXcfJniLpsB+sc5NR4SZptx3A9+3nHWiBnu3riIrAsgwc754kiSAarZeblrbFtlpoaQpra5ixKRTpsGCdwq8NBvEcseVxubRm+AdeEc+gIoilqP2H+Eu2Zvwoqj/UQKEXR0Bg7498j0V+0SjGiomqdXwtauZia9S9HG6SqvcWPBrfdTtEuQyv/eW9zq/QDxGQlPMfGhZq6hpvXFkZG1TRHaejaymM6AGWtBLkV1ypUTvlB4zqyzPtD6jRrRck6VYcK81U+Y7P3sWekLlTAX7C04PPRoITmpG93p+DUszbHubFYMEc+TEH1mlBOix6fpzzSbPlG0BKE/foH+9oYiW5B2i103SqjlfvoHUW+cizVMBgDTT+5OvDmrozgyFyt8CrU7Gq15KIR9xQTQZK68NaATrR11UxmnpOcGJt0+LIXH/EtZIxg5nwKys4AmEkbgRMn6xbGGz0mDw1gDya8QJCxwU4KLpz0tcst2YBes3OqlYtXcm7Ow2hpjG5dybeAc1fHYnOOmPl0M08nHAad4NwF3uPo+eIto=` 31 | payKey := "aTKnSUcTkbEnhwQNdutWkQxAjnhAz2jK" 32 | 33 | key := strings.ToLower(string(util.SignMD5(payKey, ""))) 34 | log.Println(key) 35 | 36 | ecb := New(AES256ECB, &Option{ 37 | Key: key, 38 | }) 39 | 40 | d, err := ecb.Decrypt([]byte(reqInfo)) 41 | t.Error(err) 42 | t.Log(string(d)) 43 | err = xml.Unmarshal(d, &maps) 44 | t.Log(maps) 45 | t.Log(err) 46 | return 47 | } 48 | 49 | var validateEncryptCryptAES128CBC = []byte(`vuuUEGrIilcZMRvbNkRaV3wtRE4guDjo4TGiXYDt9wPNfaHxtXLIB3qipjxmJSstK3WFm32Lu/whd4+XkDcQBu5cZy0D2WwJLJY/4jrNczrRVFIxpRshAVoIDY88rPdEec51Qc+UMqKBBytGf8KXm3vXvBdq1taf2ZwF7rlEtcPdCCL0uG5qL3LByQm7OnsIPi/iBsOt+CxcwH4eNUAACvQCZmhEj89LznO6zAWvloqrL1pXmt1mIVRIq7lyTxEbgo5OMcsKlbyVFGU1WKhTfkX2pCVLUrsXu+dJrh3BheCoBIq0GkouyZ+cUZr3JpcI02tGaCcX2j4L29QFKGgquL6/w5Qn0hW4frpUuWTaY9hkpLOfVSpWDxe2i2cLdgTv2bbbkMx9kShRuNcFAQoB9bbaUurY/hwGaY6ybnYvt/OgqBHATrswbaF/7YdxDMaM`) 50 | var validateDecryptCryptAES128CBC = []byte(` 51 | 52 | 53 | 1524409354 54 | 55 | 56 | 6547288321577417974 57 | `) 58 | 59 | // TestPrpCrypt_Encrypt ... 60 | func TestCryptAES128CBC(t *testing.T) { 61 | k, _ := base64.RawStdEncoding.DecodeString(encodingAesKey) 62 | log.Println(string(k)) 63 | prp := &cryptAES128CBC{ 64 | iv: k[:16], 65 | key: k, 66 | } 67 | b, e := prp.Encrypt(text) 68 | if bytes.Compare(b, validateEncryptCryptAES128CBC) != 0 || e != nil { 69 | t.Error(string(b), e) 70 | return 71 | } 72 | t.Log(string(b)) 73 | 74 | b, e = prp.Decrypt(b) 75 | if e != nil { 76 | t.Error(string(b), e) 77 | return 78 | } 79 | t.Log(string(b)) 80 | 81 | } 82 | -------------------------------------------------------------------------------- /cipher/bizmsg.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import ( 4 | "bytes" 5 | "encoding/base64" 6 | "encoding/binary" 7 | "encoding/xml" 8 | "github.com/godcong/wego/util" 9 | "golang.org/x/xerrors" 10 | ) 11 | 12 | /*ErrorCodeType ErrorCodeType */ 13 | type ErrorCodeType int 14 | 15 | /*error code types */ 16 | const ( 17 | OK ErrorCodeType = 0 18 | ValidateSignatureError = -40001 19 | ParseXMLError = -40002 20 | ComputeSignatureError = -40003 21 | IllegalAesKey = -40004 22 | ValidateAppidError = -40005 23 | EncryptAESError = -40006 24 | DecryptAESError = -40007 25 | IllegalBuffer = -40008 26 | EncodeBase64Error = -40009 27 | DecodeBase64Error = -40010 28 | GenReturnXMLError = -40011 29 | ) 30 | 31 | /*ErrorCode ErrorCode */ 32 | var ErrorCode = map[string]ErrorCodeType{ 33 | "OK": OK, 34 | "ValidateSignatureError": ValidateSignatureError, 35 | "ParseXMLError": ParseXMLError, 36 | "ComputeSignatureError": ComputeSignatureError, 37 | "IllegalAesKey": IllegalAesKey, 38 | "ValidateAppIDError": ValidateAppidError, 39 | "EncryptAESError": EncryptAESError, 40 | "DecryptAESError": DecryptAESError, 41 | "IllegalBuffer": IllegalBuffer, 42 | "EncodeBase64Error": EncodeBase64Error, 43 | "DecodeBase64Error": DecodeBase64Error, 44 | "GenReturnXMLError": GenReturnXMLError, 45 | } 46 | 47 | /*BizMsg BizMsg */ 48 | type cryptBizMsg struct { 49 | token string 50 | key []byte 51 | id string 52 | cipher Cipher 53 | } 54 | 55 | // BizMsgData ... 56 | type BizMsgData struct { 57 | _ xml.Name `xml:"xml"` 58 | Text string `xml:"-"` 59 | RSAEncrypt string `xml:"RSAEncrypt"` 60 | TimeStamp string `xml:"TimeStamp"` 61 | Nonce string `xml:"Nonce"` 62 | MsgSignature string `xml:"MsgSignature"` 63 | } 64 | 65 | // EncryptBizMsg ... 66 | func EncryptBizMsg(text, ts, nonce string) interface{} { 67 | return &BizMsgData{ 68 | Text: text, 69 | TimeStamp: ts, 70 | Nonce: nonce, 71 | } 72 | } 73 | 74 | // DecryptBizMsg ... 75 | func DecryptBizMsg(text string, ts, nonce, rsa string) interface{} { 76 | return &BizMsgData{ 77 | Text: text, 78 | RSAEncrypt: rsa, 79 | TimeStamp: ts, 80 | Nonce: nonce, 81 | } 82 | } 83 | 84 | // Type ... 85 | func (obj *cryptBizMsg) Type() CryptType { 86 | return BizMsg 87 | } 88 | 89 | // Encrypt ... 90 | func (obj *cryptBizMsg) Encrypt(data interface{}) ([]byte, error) { 91 | bizMsg, e := parseBizMsg(data) 92 | if e != nil { 93 | return nil, e 94 | } 95 | buf := bytes.Buffer{} 96 | buf.WriteString(obj.RandomString()) 97 | buf.Write(obj.LengthBytes(bizMsg.Text)) 98 | buf.WriteString(bizMsg.Text) 99 | buf.WriteString(obj.id) 100 | 101 | encrypt, e := obj.cipher.Encrypt(buf.Bytes()) 102 | if e != nil { 103 | return nil, xerrors.Errorf("biz msg encrypt:%w", e) 104 | } 105 | 106 | r := &BizMsgData{ 107 | Text: "", 108 | RSAEncrypt: string(encrypt), 109 | TimeStamp: bizMsg.TimeStamp, 110 | Nonce: bizMsg.Nonce, 111 | MsgSignature: util.GenSHA1(obj.token, bizMsg.TimeStamp, bizMsg.Nonce, string(encrypt)), 112 | } 113 | 114 | return xml.Marshal(r) 115 | } 116 | 117 | // Decrypt ... 118 | func (obj *cryptBizMsg) Decrypt(data interface{}) ([]byte, error) { 119 | bizMsg, e := parseBizMsg(data) 120 | if e != nil { 121 | log.Error(e) 122 | return nil, e 123 | } 124 | e = xml.Unmarshal([]byte(bizMsg.Text), bizMsg) 125 | if e != nil { 126 | log.Error(e) 127 | return nil, e 128 | } 129 | newSign := util.GenSHA1(obj.token, bizMsg.TimeStamp, bizMsg.Nonce, bizMsg.RSAEncrypt) 130 | if bizMsg.MsgSignature != newSign { 131 | log.Error(bizMsg.MsgSignature, newSign) 132 | return nil, xerrors.New("ValidateSignatureError") 133 | } 134 | decrypt, e := obj.cipher.Decrypt(bizMsg.RSAEncrypt) 135 | if e != nil { 136 | log.Error(e) 137 | return nil, e 138 | } 139 | 140 | buf := bytes.NewBuffer(decrypt) 141 | _ = buf.Next(16) //skip first 16 random string 142 | size := obj.BytesLength(buf.Next(4)) //size:4 bit 143 | content := buf.Next(int(size)) //content:size 144 | id := buf.Bytes() //end:id 145 | if string(id) != obj.id { 146 | return nil, xerrors.New("ValidateAppIDError") 147 | } 148 | return content, nil 149 | 150 | } 151 | 152 | /*NewBizMsg NewBizMsg */ 153 | func NewBizMsg(opts *Options) Cipher { 154 | key, e := base64.RawStdEncoding.DecodeString(opts.Key) 155 | if e != nil { 156 | return nil 157 | } 158 | return &cryptBizMsg{ 159 | token: opts.Token, 160 | key: key, 161 | id: opts.ID, 162 | cipher: &cryptAES128CBC{ 163 | iv: key[:16], 164 | key: key, 165 | }, 166 | } 167 | } 168 | 169 | /*RandomString RandomString */ 170 | func (obj *cryptBizMsg) RandomString() string { 171 | return util.GenerateRandomString(16, util.RandomAll) 172 | } 173 | 174 | /*LengthBytes LengthBytes */ 175 | func (obj *cryptBizMsg) LengthBytes(s string) []byte { 176 | var buf = make([]byte, 4) 177 | binary.BigEndian.PutUint32(buf, uint32(len(s))) 178 | return buf 179 | } 180 | 181 | /*BytesLength BytesLength */ 182 | func (obj *cryptBizMsg) BytesLength(b []byte) uint32 { 183 | return binary.BigEndian.Uint32(b) 184 | } 185 | -------------------------------------------------------------------------------- /cipher/bizmsg_test.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import "testing" 4 | 5 | var encodingAesKey = "TNwHN28RXXoyVxkMCUEqKuCL08eBpCKgWZTkWNVnGLu" 6 | var token = "godcong" 7 | var timeStamp = "1524409354" 8 | var nonce = "542437598" 9 | var appID = "wxbafed7010e0f4531" 10 | var text = ` 11 | 12 | 13 | 1524409354 14 | 15 | 16 | 6547288321577417974 17 | ` 18 | 19 | var text1 = `` 20 | var decrypt1 = `16329091791524416866` 21 | var decrypt2 = `1524421916457570794` 22 | var bizMsg = New(BizMsg, &Option{ 23 | IV: "", 24 | Key: encodingAesKey, 25 | RSAPrivate: "", 26 | RSAPublic: "", 27 | Token: token, 28 | ID: appID, 29 | }) 30 | 31 | // TestBizMsg_Encrypt ... 32 | func TestCryptBizMsg_Encrypt(t *testing.T) { 33 | var e error 34 | enc, e := bizMsg.Encrypt(EncryptBizMsg(text, timeStamp, nonce)) 35 | if e != nil { 36 | t.Error(string(enc), e) 37 | return 38 | } 39 | t.Log(string(enc)) 40 | 41 | } 42 | 43 | // TestCryptBizMsg_Decrypt ... 44 | func TestCryptBizMsg_Decrypt(t *testing.T) { 45 | data := &BizMsgData{ 46 | //RSAEncrypt: "", 47 | //TimeStamp: "1524421916", 48 | //Nonce: "457570794", 49 | //MsgSignature: "8c0f8d64124367eccb5f292dad91955eb0cd12d8", 50 | } 51 | data.Text = decrypt1 52 | dec1, e := bizMsg.Decrypt(data) 53 | if e != nil { 54 | t.Error(string(dec1), e) 55 | return 56 | } 57 | t.Log(string(dec1)) 58 | dec2, e := bizMsg.Decrypt(decrypt2) 59 | if e != nil { 60 | t.Error(string(dec2), e) 61 | return 62 | } 63 | t.Log(string(dec2)) 64 | 65 | dec3, e := bizMsg.Decrypt(DecryptBizMsg(decrypt1)) 66 | if e != nil { 67 | t.Error(string(dec3), e) 68 | return 69 | } 70 | t.Log(string(dec3)) 71 | } 72 | -------------------------------------------------------------------------------- /cipher/cipher.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import ( 4 | "encoding/base64" 5 | "golang.org/x/xerrors" 6 | ) 7 | 8 | // CryptType ... 9 | type CryptType int 10 | 11 | // AES128CBC ... 12 | const ( 13 | AES128CBC CryptType = iota 14 | AES256ECB = iota 15 | BizMsg = iota 16 | RSA 17 | ) 18 | 19 | // InstanceFunc ... 20 | type InstanceFunc func(opts *Options) Cipher 21 | 22 | // Cipher ... 23 | type Cipher interface { 24 | Type() CryptType 25 | Encrypt(interface{}) ([]byte, error) 26 | Decrypt(interface{}) ([]byte, error) 27 | } 28 | 29 | var cipherList = []InstanceFunc{ 30 | AES128CBC: NewAES128CBC, 31 | AES256ECB: NewAES256ECB, 32 | BizMsg: NewBizMsg, 33 | RSA: NewRSA, 34 | } 35 | 36 | // Options ... 37 | type Options struct { 38 | IV string 39 | Key string 40 | RSAPrivate string 41 | RSAPublic string 42 | Token string 43 | ID string 44 | } 45 | 46 | // OptionKey ... 47 | func OptionKey(s string) func(opts *Options) { 48 | return func(opts *Options) { 49 | opts.Key = s 50 | } 51 | } 52 | 53 | // OptionIV ... 54 | func OptionIV(s string) func(opts *Options) { 55 | return func(opts *Options) { 56 | opts.IV = s 57 | } 58 | } 59 | 60 | // OptionToken ... 61 | func OptionToken(s string) func(opts *Options) { 62 | return func(opts *Options) { 63 | opts.Token = s 64 | } 65 | } 66 | 67 | // OptionID ... 68 | func OptionID(s string) func(opts *Options) { 69 | return func(opts *Options) { 70 | opts.ID = s 71 | } 72 | } 73 | 74 | // OptionPublic ... 75 | func OptionPublic(s string) func(opts *Options) { 76 | return func(opts *Options) { 77 | opts.RSAPublic = s 78 | } 79 | } 80 | 81 | // OptionPrivate ... 82 | func OptionPrivate(s string) func(opts *Options) { 83 | return func(opts *Options) { 84 | opts.RSAPrivate = s 85 | } 86 | } 87 | 88 | // Option ... 89 | type Option func(opts *Options) 90 | 91 | // New create a new cipher 92 | func New(cryptType CryptType, opts ...Option) Cipher { 93 | var options Options 94 | for _, o := range opts { 95 | o(&options) 96 | } 97 | return cipherList[cryptType](&options) 98 | } 99 | 100 | func parseBytes(data interface{}) []byte { 101 | switch tmp := data.(type) { 102 | case []byte: 103 | return tmp 104 | case string: 105 | return []byte(tmp) 106 | default: 107 | return nil 108 | } 109 | } 110 | 111 | func parseBizMsg(data interface{}) (d *BizMsgData, e error) { 112 | switch tmp := data.(type) { 113 | case *BizMsgData: 114 | d = tmp 115 | case BizMsgData: 116 | d = &tmp 117 | case string: 118 | d = &BizMsgData{ 119 | Text: tmp, 120 | } 121 | default: 122 | e = xerrors.New("wrong type inputed") 123 | } 124 | return 125 | } 126 | 127 | /*Base64Encode Base64Encode */ 128 | func Base64Encode(b []byte) []byte { 129 | buf := make([]byte, base64.StdEncoding.EncodedLen(len(b))) 130 | base64.StdEncoding.Encode(buf, b) 131 | return buf 132 | } 133 | 134 | /*Base64Decode Base64Decode */ 135 | func Base64Decode(b []byte) ([]byte, error) { 136 | buf := make([]byte, base64.StdEncoding.DecodedLen(len(b))) 137 | n, err := base64.StdEncoding.Decode(buf, b) 138 | return buf[:n], err 139 | } 140 | 141 | /*Base64DecodeString Base64DecodeString */ 142 | func Base64DecodeString(s string) ([]byte, error) { 143 | return base64.StdEncoding.DecodeString(s) 144 | } 145 | -------------------------------------------------------------------------------- /cipher/ecb.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import "crypto/cipher" 4 | 5 | type ecb struct { 6 | b cipher.Block 7 | blockSize int 8 | } 9 | 10 | func newECB(b cipher.Block) *ecb { 11 | return &ecb{ 12 | b: b, 13 | blockSize: b.BlockSize(), 14 | } 15 | } 16 | 17 | type ecbEncrypter ecb 18 | 19 | // NewECBEncrypter returns a BlockMode which encrypts in electronic code book 20 | // mode, using the given Block. 21 | func NewECBEncrypter(b cipher.Block) cipher.BlockMode { 22 | return (*ecbEncrypter)(newECB(b)) 23 | } 24 | 25 | // BlockSize ... 26 | func (x *ecbEncrypter) BlockSize() int { return x.blockSize } 27 | 28 | // CryptBlocks ... 29 | func (x *ecbEncrypter) CryptBlocks(dst, src []byte) { 30 | if len(src)%x.blockSize != 0 { 31 | panic("crypto/cipher: input not full blocks") 32 | } 33 | if len(dst) < len(src) { 34 | panic("crypto/cipher: output smaller than input") 35 | } 36 | for len(src) > 0 { 37 | x.b.Encrypt(dst, src[:x.blockSize]) 38 | src = src[x.blockSize:] 39 | dst = dst[x.blockSize:] 40 | } 41 | } 42 | 43 | type ecbDecrypter ecb 44 | 45 | // NewECBDecrypter returns a BlockMode which decrypts in electronic code book 46 | // mode, using the given Block. 47 | func NewECBDecrypter(b cipher.Block) cipher.BlockMode { 48 | return (*ecbDecrypter)(newECB(b)) 49 | } 50 | 51 | // BlockSize ... 52 | func (x *ecbDecrypter) BlockSize() int { return x.blockSize } 53 | 54 | // CryptBlocks ... 55 | func (x *ecbDecrypter) CryptBlocks(dst, src []byte) { 56 | if len(src)%x.blockSize != 0 { 57 | panic("crypto/cipher: input not full blocks") 58 | } 59 | if len(dst) < len(src) { 60 | panic("crypto/cipher: output smaller than input") 61 | } 62 | for len(src) > 0 { 63 | x.b.Decrypt(dst, src[:x.blockSize]) 64 | src = src[x.blockSize:] 65 | dst = dst[x.blockSize:] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /cipher/errors.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | type cipherError struct { 4 | Message string 5 | } 6 | 7 | // Err ... 8 | func Err(msg string) error { 9 | return &cipherError{ 10 | Message: msg, 11 | } 12 | } 13 | 14 | // Error ... 15 | func (e *cipherError) Error() string { 16 | return e.Message 17 | } 18 | -------------------------------------------------------------------------------- /cipher/log.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import ( 4 | "github.com/godcong/go-trait" 5 | ) 6 | 7 | var log = trait.ZapSugar() 8 | -------------------------------------------------------------------------------- /cipher/padding.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import "bytes" 4 | 5 | // ZeroPadding ... 6 | func ZeroPadding(ciphertext []byte, blockSize int) []byte { 7 | padding := blockSize - len(ciphertext)%blockSize 8 | padtext := bytes.Repeat([]byte{0}, padding) 9 | return append(ciphertext, padtext...) 10 | } 11 | 12 | // ZeroUnPadding ... 13 | func ZeroUnPadding(origData []byte) []byte { 14 | return bytes.TrimRightFunc(origData, func(r rune) bool { 15 | return r == rune(0) 16 | }) 17 | } 18 | 19 | /*PKCS7Padding PKCS7Padding */ 20 | func PKCS7Padding(ciphertext []byte, blockSize int) []byte { 21 | padding := blockSize - len(ciphertext)%blockSize 22 | padtext := bytes.Repeat([]byte{byte(padding)}, padding) 23 | return append(ciphertext, padtext...) 24 | } 25 | 26 | /*PKCS7UnPadding PKCS7UnPadding */ 27 | func PKCS7UnPadding(plantText []byte) []byte { 28 | length := len(plantText) 29 | unpadding := int(plantText[length-1]) 30 | //if unpadding < 1 || unpadding > 32 { 31 | // unpadding = 0 32 | //} 33 | return plantText[:(length - unpadding)] 34 | } 35 | -------------------------------------------------------------------------------- /cipher/rsa.go: -------------------------------------------------------------------------------- 1 | package cipher 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "crypto/sha1" 7 | "crypto/x509" 8 | "encoding/base64" 9 | "encoding/pem" 10 | "errors" 11 | "golang.org/x/xerrors" 12 | ) 13 | 14 | /* all defined errors */ 15 | var ( 16 | ErrorKeyMustBePEMEncoded = errors.New("key must be pem encoded") 17 | ErrorNotECPublicKey = errors.New("key is not a valid ECDSA public key") 18 | ErrorNotECPrivateKey = errors.New("key is not a valid ECDSA private key") 19 | ErrorNotRSAPrivateKey = errors.New("key is not a valid RSA private key") 20 | ErrorNotRSAPublicKey = errors.New("key is not a valid RSA public key") 21 | ) 22 | 23 | type cryptRSA struct { 24 | privateKey []byte 25 | publicKey []byte 26 | } 27 | 28 | // NewRSA ... 29 | func NewRSA(opts *Options) Cipher { 30 | return &cryptRSA{ 31 | privateKey: []byte(opts.RSAPrivate), 32 | publicKey: []byte(opts.RSAPublic), 33 | } 34 | } 35 | 36 | // Encrypt ... 37 | func (c *cryptRSA) Encrypt(data interface{}) ([]byte, error) { 38 | key, err := ParseRSAPublicKeyFromPEM(c.publicKey) 39 | if err != nil { 40 | return nil, xerrors.Errorf("ParseRSAPublicKeyFromPEM:%w", err) 41 | } 42 | part, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, key, parseBytes(data), nil) 43 | if err != nil { 44 | return nil, xerrors.Errorf("EncryptOAEP:%w", err) 45 | } 46 | 47 | buf := make([]byte, base64.StdEncoding.EncodedLen(len(part))) 48 | base64.StdEncoding.Encode(buf, part) 49 | return buf, nil 50 | } 51 | 52 | // Decrypt ... 53 | func (c *cryptRSA) Decrypt(data interface{}) ([]byte, error) { 54 | key, err := ParseRSAPrivateKeyFromPEM(c.privateKey) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | t, err := Base64Decode(parseBytes(data)) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | b, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, key, t, nil) 65 | if err != nil { 66 | return nil, err 67 | } 68 | return b, nil 69 | } 70 | 71 | // Type ... 72 | func (*cryptRSA) Type() CryptType { 73 | return RSA 74 | } 75 | 76 | /*ParseRSAPrivateKeyFromPEM Parse PEM encoded PKCS1 or PKCS8 private key */ 77 | func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { 78 | block, _ := pem.Decode(key) 79 | if block == nil { 80 | return nil, ErrorKeyMustBePEMEncoded 81 | } 82 | 83 | pkey, err := x509.ParsePKCS1PrivateKey(block.Bytes) 84 | if err != nil { 85 | if parsedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { 86 | var ok bool 87 | if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { 88 | return nil, ErrorNotRSAPrivateKey 89 | } 90 | } 91 | } 92 | return pkey, nil 93 | } 94 | 95 | /*ParseRSAPublicKeyFromPEM Parse PEM encoded PKCS1 or PKCS8 public key */ 96 | func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { 97 | block, _ := pem.Decode(key) 98 | if block == nil { 99 | return nil, ErrorKeyMustBePEMEncoded 100 | } 101 | 102 | // Parse the key 103 | parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes) 104 | if err != nil { 105 | if cert, err := x509.ParseCertificate(block.Bytes); err == nil { 106 | parsedKey = cert.PublicKey 107 | } else { 108 | return nil, err 109 | } 110 | } 111 | 112 | if pkey, ok := parsedKey.(*rsa.PublicKey); ok { 113 | return pkey, nil 114 | } 115 | 116 | return nil, ErrorNotRSAPublicKey 117 | 118 | } 119 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "github.com/godcong/wego/util" 7 | "golang.org/x/xerrors" 8 | "io" 9 | "io/ioutil" 10 | "math" 11 | "net" 12 | "net/http" 13 | "time" 14 | ) 15 | 16 | const defaultCa = ` 17 | -----BEGIN CERTIFICATE----- 18 | MIIDIDCCAomgAwIBAgIENd70zzANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV 19 | UzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2Vy 20 | dGlmaWNhdGUgQXV0aG9yaXR5MB4XDTk4MDgyMjE2NDE1MVoXDTE4MDgyMjE2NDE1 21 | MVowTjELMAkGA1UEBhMCVVMxEDAOBgNVBAoTB0VxdWlmYXgxLTArBgNVBAsTJEVx 22 | dWlmYXggU2VjdXJlIENlcnRpZmljYXRlIEF1dGhvcml0eTCBnzANBgkqhkiG9w0B 23 | AQEFAAOBjQAwgYkCgYEAwV2xWGcIYu6gmi0fCG2RFGiYCh7+2gRvE4RiIcPRfM6f 24 | BeC4AfBONOziipUEZKzxa1NfBbPLZ4C/QgKO/t0BCezhABRP/PvwDN1Dulsr4R+A 25 | cJkVV5MW8Q+XarfCaCMczE1ZMKxRHjuvK9buY0V7xdlfUNLjUA86iOe/FP3gx7kC 26 | AwEAAaOCAQkwggEFMHAGA1UdHwRpMGcwZaBjoGGkXzBdMQswCQYDVQQGEwJVUzEQ 27 | MA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBTZWN1cmUgQ2VydGlm 28 | aWNhdGUgQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMBoGA1UdEAQTMBGBDzIwMTgw 29 | ODIyMTY0MTUxWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUSOZo+SvSspXXR9gj 30 | IBBPM5iQn9QwHQYDVR0OBBYEFEjmaPkr0rKV10fYIyAQTzOYkJ/UMAwGA1UdEwQF 31 | MAMBAf8wGgYJKoZIhvZ9B0EABA0wCxsFVjMuMGMDAgbAMA0GCSqGSIb3DQEBBQUA 32 | A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y 33 | 7qj/WsjTVbJmcVfewCHrPSqnI0kBBIZCe/zuf6IWUrVnZ9NA2zsmWLIodz2uFHdh 34 | 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 35 | -----END CERTIFICATE-----` 36 | 37 | // RequestContent ... 38 | type RequestContent struct { 39 | Method string 40 | URL string 41 | Query util.Map 42 | Body *RequestBody 43 | } 44 | 45 | // Client ... 46 | type Client struct { 47 | context context.Context 48 | TLSConfig *tls.Config 49 | BodyType BodyType 50 | //useSafe bool 51 | //safeCert *SafeCertProperty 52 | accessToken *AccessToken 53 | //timeout int64 54 | //keepAlive int64 55 | } 56 | 57 | // UseSafe ... 58 | func (obj *Client) UseSafe() bool { 59 | return obj.TLSConfig != nil 60 | } 61 | 62 | // Context ... 63 | func (obj *Client) Context() context.Context { 64 | if obj.context == nil { 65 | i, _ := Context() 66 | return i 67 | } 68 | return obj.context 69 | } 70 | 71 | // NewClient ... 72 | func NewClient(options ...ClientOption) *Client { 73 | client := &Client{ 74 | BodyType: BodyTypeXML, 75 | } 76 | client.parse(options...) 77 | return client 78 | } 79 | 80 | func (obj *Client) parse(options ...ClientOption) { 81 | if options == nil { 82 | return 83 | } 84 | 85 | for _, o := range options { 86 | o(obj) 87 | } 88 | } 89 | 90 | // GetToken ... 91 | func (obj *Client) GetToken() (util.Map, error) { 92 | if obj.accessToken != nil { 93 | return obj.accessToken.KeyMap(), nil 94 | } 95 | return nil, xerrors.New("nil accessToken") 96 | } 97 | 98 | // MustToken ... 99 | func (obj *Client) MustToken() (token util.Map) { 100 | token, err := obj.GetToken() 101 | if err != nil { 102 | panic(err) 103 | } 104 | return token 105 | } 106 | 107 | // Post ... 108 | func (obj *Client) Post(ctx context.Context, url string, query util.Map, body interface{}) Responder { 109 | log.Debug("post ", url, body) 110 | return obj.do(ctx, &RequestContent{ 111 | Method: POST, 112 | URL: url, 113 | Query: util.CombineMaps(query, obj.MustToken()), 114 | Body: buildBody(body, obj.BodyType), 115 | }) 116 | } 117 | 118 | // Get ... 119 | func (obj *Client) Get(ctx context.Context, url string, query util.Map) Responder { 120 | log.Debug("get ", url) 121 | return obj.do(ctx, &RequestContent{ 122 | Method: POST, 123 | URL: url, 124 | Query: util.CombineMaps(query, obj.MustToken()), 125 | Body: buildBody(nil, obj.BodyType), 126 | }) 127 | } 128 | 129 | // HTTPClient ... 130 | func (obj *Client) HTTPClient() (*http.Client, error) { 131 | return buildHTTPClient(obj, obj.UseSafe()) 132 | } 133 | 134 | // do ... 135 | func (obj *Client) do(ctx context.Context, content *RequestContent) Responder { 136 | client, e := obj.HTTPClient() 137 | if e != nil { 138 | return ErrResponder(xerrors.Errorf("client build err:%+v", e)) 139 | } 140 | request, e := content.BuildRequest() 141 | if e != nil { 142 | return ErrResponder(xerrors.Errorf("request build err:%+v", e)) 143 | } 144 | response, e := client.Do(request.WithContext(ctx)) 145 | if e != nil { 146 | return ErrResponder(xerrors.Errorf("response get err:%+v", e)) 147 | } 148 | return BuildResponder(response) 149 | } 150 | 151 | // PostForm post form request 152 | func PostForm(url string, query util.Map, form interface{}) Responder { 153 | log.Debug("post form:", url, query, form) 154 | client := &Client{ 155 | BodyType: BodyTypeForm, 156 | } 157 | return client.Post(context.Background(), url, query, form) 158 | } 159 | 160 | // PostJSON json post请求 161 | func PostJSON(url string, query util.Map, json interface{}) Responder { 162 | log.Debug("post json:", url, query, json) 163 | client := &Client{ 164 | BodyType: BodyTypeJSON, 165 | } 166 | return client.Post(context.Background(), url, query, json) 167 | } 168 | 169 | // PostXML xml post请求 170 | func PostXML(url string, query util.Map, xml interface{}) Responder { 171 | log.Debug("post xml:", url, query, xml) 172 | client := &Client{ 173 | BodyType: BodyTypeXML, 174 | } 175 | return client.Post(context.Background(), url, query, xml) 176 | } 177 | 178 | // Upload upload请求 179 | func Upload(url string, query, multi util.Map) Responder { 180 | client := &Client{ 181 | BodyType: BodyTypeMultipart, 182 | } 183 | 184 | return client.Post(context.Background(), url, query, multi) 185 | } 186 | 187 | // Get get请求 188 | func Get(url string, query util.Map) Responder { 189 | log.Println("get request:", url, query) 190 | client := NewClient() 191 | return client.Get(context.Background(), url, query) 192 | } 193 | 194 | // Context ... 195 | func Context() (context.Context, context.CancelFunc) { 196 | return context.WithTimeout(context.Background(), 30*time.Second) 197 | } 198 | 199 | func buildTransport(client *Client) (*http.Transport, error) { 200 | return &http.Transport{ 201 | Proxy: nil, 202 | DialContext: (&net.Dialer{ 203 | //Timeout: client.TimeOut(), 204 | //KeepAlive: client.KeepAlive(), 205 | }).DialContext, 206 | //Dial: nil, 207 | //DialTLS: nil, 208 | TLSClientConfig: &tls.Config{ 209 | InsecureSkipVerify: true, 210 | }, 211 | //TLSHandshakeTimeout: 0, 212 | //DisableKeepAlives: false, 213 | //DisableCompression: false, 214 | //MaxIdleConns: 0, 215 | //MaxIdleConnsPerHost: 0, 216 | //MaxConnsPerHost: 0, 217 | //IdleConnTimeout: 0, 218 | //ResponseHeaderTimeout: 0, 219 | //ExpectContinueTimeout: 0, 220 | //TLSNextProto: nil, 221 | //ProxyConnectHeader: nil, 222 | //MaxResponseHeaderBytes: 0, 223 | }, nil 224 | 225 | } 226 | 227 | func buildSafeTransport(client *Client) (*http.Transport, error) { 228 | return &http.Transport{ 229 | DialContext: (&net.Dialer{ 230 | //Timeout: client.TimeOut(), 231 | //KeepAlive: client.KeepAlive(), 232 | //DualStack: true, 233 | }).DialContext, 234 | TLSClientConfig: client.TLSConfig, 235 | Proxy: nil, 236 | //TLSHandshakeTimeout: 10 * time.Second, 237 | //ResponseHeaderTimeout: 10 * time.Second, 238 | //ExpectContinueTimeout: 1 * time.Second, 239 | }, nil 240 | } 241 | 242 | func buildHTTPClient(client *Client, isSafe bool) (cli *http.Client, e error) { 243 | cli = new(http.Client) 244 | //判断能否创建safe client 245 | fun := buildTransport 246 | if isSafe { 247 | fun = buildSafeTransport 248 | } 249 | cli.Transport, e = fun(client) 250 | return 251 | } 252 | 253 | // BodyReader ... 254 | type BodyReader interface { 255 | ToMap() util.Map 256 | Bytes() []byte 257 | Error() error 258 | Unmarshal(v interface{}) error 259 | Result() (util.Map, error) 260 | } 261 | 262 | /*readBody get response data */ 263 | func readBody(r io.ReadCloser) ([]byte, error) { 264 | return ioutil.ReadAll(io.LimitReader(r, math.MaxUint32)) 265 | } 266 | 267 | // BuildRequest ... 268 | func (c *RequestContent) BuildRequest() (*http.Request, error) { 269 | if c.Body == nil { 270 | return http.NewRequest(c.Method, c.URLQuery(), nil) 271 | } 272 | return c.Body.RequestBuilder(c.Method, c.URLQuery(), c.Body.BodyInstance) 273 | } 274 | 275 | // URLQuery ... 276 | func (c *RequestContent) URLQuery() string { 277 | if c.Query == nil { 278 | return c.URL 279 | } 280 | return c.URL + "?" + c.Query.URLEncode() 281 | 282 | } 283 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "github.com/pelletier/go-toml" 5 | ) 6 | 7 | // Config 配置文件,用来生成Property各种属性 8 | type Config struct { 9 | AppID string 10 | AppSecret string 11 | MchID string 12 | MchKey string 13 | PemCert []byte 14 | PemKEY []byte 15 | RootCA []byte 16 | Token string 17 | AesKey string 18 | Scopes []string 19 | RedirectURI string 20 | } 21 | 22 | // DefaultConfig ... 23 | func DefaultConfig() *Config { 24 | return &Config{} 25 | } 26 | 27 | // LoadConfig ... 28 | func LoadConfig(path string) *Config { 29 | cfg := DefaultConfig() 30 | t, e := toml.LoadFile(path) 31 | if e != nil { 32 | log.Info("filepath: " + path) 33 | log.Info(e.Error()) 34 | return DefaultConfig() 35 | } 36 | 37 | e = t.Unmarshal(cfg) 38 | if e != nil { 39 | log.Info("filepath: " + path) 40 | log.Info(e.Error()) 41 | return DefaultConfig() 42 | } 43 | 44 | return cfg 45 | } 46 | -------------------------------------------------------------------------------- /declare.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | const clearQuota = "/cgi-bin/clear_quota" 4 | const getCallbackIP = "/cgi-bin/getcallbackip" 5 | const sandboxNew = "sandboxnew" 6 | const getSignKey = "pay/getsignkey" 7 | 8 | // APIMCHUS ... 9 | const APIMCHUS = "https://apius.mch.weixin.qq.com" 10 | 11 | // APIMCHHK ... 12 | const APIMCHHK = "https://apihk.mch.weixin.qq.com" 13 | 14 | // APIMCHDefault ... 15 | const APIMCHDefault = "https://api.mch.weixin.qq.com" 16 | 17 | const apiWeixin = "https://api.weixin.qq.com" 18 | const oauth2Authorize = "https://open.weixin.qq.com/connect/oauth2/authorize" 19 | const oauth2AccessToken = "https://api.weixin.qq.com/sns/oauth2/access_token" 20 | const snsUserinfo = "https://api.weixin.qq.com/sns/userinfo" 21 | 22 | const riskGetPublicKey = "https://fraud.mch.weixin.qq.com/risk/getpublickey" 23 | 24 | const mchSubMchManage = "/secapi/mch/submchmanage" 25 | const mchModifymchinfo = "/secapi/mch/modifymchinfo" 26 | const mktAddrecommendconf = "/secapi/mkt/addrecommendconf" 27 | const mchAddSubDevConfig = "/secapi/mch/addsubdevconfig" 28 | 29 | const mmpaymkttransfersSendRedPack = "/mmpaymkttransfers/sendredpack" 30 | const mmpaymkttransfersGetHbInfo = "/mmpaymkttransfers/gethbinfo" 31 | const mmpaymkttransfersSendGroupRedPack = "/mmpaymkttransfers/sendgroupredpack" 32 | const mmpaymkttransfersGetTransferInfo = "/mmpaymkttransfers/gettransferinfo" 33 | const mmpaymkttransfersPromotionTransfers = "/mmpaymkttransfers/promotion/transfers" 34 | 35 | const mmpaymkttransfersSendCoupon = "/mmpaymkttransfers/send_coupon" 36 | const mmpaymkttransfersQueryCouponStock = "/mmpaymkttransfers/query_coupon_stock" 37 | const mmpaymkttransfersQueryCouponsInfo = "/mmpaymkttransfers/querycouponsinfo" 38 | 39 | const mmpaysptransQueryBank = "/mmpaysptrans/query_bank" 40 | const mmpaysptransPayBank = "/mmpaysptrans/pay_bank" 41 | 42 | const sandbox = "/sandboxnew" 43 | const sandboxSignKey = sandbox + "/pay/getsignkey" 44 | 45 | // BizPayURL ... 46 | const bizPayURL = "weixin://wxpay/bizpayurl?" 47 | 48 | const authCodeToOpenid = "/tools/authcodetoopenid" 49 | const batchQueryComment = "/billcommentsp/batchquerycomment" 50 | const payDownloadBill = "/pay/downloadbill" 51 | const payDownloadFundFlow = "/pay/downloadfundflow" 52 | const paySettlementquery = "/pay/settlementquery" 53 | const payQueryexchagerate = "pay/queryexchagerate" 54 | const payUnifiedOrder = "/pay/unifiedorder" 55 | const payOrderQuery = "/pay/orderquery" 56 | const payMicroPay = "/pay/micropay" 57 | const payCloseOrder = "/pay/closeorder" 58 | const payRefundQuery = "/pay/refundquery" 59 | 60 | const payReverse = "/secapi/pay/reverse" 61 | const payRefund = "/secapi/pay/refund" 62 | 63 | //ticketGetTicket api address suffix 64 | const ticketGetTicket = "/cgi-bin/ticket/getticket" 65 | 66 | const wegoLocal = "http://localhost" 67 | const notifyCB = "notify_cb" 68 | const refundedCB = "refunded_cb" 69 | const scannedCB = "scanned_cb" 70 | const defaultKeepAlive = 30 71 | const defaultTimeout = 30 72 | 73 | /*accessTokenKey 键值 */ 74 | const accessTokenKey = "access_token" 75 | const accessToken = "/cgi-bin/token" 76 | 77 | const getKFList = "/cgi-bin/customservice/getkflist" 78 | 79 | const menuCreate = "/cgi-bin/menu/create" 80 | const menuGet = "/cgi-bin/menu/get" 81 | const menuDelete = "/cgi-bin/menu/delete" 82 | const menuAddConditional = "/cgi-bin/menu/addconditional" 83 | const menuDeleteConditional = "/cgi-bin/menu/delconditional" 84 | const menuTryMatch = "/cgi-bin/menu/trymatch" 85 | 86 | const templateAPISetIndustry = "/cgi-bin/template/api_set_industry" 87 | const templateGetIndustry = "/cgi-bin/template/get_industry" 88 | const templateAPIAddTemplate = "/cgi-bin/template/api_add_template" 89 | const templateGetAllPrivateTemplate = "/cgi-bin/template/get_all_private_template" 90 | const templateDelPrivateTemplate = "/cgi-bin/template/del_private_template" 91 | const messageTemplateSend = "/cgi-bin/message/template/send" 92 | 93 | const mediaUpload = "/cgi-bin/media/upload" 94 | const mediaUploadImg = "/cgi-bin/media/uploadimg" 95 | const mediaGet = "/cgi-bin/media/get" 96 | const mediaGetJssdk = "/cgi-bin/media/get/jssdk" 97 | 98 | const tagsCreate = "/cgi-bin/tags/create" 99 | const tagsGet = "/cgi-bin/tags/get" 100 | const tagsUpdate = "/cgi-bin/tags/update" 101 | const tagsDelete = "/cgi-bin/tags/delete" 102 | 103 | const tagsMembersBatchTagging = "/cgi-bin/tags/members/batchtagging" 104 | const tagsMembersBatchUntagging = "/cgi-bin/tags/members/batchuntagging" 105 | const tagsGetIDList = "/cgi-bin/tags/getidlist" 106 | const tagsMembersGetBlackList = "/cgi-bin/tags/members/getblacklist" 107 | const tagsMembersBatchBlackList = "/cgi-bin/tags/members/batchblacklist" 108 | const tagsMembersBatchUnblackList = "/cgi-bin/tags/members/batchunblacklist" 109 | 110 | const userTagGet = "/cgi-bin/user/tag/get" 111 | const userInfoUpdateRemark = "/cgi-bin/user/info/updateremark" 112 | const userInfo = "/cgi-bin/user/info" 113 | const userInfoBatchGet = "/cgi-bin/user/info/batchget" 114 | const userGet = "/cgi-bin/user/get" 115 | 116 | const qrcodeCreate = "/cgi-bin/qrcode/create" 117 | const showQrcode = "/cgi-bin/showqrcode" 118 | 119 | const messageMassSend = "/cgi-bin/message/mass/send" 120 | const messageMassSendall = "/cgi-bin/message/mass/sendall" 121 | const messageMassPreview = "cgi-bin/message/mass/preview" 122 | const messageMassDelete = "/cgi-bin/message/mass/delete" 123 | const messageMassGet = "/cgi-bin/message/mass/get" 124 | 125 | //DatacubeTimeLayout time format for datacube 126 | const DatacubeTimeLayout = "2006-01-02" 127 | 128 | // const tags_members_batchuntagging_URL_SUFFIX = "/cgi-bin/tags/members/batchuntagging" 129 | // const tags_members_batchtagging_URL_SUFFIX = "/cgi-bin/tags/members/batchtagging" 130 | // const tags_members_batchuntagging_URL_SUFFIX = "/cgi-bin/tags/members/batchuntagging" 131 | const dataCubeGetUserSummary = "/datacube/getusersummary" 132 | const dataCubeGetUserCumulate = "/datacube/getusercumulate" 133 | const dataCubeGetArticleSummary = "/datacube/getarticlesummary" 134 | const dataCubeGetArticleTotal = "/datacube/getarticletotal" 135 | const dataCubeGetUserRead = "/datacube/getuserread" 136 | const dataCubeGetUserReadHour = "/datacube/getuserreadhour" 137 | const dataCubeGetUserShare = "/datacube/getusershare" 138 | const dataCubeGetUserShareHour = "/datacube/getusersharehour" 139 | 140 | const dataCubeGetUpstreamMsg = "/datacube/getupstreammsg" 141 | const dataCubeGetUpstreamMsgHour = "/datacube/getupstreammsghour" 142 | const dataCubeGetUpstreamMsgWeek = "/datacube/getupstreammsgweek" 143 | const dataCubeGetUpstreamMsgDist = "/datacube/getupstreammsgdist" 144 | const dataCubeGetUpstreamMsgMonth = "/datacube/getupstreammsgmonth" 145 | const dataCubeGetUpstreamMsgDistWeek = "/datacube/getupstreammsgdistweek" 146 | const dataCubeGetUpstreamMsgDistMonth = "/datacube/getupstreammsgdistmonth" 147 | const dataCubeGetInterfaceSummary = "/datacube/getinterfacesummary" 148 | const dataCubeGetInterfaceSummaryHour = "/datacube/getinterfacesummaryhour" 149 | 150 | const materialAddNews = "/cgi-bin/material/add_news" 151 | const materialAddMaterial = "/cgi-bin/material/add_material" 152 | const materialGetMaterial = "/cgi-bin/material/get_material" 153 | const materialDelMaterial = "/cgi-bin/material/del_material" 154 | const materialUpdateNews = "/cgi-bin/material/update_news" 155 | const materialGetMaterialcount = "/cgi-bin/material/get_materialcount" 156 | const materialBatchgetMaterial = "/cgi-bin/material/batchget_material" 157 | const commentOpen = "/cgi-bin/comment/open" 158 | const commentClose = "/cgi-bin/comment/close" 159 | const commentList = "/cgi-bin/comment/list" 160 | const commentMarkelect = "/cgi-bin/comment/markelect" 161 | const commentUnmarkelect = "/cgi-bin/comment/unmarkelect" 162 | const commentDelete = "/cgi-bin/comment/delete" 163 | const commentReplyAdd = "/cgi-bin/comment/reply/add" 164 | const commentReplyDelete = "/cgi-bin/comment/reply/delete" 165 | 166 | //const oauth2AccessToken = "/sns/oauth2/access_token" 167 | const oauth2RefreshToken = "/sns/oauth2/refresh_token" 168 | const oauth2Userinfo = "/sns/userinfo" 169 | const oauth2Auth = "/sns/auth" 170 | const defaultOauthRedirect = "/oauth_redirect" 171 | const snsapiBase = "snsapi_base" 172 | const snsapiUserinfo = "snsapi_userinfo" 173 | const cardLandingPageCreate = "/card/landingpage/create" 174 | const cardCodeDeposit = "/card/code/deposit" 175 | const cardCodeGetDepositCount = "/card/code/getdepositcount" 176 | const cardQrcodeCreate = "/card/qrcode/create" 177 | const cardCodeCheckCode = "/card/code/checkcode" 178 | const cardCodeGet = "/card/code/get" 179 | const cardMPNewsGetHTML = "/card/mpnews/gethtml" 180 | const cardTestWhiteListSet = "/card/testwhitelist/set" 181 | const cardCreate = "/card/create" 182 | const cardGet = "/card/get" 183 | const cardGetApplyProtocol = "/card/getapplyprotocol" 184 | const cardGetColors = "/card/getcolors" 185 | const cardGetapplyprotocol = "/card/getapplyprotocol" 186 | const cardBatchget = "/card/batchget" 187 | const cardUpdate = "/card/update" 188 | const cardDelete = "/card/delete" 189 | const cardUserGetcardlist = "/card/user/getcardlist" 190 | const cardPaycellSet = "card/paycell/set" 191 | const cardModifystock = "card/modifystock" 192 | const cardBoardingpassCheckin = "/card/boardingpass/checkin" 193 | const poiAddPoi = "/cgi-bin/poi/addpoi" 194 | const poiGetPoi = "/cgi-bin/poi/getpoi" 195 | const poiUpdatePoi = "/cgi-bin/poi/updatepoi" 196 | const poiGetListPoi = "/cgi-bin/poi/getpoilist" 197 | const poiDelPoi = "/cgi-bin/poi/delpoi" 198 | const poiGetWXCategory = "/cgi-bin/poi/getwxcategory" 199 | const getCurrentAutoReplyInfo = "/cgi-bin/get_current_autoreply_info" 200 | const getCurrentSelfMenuInfo = "/cgi-bin/get_current_selfmenu_info" 201 | 202 | // POST ... 203 | const POST = "POST" 204 | 205 | // GET ... 206 | const GET = "GET" 207 | -------------------------------------------------------------------------------- /declare_card.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import "github.com/godcong/wego/util" 4 | 5 | // CardScene ... 6 | type CardScene string 7 | 8 | // CardSceneNearBy ... 9 | const ( 10 | CardSceneNearBy CardScene = "SCENE_NEAR_BY" //CardSceneNearBy 附近 11 | CardSceneMenu CardScene = "SCENE_MENU" //CardSceneMenu 自定义菜单 12 | CardSceneQrcode CardScene = "SCENE_QRCODE" //CardSceneQrcode 二维码 13 | CardSceneArticle CardScene = "SCENE_ARTICLE" //CardSceneArticle 公众号文章 14 | CardSceneH5 CardScene = "SCENE_H5" //CardSceneH5 H5页面 15 | CardSceneIvr CardScene = "SCENE_IVR" //CardSceneIvr 自动回复 16 | CardSceneCardCustomCell CardScene = "SCENE_CARD_CUSTOM_CELL" //CardSceneCardCustomCell 卡券自定义cell 17 | ) 18 | 19 | //CardStatus 支持开发者拉出指定状态的卡券列表 20 | type CardStatus string 21 | 22 | // CARD_STATUS_NOT_VERIFY ... 23 | const ( 24 | CardStatusNotVerify CardStatus = "CARD_STATUS_NOT_VERIFY" //待审核 25 | CardStatusVerifyFail CardStatus = "CARD_STATUS_VERIFY_FAIL" //审核失败 26 | CardStatusVerifyOk CardStatus = "CARD_STATUS_VERIFY_OK" //通过审核 27 | CardStatusDelete CardStatus = "CARD_STATUS_DELETE" //卡券被商户删除 28 | CardStatusDispatch CardStatus = "CARD_STATUS_DISPATCH" //在公众平台投放过的卡券; 29 | ) 30 | 31 | // CardList ... 32 | type CardList struct { 33 | CardID string `json:"card_id"` // card_id 所要在页面投放的card_id 是 34 | ThumbURL string `json:"thumb_url"` // thumb_url 缩略图url 是 35 | } 36 | 37 | // CardLandingPage ... 38 | type CardLandingPage struct { 39 | Banner string `json:"banner"` //页面的banner图片链接,须调用,建议尺寸为640*300。 是 40 | Title string `json:"page_title"` //页面的title。 是 41 | CanShare bool `json:"can_share"` //页面是否可以分享,填入true/false 是 42 | Scene CardScene `json:"scene"` // 投放页面的场景值; SCENE_NEAR_BY 附近 SCENE_MENU 自定义菜单 SCENE_QRCODE 二维码 SCENE_ARTICLE 公众号文章 SCENE_H5 h5页面 SCENE_IVR 自动回复 SCENE_CARD_CUSTOM_CELL 卡券自定义cell 是 43 | CardList []CardList `json:"card_list"` // card_list 卡券列表,每个item有两个字段 是 44 | } 45 | 46 | // CardType ... 47 | type CardType string 48 | 49 | // String ... 50 | func (t CardType) String() string { 51 | return string(t) 52 | } 53 | 54 | // CardTypeGroupon ... 55 | const ( 56 | CardTypeGroupon CardType = "GROUPON" //CardTypeGroupon GROUPON 团购券类型。 57 | CardTypeCash CardType = "CASH" //CardTypeCash CASH 代金券类型。 58 | CardTypeDiscount CardType = "DISCOUNT" //CardTypeDiscount DISCOUNT 折扣券类型。 59 | CardTypeGift CardType = "GIFT" //CardTypeGift GIFT 兑换券类型。 60 | CardTypeGeneralCoupon CardType = "GENERAL_COUPON" //CardTypeGeneralCoupon GENERAL_COUPON 优惠券类型。 61 | ) 62 | 63 | // CardDataInfo ... 64 | type CardDataInfo struct { 65 | Type string `json:"type"` // type 是 string DATE_TYPE_FIX _TIME_RANGE 表示固定日期区间,DATETYPE FIX_TERM 表示固定时长 (自领取后按天算。 使用时间的类型,旧文档采用的1和2依然生效。 66 | BeginTimestamp int64 `json:"begin_timestamp"` // begin_time stamp 是 unsigned int 14300000 type为DATE_TYPE_FIX_TIME_RANGE时专用,表示起用时间。从1970年1月1日00:00:00至起用时间的秒数,最终需转换为字符串形态传入。(东八区时间,UTC+8,单位为秒) 67 | EndTimestamp int64 `json:"end_timestamp"` // end_time stamp 是 unsigned int 15300000 表示结束时间 , 建议设置为截止日期的23:59:59过期 。 ( 东八区时间,UTC+8,单位为秒 ) 68 | FixedTerm int `json:"fixed_term"` // fixed_term 是 int 15 type为DATE_TYPE_FIX_TERM时专用,表示自领取后多少天内有效,不支持填写0。 69 | FixedBeginTerm int `json:"fixed_begin_term"` // fixed_begin_term 是 int 0 type为DATE_TYPE_FIX_TERM时专用,表示自领取后多少天开始生效,领取后当天生效填写0。(单位为天) 70 | } 71 | 72 | // CardSku ... 73 | type CardSku struct { 74 | Quantity int `json:"quantity"` // quantity 是 int 100000 卡券库存的数量,上限为100000000。 75 | } 76 | 77 | // CardCodeType ... 78 | type CardCodeType string 79 | 80 | // String ... 81 | func (t CardCodeType) String() string { 82 | return string(t) 83 | } 84 | 85 | // CardCodeTypeText ... 86 | const ( 87 | CardCodeTypeText CardCodeType = "CODE_TYPE_TEXT" //CardCodeTypeText 文 本 88 | CardCodeTypeBarcode CardCodeType = "CODE_TYPE_BARCODE" //CardCodeTypeBarcode 一维码 89 | CardCodeTypeQrcode CardCodeType = "CODE_TYPE_QRCODE" //CardCodeTypeQrcode 二维码 90 | CardCodeTypeOnlyQrcode CardCodeType = "CODE_TYPE_ONLY_QRCODE" //CardCodeTypeOnlyQrcode 二维码无code显示 91 | CardCodeTypeOnlyBarcode CardCodeType = "CODE_TYPE_ONLY_BARCODE" //CardCodeTypeOnlyBarcode 一维码无code显示 92 | CardCodeTypeNone CardCodeType = "CODE_TYPE_NONE" //CardCodeTypeNone 不显示code和条形码类型 93 | ) 94 | 95 | // CardBaseInfo ... 96 | type CardBaseInfo struct { 97 | LogoURL string `json:"logo_url"` // logo_url 是 strin g(128) http://mmbiz.qpic.cn/ 卡券的商户logo,建议像素为300*300。 98 | BrandName string `json:"brand_name"` // brand_name 是 string(36) 海底捞 商户名字,字数上限为12个汉字。 99 | CodeType CardCodeType `json:"code_type"` // code_type 是 string(16) CODE_TYPE_TEXT 码型: "CODE_TYPE_TEXT"文 本 ; "CODE_TYPE_BARCODE"一维码 "CODE_TYPE_QRCODE"二维码 "CODE_TYPE_ONLY_QRCODE",二维码无code显示; "CODE_TYPE_ONLY_BARCODE",一维码无code显示;CODE_TYPE_NONE, 不显示code和条形码类型 100 | Title string `json:"title"` // title 是 string(27) 双人套餐100元兑换券 卡券名,字数上限为9个汉字。(建议涵盖卡券属性、服务及金额)。 101 | Color string `json:"color"` // color 是 string(16) Color010 券颜色。按色彩规范标注填写Color010-Color100。 102 | Notice string `json:"notice"` // notice 是 string(48) 请出示二维码 卡券使用提醒,字数上限为16个汉字。 103 | ServicePhone string `json:"service_phone,omitempty"` // service_phone 否 string(24) 40012234 客服电话。 104 | Description string `json:"description"` // description 是 strin g (3072) 不可与其他优惠同享 卡券使用说明,字数上限为1024个汉字。 105 | DateInfo CardDataInfo `json:"date_info"` // date_info 是 JSON结构 见上述示例。 使用日期,有效期的信息。 106 | Sku CardSku `json:"sku"` // sku 是 JSON结构 见上述示例。 商品信息。 107 | UseLimit int `json:"use_limit,omitempty"` // use_limit否int100每人可核销的数量限制,不填写默认为50。 108 | GetLimit int `json:"get_limit,omitempty"` // get_limit 否 int 1 每人可领券的数量限制,不填写默认为50。 109 | UseCustomCode bool `json:"use_custom_code,omitempty"` // use_custom_code 否 bool true 是否自定义Code码 。填写true或false,默认为false。 通常自有优惠码系统的开发者选择 自定义Code码,并在卡券投放时带入 Code码,详情见 是否自定义Code码 。 110 | GetCustomCodeMode string `json:"get_custom_code_mode,omitempty"` // get_custom_code_mode 否 string(32) GET_CUSTOM_COD E_MODE_DEPOSIT 填入 GET_CUSTOM_CODE_MODE_DEPOSIT 表示该卡券为预存code模式卡券, 须导入超过库存数目的自定义code后方可投放, 填入该字段后,quantity字段须为0,须导入code 后再增加库存 111 | BindOpenid bool `json:"bind_openid"` // bind_openid 否 bool true 是否指定用户领取,填写true或false 。默认为false。通常指定特殊用户群体 投放卡券或防止刷券时选择指定用户领取。 112 | CanShare bool `json:"can_share,omitempty"` // can_share 否 bool false 卡券领取页面是否可分享。 113 | CanGiveFriend bool `json:"can_give_friend,omitempty"` // can_give_friend否boolfalse卡券是否可转赠。 114 | LocationIDList []int `json:"location_id_list,omitempty"` // location_id_list 否 array 1234,2312 门店位置poiid。 调用 POI门店管理接 口 获取门店位置poiid。具备线下门店 的商户为必填。 115 | UseAllLocations bool `json:"use_all_locations,omitempty"` // use_all_locations 否 bool true 设置本卡券支持全部门店,与location_id_list互斥 116 | CenterTitle string `json:"center_title,omitempty"` // center_title 否 string(18) 立即使用 卡券顶部居中的按钮,仅在卡券状 态正常(可以核销)时显示 117 | CenterSubTitle string `json:"center_sub_title,omitempty"` // center_sub_title 否 string(24) 立即享受优惠 显示在入口下方的提示语 ,仅在卡券状态正常(可以核销)时显示。 118 | CenterURL string `json:"center_url,omitempty"` // center_url 否 string(128) www.qq.com 顶部居中的url ,仅在卡券状态正常(可以核销)时显示。 119 | CenterAppBrandUserName string `json:"center_app_brand_user_name,omitempty"` // center_app_brand_user_name 否 string(128) gh_86a091e50ad4@app 卡券跳转的小程序的user_name,仅可跳转该 公众号绑定的小程序 。 120 | CenterAppBrandPass string `json:"center_app_brand_pass,omitempty"` // center_app_brand_pass 否 string(128) API/cardPage 卡券跳转的小程序的path 121 | CustomURLName string `json:"custom_url_name,omitempty"` // custom_url_name 否 string(15) 立即使用 自定义跳转外链的入口名字。 122 | CustomURL string `json:"custom_url,omitempty"` // custom_url 否 string(128) www.qq.com 自定义跳转的URL。 123 | CustomURLSubTitle string `json:"custom_url_sub_title,omitempty"` // custom_url_sub_title 否 string(18) 更多惊喜 显示在入口右侧的提示语。 124 | CustomAppBrandUserName string `json:"custom_app_brand_user_name,omitempty"` // custom_app_brand_user_name 否 string(128) gh_86a091e50ad4@app 卡券跳转的小程序的user_name,仅可跳转该 公众号绑定的小程序 。 125 | CustomAppBrandPass string `json:"custom_app_brand_pass,omitempty"` // custom _app_brand_pass否string(128)API/cardPage卡券跳转的小程序的path 126 | PromotionURLName string `json:"promotion_url_name,omitempty"` // promotion_url_name 否 string(15) 产品介绍 营销场景的自定义入口名称。 127 | PromotionURL string `json:"promotion_url,omitempty"` // promotion_url 否 string(128) www.qq.com 入口跳转外链的地址链接。 128 | PromotionURLSubTitle string `json:"promotion_url_sub_title,omitempty"` // promotion_url_sub_title 否 string(18) 卖场大优惠。 显示在营销入口右侧的提示语。 129 | PromotionAppBrandUserName string `json:"promotion_app_brand_user_name,omitempty"` // promotion_app_brand_user_name 否 string(128) gh_86a091e50ad4@app 卡券跳转的小程序的user_name,仅可跳转该 公众号绑定的小程序 。 130 | PromotionAppBrandPass string `json:"promotion_app_brand_pass,omitempty"` // promotion_app_brand_pass 否 string(128) API/cardPage 卡券跳转的小程序的path 131 | Source string `json:"source"` // "source": "大众点评" 132 | } 133 | 134 | // CardUseCondition ... 135 | type CardUseCondition struct { 136 | AcceptCategory string `json:"accept_category,omitempty"` // accept_category 否 string(512) 指定可用的商品类目,仅用于代金券类型 ,填入后将在券面拼写适用于xxx 137 | RejectCategory string `json:"reject_category,omitempty"` // reject_category 否 string( 512 ) 指定不可用的商品类目,仅用于代金券类型 ,填入后将在券面拼写不适用于xxxx 138 | LeastCost int `json:"least_cost,omitempty"` //least_cost 否 int 满减门槛字段,可用于兑换券和代金券 ,填入后将在全面拼写消费满xx元可用。 139 | ObjectUseFor string `json:"object_use_for,omitempty"` //object_use_for 否 string( 512 ) 购买xx可用类型门槛,仅用于兑换 ,填入后自动拼写购买xxx可用。 140 | CanUseWithOtherDiscount bool `json:"can_use_with_other_discount,omitempty"` // can_use_with_other_discount 否 bool 不可以与其他类型共享门槛 ,填写false时系统将在使用须知里 拼写“不可与其他优惠共享”, 填写true时系统将在使用须知里 拼写“可与其他优惠共享”, 默认为true 141 | } 142 | 143 | // CardAbstract ... 144 | type CardAbstract struct { 145 | Abstract string `json:"abstract,omitempty"` // abstract 否 string(24 ) 封面摘要简介。 146 | IconURLList []string `json:"icon_url_list,omitempty"` // icon_url_list 否 string(128 ) 封面图片列表,仅支持填入一 个封面图片链接, 上传图片接口 上传获取图片获得链接,填写 非CDN链接会报错,并在此填入。 建议图片尺寸像素850*350 147 | } 148 | 149 | // CardTextImageList ... 150 | type CardTextImageList struct { 151 | ImageURL string `json:"image_url,omitempty"` // image_url 否 string(128 ) 图片链接,必须调用 上传图片接口 上传图片获得链接,并在此填入, 否则报错 152 | Text string `json:"text,omitempty"` // text 否 string(512 ) 图文描述 153 | } 154 | 155 | // CardTimeLimit ... 156 | type CardTimeLimit struct { 157 | Type string `json:"type,omitempty"` // type 否 string(24 ) 限制类型枚举值:支持填入 MONDAY 周一 TUESDAY 周二 WEDNESDAY 周三 THURSDAY 周四 FRIDAY 周五 SATURDAY 周六 SUNDAY 周日 此处只控制显示, 不控制实际使用逻辑,不填默认不显示 158 | BeginHour int `json:"begin_hour,omitempty"` // begin_hour 否 int 当前type类型下的起始时间(小时) ,如当前结构体内填写了MONDAY, 此处填写了10,则此处表示周一 10:00可用 159 | EndHour int `json:"end_hour,omitempty"` // end_hour 否 int 当前type类型下的结束时间(小时) ,如当前结构体内填写了MONDAY, 此处填写了20, 则此处表示周一 10:00-20:00可用 160 | BeginMinute int `json:"begin_minute,omitempty"` // begin_minute 否 int 当前type类型下的起始时间(分钟) ,如当前结构体内填写了MONDAY, begin_hour填写10,此处填写了59, 则此处表示周一 10:59可用 161 | EndMinute int `json:"end_minute,omitempty"` // end_minute 否 int 当前type类型下的结束时间(分钟) ,如当前结构体内填写了MONDAY, begin_hour填写10,此处填写了59, 则此处表示周一 10:59-00:59可用 162 | } 163 | 164 | // CardAdvancedInfo ... 165 | type CardAdvancedInfo struct { 166 | UseCondition *CardUseCondition `json:"use_condition,omitempty"` // use_condition 否 JSON结构 使用门槛(条件)字段,若不填写使用条件则在券面拼写 :无最低消费限制,全场通用,不限品类;并在使用说明显示: 可与其他优惠共享 167 | Abstract *CardAbstract `json:"abstract,omitempty"` // abstract 否 JSON结构 封面摘要结构体名称 168 | TextImageList []CardTextImageList `json:"text_image_list,omitempty"` // text_image_list 否 JSON结构 图文列表,显示在详情内页 ,优惠券券开发者须至少传入 一组图文列表 169 | TimeLimit []CardTimeLimit `json:"time_limit,omitempty"` // time_limit否JSON结构使用时段限制,包含以下字段 170 | BusinessService []string `json:"business_service,omitempty"` // business_service 否 array 商家服务类型: BIZ_SERVICE_DELIVER 外卖服务; BIZ_SERVICE_FREE_PARK 停车位; BIZ_SERVICE_WITH_PET 可带宠物; BIZ_SERVICE_FREE_WIFI 免费wifi, 可多选 171 | } 172 | 173 | // OneCard ... 174 | type OneCard struct { 175 | CardType CardType `json:"card_type"` 176 | data util.Map 177 | } 178 | -------------------------------------------------------------------------------- /declare_message.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/godcong/wego/util" 6 | "strings" 7 | ) 8 | 9 | /*Messager Messager */ 10 | type Messager interface { 11 | ToXML() ([]byte, error) 12 | ToJSON() ([]byte, error) 13 | } 14 | 15 | /*CDATA CDATA */ 16 | type CDATA = util.CDATA 17 | 18 | /*MsgType MsgType */ 19 | type MsgType string 20 | 21 | /*message types */ 22 | const ( 23 | MsgTypeText MsgType = "text" //表示文本消息 `` 24 | MsgTypeImage MsgType = "image" //表示图片消息 25 | MsgTypeVoice MsgType = "voice" //表示语音消息 26 | MsgTypeVideo MsgType = "video" //表示视频消息 27 | MsgTypeShortvideo MsgType = "shortvideo" //表示短视频消息[限接收] 28 | MsgTypeLocation MsgType = "location" //表示坐标消息[限接收] 29 | MsgTypeLink MsgType = "link" //表示链接消息[限接收] 30 | MsgTypeMusic MsgType = "music" //表示音乐消息[限回复] 31 | MsgTypeNews MsgType = "news" //表示图文消息[限回复] 32 | MsgTypeTransfer MsgType = "transfer_customer_service" //表示消息消息转发到客服 33 | MsgTypeEvent MsgType = "event" //表示事件推送消息 34 | MsgTypeMiniprogrampage MsgType = "miniprogrampage" 35 | ) 36 | 37 | /*MSGCDATA MSGCDATA */ 38 | type MSGCDATA struct { 39 | MsgType `xml:",cdata"` 40 | } 41 | 42 | /*Message Message */ 43 | type Message struct { 44 | XMLName xml.Name `xml:"xml"` 45 | MsgType MSGCDATA `xml:"MsgType"` 46 | MsgID int64 `xml:"MsgID,omitempty"` 47 | ToUserName CDATA `xml:"to_user_name"` 48 | FromUserName CDATA `xml:"from_user_name"` 49 | CreateTime int64 `xml:"create_time"` 50 | } 51 | 52 | /*String String */ 53 | func (e MsgType) String() string { 54 | return string(e) 55 | } 56 | 57 | /*Compare compare message type*/ 58 | func (e MsgType) Compare(msgType MsgType) int { 59 | return strings.Compare(strings.ToLower(e.String()), msgType.String()) 60 | } 61 | 62 | /*Compare compare message type*/ 63 | func (e *Message) Compare(msgType MsgType) int { 64 | return e.MsgType.Compare(msgType) 65 | } 66 | 67 | /*NewMessage create a new message */ 68 | func NewMessage(msgType MsgType, toUser, fromUser string, msgID, createTime int64) *Message { 69 | return &Message{ 70 | MsgType: MSGCDATA{ 71 | MsgType: msgType, 72 | }, 73 | MsgID: msgID, 74 | ToUserName: CDATA{ 75 | Value: toUser, 76 | }, 77 | FromUserName: CDATA{ 78 | Value: fromUser, 79 | }, 80 | CreateTime: createTime, 81 | } 82 | } 83 | 84 | /*EventType EventType */ 85 | type EventType string 86 | 87 | /*event types */ 88 | const ( 89 | EventTypeSubscribe EventType = "subscribe" // 订阅 90 | EventTypeUnsubscribe EventType = "unsubscribe" // 取消订阅 91 | EventTypeScan EventType = "scan" // 用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者 92 | EventTypeLocation EventType = "location" // 上报地理位置事件 93 | EventTypeClick EventType = "CLICK" // 点击菜单拉取消息时的事件推送 94 | EventTypeView EventType = "view" // 点击菜单跳转链接时的事件推送 95 | EventTypeScancodePush EventType = "scancode_push" // 扫码推事件的事件推送 96 | EventTypeScancodeWaitmsg EventType = "scancode_waitmsg" // 扫码推事件且弹出“消息接收中”提示框的事件推送 97 | EventTypePicSysphoto EventType = "pic_sysphoto" // 弹出系统拍照发图的事件推送 98 | EventTypePicPhotoOrAlbum EventType = "pic_photo_or_album" // 弹出拍照或者相册发图的事件推送 99 | EventTypePicWeixin EventType = "pic_weixin" // 弹出微信相册发图器的事件推送 100 | EventTypeLocationSelect EventType = "location_select" // 弹出地理位置选择器的事件推送 101 | EventTypeTemplateSendJobFinish EventType = "TEMPLATESENDJOBFINISH" // 发送模板消息推送通知 102 | EventTypeUserEnterTempsession EventType = "user_enter_tempsession" // 会话事件 103 | EventTypeQualificationVerifySuccess EventType = "qualification_verify_success" // 资质认证成功(此时立即获得接口权限) 104 | EventTypeQualificationVerifyFail EventType = "qualification_verify_fail" // 资质认证失败 105 | EventTypeNamingVerifySuccess EventType = "naming_verify_success" // 名称认证成功(即命名成功) 106 | EventTypeNamingVerifyFail EventType = "naming_verify_fail" // 名称认证失败(这时虽然客户端不打勾,但仍有接口权限) 107 | EventTypeAnnualRenew EventType = "annual_renew" // 年审通知 108 | EventTypeVerifyExpired EventType = "verify_expired" // 认证过期失效通知审通知 109 | EventTypePoiCheckNotify EventType = "poi_check_notify" // 审核事件推送 110 | EventTypeMerchantOrder EventType = "merchant_order" //订单付款通知 111 | ) 112 | 113 | /*EVTCDATA EVTCDATA */ 114 | type EVTCDATA struct { 115 | Value EventType `xml:",cdata"` 116 | } 117 | 118 | /*Event Event */ 119 | type Event struct { 120 | Event EVTCDATA 121 | } 122 | 123 | /*String String */ 124 | func (e EventType) String() string { 125 | return string(e) 126 | } 127 | 128 | /*Compare compare event type */ 129 | func (e EventType) Compare(evtType EventType) int { 130 | return strings.Compare(strings.ToLower(e.String()), evtType.String()) 131 | } 132 | 133 | /*Compare compare event type */ 134 | func (e *Event) Compare(evtType EventType) int { 135 | return e.Event.Value.Compare(evtType) 136 | } 137 | -------------------------------------------------------------------------------- /declare_poi.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | // PhotoList ... 4 | type PhotoList []struct { 5 | PhotoURL string `json:"photo_url"` 6 | } 7 | 8 | /*PoiBaseInfo PoiBaseInfo*/ 9 | type PoiBaseInfo struct { 10 | Poi string `json:"poi,omitempty"` // "poi_id ":"271864249" 11 | Sid string `json:"sid,omitempty"` // "sid":"33788392", 12 | BusinessName string `json:"business_name"` //"business_name":"15个汉字或30个英文字符内", 13 | BranchName string `json:"branch_name"` //"branch_name":"不超过10个字,不能含有括号和特殊字符", 14 | Province string `json:"province"` //"province":"不超过10个字", 15 | City string `json:"city"` //"city":"不超过30个字", 16 | District string `json:"district"` //"district":"不超过10个字", 17 | Address string `json:"address"` //"address":"门店所在的详细街道地址(不要填写省市信息):不超过80个字", 18 | Telephone string `json:"telephone"` //"telephone":"不超53个字符(不可以出现文字)", 19 | Categories []string `json:"categories"` //"categories":["美食,小吃快餐"], 20 | OffsetType int `json:"offset_type"` //"offset_type":1, 21 | Longitude float64 `json:"longitude"` //"longitude":115.32375, 22 | Latitude float64 `json:"latitude"` //"latitude":25.097486, 23 | PhotoList PhotoList `json:"photo_list,omitempty"` //"photo_list":[{"photo_url":"https:// 不超过20张.com"},{"photo_url":"https://XXX.com"}], 24 | Recommend string `json:"recommend,omitempty"` //"recommend":"不超过200字。麦辣鸡腿堡套餐,麦乐鸡,全家桶", 25 | Special string `json:"special,omitempty"` //"special":"不超过200字。免费wifi,外卖服务", 26 | Introduction string `json:"introduction,omitempty"` //"introduction":"不超过300字。麦当劳是全球大型跨国连锁餐厅,1940 年创立于美国,在世界上大约拥有3 万间分店。 主要售卖汉堡包,以及薯条、炸鸡、汽水、冰品、沙拉、 水果等快餐食品", 27 | OpenTime string `json:"open_time,omitempty"` //"open_time":"8:00-20:00", 28 | AvgPrice int `json:"avg_price,omitempty"` //"avg_price":35 29 | } 30 | -------------------------------------------------------------------------------- /declare_qrcode.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | /*QrCodeScene QrCodeScene*/ 4 | type QrCodeScene struct { 5 | SceneID int `json:"scene_id,omitempty"` 6 | SceneStr string `json:"scene_str,omitempty"` 7 | } 8 | 9 | /*QrCodeCard QrCodeCard*/ 10 | type QrCodeCard struct { 11 | CardID string `json:"card_id,omitempty"` // "card_id": "pFS7Fjg8kV1IdDz01r4SQwMkuCKc", 12 | Code string `json:"code"` // "code": "198374613512", 13 | OpenID string `json:"openid,omitempty"` // "openid": "oFS7Fjl0WsZ9AMZqrI80nbIq8xrA", 14 | IsUniqueCode bool `json:"is_unique_code,omitempty"` // "is_unique_code": false, 15 | OuterStr string `json:"outer_str,omitempty"` // "outer_str":"12b" 16 | } 17 | 18 | /*QrCodeCardList QrCodeCardList*/ 19 | type QrCodeCardList struct { 20 | CardID string `json:"card_id,omitempty"` // "card_id": "p1Pj9jgj3BcomSgtuW8B1wl-wo88", 21 | Code string `json:"code"` // "code": "198374613512", 22 | OuterStr string `json:"outer_str,omitempty"` // "outer_str":"12b" 23 | } 24 | 25 | /*QrCodeMultipleCard QrCodeMultipleCard*/ 26 | type QrCodeMultipleCard struct { 27 | CardList []QrCodeCardList `json:"card_list,omitempty"` 28 | } 29 | 30 | /*QrCodeActionInfo QrCodeActionInfo*/ 31 | type QrCodeActionInfo struct { 32 | Scene *QrCodeScene `json:"scene,omitempty"` 33 | Card *QrCodeCard `json:"card,omitempty"` 34 | MultipleCard *QrCodeMultipleCard `json:"multiple_card,omitempty"` 35 | } 36 | 37 | /*QrCodeAction QrCodeAction*/ 38 | type QrCodeAction struct { 39 | ExpireSeconds int `json:"expire_seconds,omitempty"` 40 | ActionName QrCodeActionName `json:"action_name"` 41 | ActionInfo QrCodeActionInfo `json:"action_info"` 42 | } 43 | 44 | /*QrCodeActionName QrCodeActionName*/ 45 | type QrCodeActionName string 46 | 47 | // QrMultipleCard ... 48 | const ( 49 | QrMultipleCard QrCodeActionName = "QR_MULTIPLE_CARD" //QrMultipleCard QrMultipleCard 50 | QrCard QrCodeActionName = "QR_CARD" //QrCard QrCard 51 | QrScene QrCodeActionName = "QR_SCENE" //QrScene QrScene 52 | QrLimitStrScene QrCodeActionName = "QR_LIMIT_STR_SCENE" //QrLimitStrScene QrLimitStrScene 53 | ) 54 | -------------------------------------------------------------------------------- /declare_template.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "github.com/godcong/wego/util" 5 | ) 6 | 7 | /*ValueColor ValueColor */ 8 | type ValueColor struct { 9 | Value string `json:"value"` 10 | Color string `json:"color,omitempty"` 11 | } 12 | 13 | /*TemplateData TemplateData */ 14 | type TemplateData map[string]*ValueColor 15 | 16 | /*TemplateMiniProgram TemplateMiniProgram */ 17 | type TemplateMiniProgram struct { 18 | AppID string `json:"appid"` //"appid":"xiaochengxuappid12345", 19 | PagePath string `json:"pagepath"` //"pagepath":"index?foo=bar" 20 | } 21 | 22 | /*Template Template */ 23 | type Template struct { 24 | ToUser string `json:"touser"` //"touser":"OPENID", 25 | TemplateID string `json:"template_id"` // "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY", 26 | URL string `json:"url,omitempty"` //"url":"http://weixin.qq.com/download", 27 | Data TemplateData `json:"data"` //"data":{...} 28 | MiniProgram TemplateMiniProgram `json:"miniprogram,omitempty"` //"miniprogram": 29 | Page string `json:"page,omitempty"` //点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。 30 | FormID string `json:"form_id,omitempty"` //小程序为必填 表单提交场景下,为 submit 事件带上的 formId;支付场景下,为本次支付的 prepay_id 31 | EmphasisKeyword string `json:"emphasis_keyword,omitempty"` //模板需要放大的关键词,不填则默认无放大 32 | } 33 | 34 | // ToMap ... 35 | func (t Template) ToMap() util.Map { 36 | p := util.Map{} 37 | e := util.StructToMap(t, p) 38 | if e != nil { 39 | return nil 40 | } 41 | return p 42 | } 43 | -------------------------------------------------------------------------------- /declare_userinfo.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | /*UserInfo UserInfo */ 4 | type UserInfo struct { 5 | City string `json:"city"` 6 | Country string `json:"country"` 7 | HeadImgURL string `json:"headimgurl"` 8 | Language string `json:"language"` 9 | Nickname string `json:"nickname"` 10 | Openid string `json:"openid"` 11 | Privilege []string `json:"privilege"` 12 | Province string `json:"province"` 13 | Sex uint `json:"sex"` 14 | Subscribe int `json:"subscribe"` 15 | SubscribeTime uint32 `json:"subscribe_time"` 16 | UnionID string `json:"unionid"` 17 | Remark string `json:"remark"` 18 | GroupID int `json:"groupid"` 19 | TagIDList []int `json:"tagid_list"` 20 | SubscribeScene string `json:"subscribe_scene"` 21 | QrScene int `json:"qr_scene"` 22 | QrSceneStr string `json:"qr_scene_str"` 23 | } 24 | 25 | /*UserID UserID */ 26 | type UserID struct { 27 | OpenID string `json:"openid"` 28 | Lang string `json:"lang,omitempty"` 29 | } 30 | 31 | // UserInfoList ... 32 | type UserInfoList struct { 33 | UserInfoList []*UserInfo `json:"user_info_list"` 34 | } 35 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/godcong/wego 2 | 3 | require ( 4 | github.com/BurntSushi/toml v0.3.1 // indirect 5 | github.com/go-redis/redis v6.14.2+incompatible 6 | github.com/godcong/go-trait v0.0.0-20190517051917-1198fdb7648c 7 | github.com/json-iterator/go v1.1.5 8 | github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 9 | github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect 10 | github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073 // indirect 11 | github.com/kr/pretty v0.1.0 // indirect 12 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 13 | github.com/modern-go/reflect2 v1.0.1 // indirect 14 | github.com/onsi/ginkgo v1.7.0 // indirect 15 | github.com/onsi/gomega v1.4.3 // indirect 16 | github.com/pelletier/go-toml v1.2.0 17 | github.com/satori/go.uuid v1.2.0 18 | github.com/stretchr/testify v1.3.0 // indirect; indirectgo 19 | go.uber.org/zap v1.10.0 20 | golang.org/x/text v0.3.0 21 | golang.org/x/xerrors v0.0.0-20190212162355-a5947ffaace3 22 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect 23 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect 24 | gopkg.in/yaml.v2 v2.2.2 // indirect 25 | ) 26 | -------------------------------------------------------------------------------- /jssdk.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "crypto/md5" 5 | "fmt" 6 | "github.com/godcong/wego/cache" 7 | "github.com/godcong/wego/util" 8 | "strings" 9 | ) 10 | 11 | /*JSSDK JSSDK */ 12 | type JSSDK struct { 13 | *JSSDKProperty 14 | accessToken *AccessToken 15 | ticket *Ticket 16 | subAppID string 17 | url string 18 | //CacheKey func() string 19 | } 20 | 21 | /*NewJSSDK NewJSSDK */ 22 | func NewJSSDK(property *JSSDKProperty, options ...JSSDKOption) *JSSDK { 23 | jssdk := &JSSDK{ 24 | JSSDKProperty: property, 25 | } 26 | jssdk.parse(options...) 27 | return jssdk 28 | } 29 | 30 | func (obj *JSSDK) getURL() string { 31 | if obj.url != "" { 32 | return obj.url 33 | } 34 | return "http://localhost" 35 | } 36 | 37 | // BridgeConfig ... 38 | type BridgeConfig struct { 39 | AppID string `json:"appId"` 40 | NonceStr string `json:"nonceStr"` 41 | Package string `json:"package"` 42 | PaySign string `json:"paySign"` 43 | SignType string `json:"signType"` 44 | TimeStamp string `json:"timeStamp"` 45 | } 46 | 47 | /*BuildBridgeConfig bridge 设置 */ 48 | func BuildBridgeConfig(id, key, pid string) *BridgeConfig { 49 | config := &BridgeConfig{ 50 | AppID: id, 51 | NonceStr: util.GenerateNonceStr(), 52 | Package: strings.Join([]string{"prepay_id", pid}, "="), 53 | SignType: "MD5", 54 | TimeStamp: util.Time(), 55 | } 56 | config.PaySign = util.GenSign(util.Map{ 57 | "appId": config.AppID, 58 | "timeStamp": config.TimeStamp, 59 | "nonceStr": config.NonceStr, 60 | "package": config.Package, 61 | "signType": config.SignType, 62 | }, key) 63 | return config 64 | } 65 | 66 | // SDKConfig ... 67 | type SDKConfig struct { 68 | AppID string `json:"appId"` 69 | NonceStr string `json:"nonceStr"` 70 | Package string `json:"package"` 71 | PaySign string `json:"paySign"` 72 | SignType string `json:"signType"` 73 | TimeStamp string `json:"timestamp"` 74 | } 75 | 76 | /*BuildSDKConfig sdk 设置 */ 77 | func BuildSDKConfig(id, key, pid string) *SDKConfig { 78 | config := &SDKConfig{ 79 | AppID: id, 80 | NonceStr: util.GenerateNonceStr(), 81 | Package: strings.Join([]string{"prepay_id", pid}, "="), 82 | SignType: "MD5", 83 | TimeStamp: util.Time(), 84 | } 85 | config.PaySign = util.GenSign(util.Map{ 86 | "appId": config.AppID, 87 | "timeStamp": config.TimeStamp, 88 | "nonceStr": config.NonceStr, 89 | "package": config.Package, 90 | "signType": config.SignType, 91 | }, key) 92 | return config 93 | } 94 | 95 | // AppConfig ... 96 | type AppConfig struct { 97 | AppID string `json:"appid"` 98 | NonceStr string `json:"noncestr"` 99 | Package string `json:"package"` 100 | PartnerID string `json:"partnerid"` 101 | PrepayID string `json:"prepayid"` 102 | Sign string `json:"sign"` 103 | TimeStamp string `json:"timestamp"` 104 | } 105 | 106 | /*BuildAppConfig app 设置 */ 107 | func BuildAppConfig(id, mch, key, pid string) *AppConfig { 108 | config := &AppConfig{ 109 | AppID: id, 110 | NonceStr: util.GenerateNonceStr(), 111 | Package: "Sign=WXPay", 112 | PartnerID: mch, 113 | PrepayID: pid, 114 | TimeStamp: util.Time(), 115 | } 116 | 117 | config.Sign = util.GenSign(util.Map{ 118 | "appid": config.AppID, 119 | "partnerid": config.PartnerID, 120 | "prepayid": config.PrepayID, 121 | "noncestr": config.NonceStr, 122 | "timestamp": config.TimeStamp, 123 | "package": config.Package, 124 | }, key) 125 | return config 126 | } 127 | 128 | // ShareAddressConfig ... 129 | type ShareAddressConfig struct { 130 | AddrSign string `json:"addrSign"` 131 | AppID string `json:"appId"` 132 | NonceStr string `json:"nonceStr"` 133 | Scope string `json:"scope"` 134 | SignType string `json:"signType"` 135 | TimeStamp string `json:"timeStamp"` 136 | } 137 | 138 | // BuildShareAddressConfig ... 139 | // 参数:token 140 | // 类型:string或*core.AccessToken 141 | func BuildShareAddressConfig(id, url, token string) *ShareAddressConfig { 142 | config := &ShareAddressConfig{ 143 | AppID: id, 144 | NonceStr: util.GenerateNonceStr(), 145 | Scope: "jsapi_address", 146 | SignType: "SHA1", 147 | TimeStamp: util.Time(), 148 | } 149 | 150 | signMsg := util.Map{ 151 | "appid": config.AppID, 152 | "url": url, 153 | "timestamp": config.TimeStamp, 154 | "noncestr": config.NonceStr, 155 | "accesstoken": token, 156 | } 157 | 158 | config.AddrSign = util.GenSHA1(signMsg.URLEncode()) 159 | 160 | return config 161 | } 162 | 163 | // BuildConfig ... 164 | type BuildConfig struct { 165 | AppID string `json:"appID"` 166 | JSAPIList []string `json:"jsApiList"` 167 | NonceStr string `json:"nonceStr"` 168 | Signature string `json:"signature"` 169 | Timestamp string `json:"timestamp"` 170 | URL string `json:"url"` 171 | } 172 | 173 | // BuildConfig ... 174 | func (obj *JSSDK) BuildConfig(url string, list ...string) *BuildConfig { 175 | ticket := obj.GetTicket("jsapi", false) 176 | if ticket == "" { 177 | return nil 178 | } 179 | 180 | config := &BuildConfig{ 181 | AppID: obj.getID(), 182 | NonceStr: util.GenerateNonceStr(), 183 | Timestamp: util.Time(), 184 | URL: util.MustString(url, obj.getURL()), 185 | JSAPIList: list, 186 | } 187 | config.Signature = getTicketSignature(ticket, config.NonceStr, config.Timestamp, config.URL) 188 | return config 189 | } 190 | 191 | // GetTicket ... 192 | func (obj *JSSDK) GetTicket(s string, refresh bool) string { 193 | key := obj.getCacheKey() 194 | log.Info("key:", key) 195 | if !refresh && cache.Has(key) { 196 | if v, b := cache.Get(key).(string); b { 197 | log.Infof("cached ticket:%+v", v) 198 | return v 199 | } 200 | } 201 | 202 | tr, e := NewTicket(obj.accessToken).GetTicketRes(s) 203 | if e != nil { 204 | log.Error(e) 205 | return "" 206 | } 207 | log.Infof("ticket:%+v", *tr) 208 | cache.SetWithTTL(obj.getCacheKey(), tr.Ticket, tr.ExpiresIn-500) 209 | return tr.Ticket 210 | 211 | } 212 | 213 | // getID ... 214 | func (obj *JSSDK) getID() string { 215 | if obj.subAppID != "" { 216 | return obj.subAppID 217 | } 218 | return obj.AppID 219 | } 220 | 221 | func (obj *JSSDK) getCacheKey() string { 222 | c := md5.Sum([]byte("jssdk." + obj.getID())) 223 | return fmt.Sprintf("godcong.wego.jssdk.ticket.%x", c[:]) 224 | } 225 | 226 | func (obj *JSSDK) parse(options ...JSSDKOption) { 227 | if options == nil { 228 | return 229 | } 230 | for _, o := range options { 231 | o(obj) 232 | } 233 | } 234 | 235 | func getTicketSignature(ticket, nonce, ts, url string) string { 236 | return util.GenSHA1(fmt.Sprintf("jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s", ticket, nonce, ts, url)) 237 | } 238 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "github.com/godcong/go-trait" 5 | ) 6 | 7 | var log = trait.ZapSugar() 8 | -------------------------------------------------------------------------------- /notify.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/godcong/wego/cipher" 6 | "github.com/godcong/wego/util" 7 | "github.com/json-iterator/go" 8 | "golang.org/x/xerrors" 9 | "net/http" 10 | "net/url" 11 | ) 12 | 13 | // NotifyResult ... 14 | type NotifyResult struct { 15 | ReturnCode string `json:"return_code" xml:"return_code"` 16 | ReturnMsg string `json:"return_msg,omitempty" xml:"return_msg,omitempty"` 17 | AppID string `json:"appid,omitempty" xml:"appid,omitempty"` 18 | MchID string `json:"mch_id,omitempty" xml:"mch_id,omitempty"` 19 | NonceStr string `json:"nonce_str,omitempty" xml:"nonce_str,omitempty"` 20 | PrepayID string `json:"prepay_id,omitempty" xml:"prepay_id,omitempty"` 21 | ResultCode string `json:"result_code,omitempty" xml:"result_code,omitempty"` 22 | ErrCodeDes string `json:"err_code_des,omitempty" xml:"err_code_des,omitempty"` 23 | Sign string `json:"sign,omitempty" xml:"sign,omitempty"` 24 | } 25 | 26 | // Notifier ... 27 | type Notifier interface { 28 | ServeHTTP(w http.ResponseWriter, req *http.Request) 29 | } 30 | 31 | // ServeHTTPFunc ... 32 | type ServeHTTPFunc func(w http.ResponseWriter, req *http.Request) 33 | 34 | // RequestHook ... 35 | type RequestHook func(req Requester) (util.Map, error) 36 | 37 | // TokenHook ... 38 | type TokenHook func(w http.ResponseWriter, req *http.Request, token *Token, state string) []byte 39 | 40 | // UserHook ... 41 | type UserHook func(w http.ResponseWriter, req *http.Request, user *WechatUser) []byte 42 | 43 | // StateHook ... 44 | type StateHook func(w http.ResponseWriter, req *http.Request) string 45 | 46 | /*authorizeNotify 监听 */ 47 | type authorizeNotify struct { 48 | *OfficialAccount 49 | TokenHook 50 | UserHook 51 | StateHook 52 | } 53 | 54 | // ServeHTTP ... 55 | func (n *authorizeNotify) ServeHTTP(w http.ResponseWriter, req *http.Request) { 56 | log.Debug("authorizeNotify") 57 | query := req.URL.Query() 58 | if code := query.Get("code"); code != "" { 59 | token := n.hookAuthorizeToken(w, req, code, query.Get("state")) 60 | if token != nil { 61 | info := n.hookUserInfo(w, req, token) 62 | if info != nil { 63 | 64 | } 65 | } 66 | return 67 | } 68 | 69 | u := n.hookState(w, req) 70 | log.Debug("hookState|url", u) 71 | http.Redirect(w, req, u, http.StatusFound) 72 | } 73 | 74 | func (n *authorizeNotify) hookState(w http.ResponseWriter, req *http.Request) string { 75 | if n.StateHook != nil { 76 | s := n.StateHook(w, req) 77 | return n.AuthCodeURL(s) 78 | } 79 | return n.AuthCodeURL("") 80 | } 81 | 82 | func (n *authorizeNotify) hookUserInfo(w http.ResponseWriter, req *http.Request, token *Token) *WechatUser { 83 | log.Debug("hookUserInfo", token) 84 | info, e := n.GetUserInfo(token) 85 | if e != nil { 86 | log.Error("hookUserInfo err:", e.Error()) 87 | return nil 88 | } 89 | if n.UserHook != nil { 90 | bytes := n.UserHook(w, req, info) 91 | n.responseWriter(w, bytes) 92 | } 93 | return info 94 | } 95 | 96 | // NotifyResult ... 97 | func (n *authorizeNotify) responseWriter(w http.ResponseWriter, bytes []byte) { 98 | e := ResponseWriter(w, JSONResponse(bytes)) 99 | if e != nil { 100 | log.Error(e) 101 | } 102 | return 103 | } 104 | 105 | func (n *authorizeNotify) hookAuthorizeToken(w http.ResponseWriter, req *http.Request, code string, state string) *Token { 106 | log.Debug("hookAuthorizeToken", code) 107 | token, e := n.Oauth2AuthorizeToken(code) 108 | if e != nil { 109 | return nil 110 | } 111 | if n.TokenHook != nil { 112 | bytes := n.TokenHook(w, req, token, state) 113 | n.responseWriter(w, bytes) 114 | } 115 | return token 116 | } 117 | 118 | /*messageNotify 监听 */ 119 | type messageNotify struct { 120 | *OfficialAccount 121 | RequestHook 122 | cipher cipher.Cipher 123 | //bizMsg *cipher.BizMsg 124 | } 125 | 126 | // DecodeReqInfo ... 127 | func (n *messageNotify) decodeInfo(query url.Values, requester Requester) (util.Map, error) { 128 | var bodies []byte 129 | var e error 130 | encryptType := query.Get("encrypt_type") 131 | timeStamp := query.Get("timestamp") 132 | nonce := query.Get("nonce") 133 | msgSignature := query.Get("msg_signature") 134 | if encryptType != "aes" { 135 | p := util.Map{} 136 | e = xml.Unmarshal(bodies, &p) 137 | if e != nil { 138 | log.Error(e) 139 | return nil, e 140 | } 141 | 142 | bodies, e = n.cipher.Decrypt(&cipher.BizMsgData{ 143 | RSAEncrypt: p.GetString("RSAEncrypt"), 144 | TimeStamp: timeStamp, 145 | Nonce: nonce, 146 | MsgSignature: msgSignature, 147 | }) 148 | 149 | //错误返回,并记录log 150 | if e != nil { 151 | log.Error(e) 152 | return nil, e 153 | } 154 | } 155 | p := util.Map{} 156 | e = xml.Unmarshal(bodies, &p) 157 | if e != nil { 158 | log.Error(e) 159 | return nil, e 160 | } 161 | return p, e 162 | } 163 | 164 | // DecodeReqInfo ... 165 | func (n *messageNotify) encodeInfo(p util.Map, ts, nonce string) ([]byte, error) { 166 | var e error 167 | bodies, e := n.cipher.Encrypt(&cipher.BizMsgData{ 168 | Text: string(p.ToXML()), 169 | TimeStamp: ts, 170 | Nonce: nonce, 171 | }) 172 | //错误返回,并记录log 173 | if e != nil { 174 | log.Error(e) 175 | return nil, e 176 | } 177 | return bodies, nil 178 | } 179 | 180 | // ServeHTTP ... 181 | func (n *messageNotify) ServeHTTP(w http.ResponseWriter, req *http.Request) { 182 | var e error 183 | 184 | if n.RequestHook == nil { 185 | log.Error(xerrors.New("null notify callback ")) 186 | return 187 | } 188 | requester := BuildRequester(req) 189 | if e = requester.Error(); e != nil { 190 | log.Error(e) 191 | return 192 | } 193 | 194 | query, e := url.ParseQuery(req.URL.RawQuery) 195 | if e != nil { 196 | log.Error(e) 197 | return 198 | } 199 | maps, e := n.decodeInfo(query, requester) 200 | if e != nil { 201 | log.Error(e) 202 | return 203 | } 204 | 205 | r, e := n.RequestHook(RebuildRequester(requester, maps)) 206 | if e != nil { 207 | log.Error(e) 208 | return 209 | } 210 | 211 | _, e = w.Write(r.ToXML()) 212 | 213 | if e != nil { 214 | log.Error(e) 215 | return 216 | } 217 | } 218 | 219 | /*Notifier 监听 */ 220 | type paymentPaidNotify struct { 221 | *Payment 222 | RequestHook 223 | } 224 | 225 | // ServerHttp ... 226 | func (n *paymentPaidNotify) ServeHTTP(w http.ResponseWriter, req *http.Request) { 227 | var e error 228 | requester := BuildRequester(req) 229 | resp := NotifyTypeResponder(requester.Type(), NotifySuccess()) 230 | defer func() { 231 | e = resp.Write(w) 232 | log.Error(e) 233 | }() 234 | 235 | if e = requester.Error(); e != nil { 236 | log.Error(e.Error()) 237 | resp.SetNotifyResult(NotifyFail(e.Error())) 238 | return 239 | } 240 | reqData := requester.ToMap() 241 | if util.ValidateSign(reqData, n.GetKey()) { 242 | if n.RequestHook == nil { 243 | log.Error(xerrors.New("null notify callback ")) 244 | return 245 | } 246 | _, e = n.RequestHook(requester) 247 | if e != nil { 248 | log.Error(e.Error()) 249 | resp.SetNotifyResult(NotifyFail(e.Error())) 250 | } 251 | } 252 | 253 | } 254 | 255 | /*Notifier 监听 */ 256 | type paymentRefundedNotify struct { 257 | cipher cipher.Cipher 258 | RequestHook 259 | } 260 | 261 | // ServeHTTP ... 262 | func (obj *paymentRefundedNotify) ServeHTTP(w http.ResponseWriter, req *http.Request) { 263 | var e error 264 | if obj.RequestHook == nil { 265 | log.Error(xerrors.New("null notify callback")) 266 | return 267 | } 268 | 269 | requester := BuildRequester(req) 270 | resp := NotifyTypeResponder(requester.Type(), NotifySuccess()) 271 | defer func() { 272 | e = resp.Write(w) 273 | log.Error(e) 274 | }() 275 | 276 | if e = requester.Error(); e != nil { 277 | log.Error(e.Error()) 278 | resp.SetNotifyResult(NotifyFail(e.Error())) 279 | return 280 | } 281 | reqData := requester.ToMap() 282 | reqInfo := reqData.GetString("req_info") 283 | reqData.Set("reqInfo", obj.DecodeReqInfo(reqInfo)) 284 | 285 | _, e = obj.RequestHook(requester) 286 | if e != nil { 287 | log.Error(e.Error()) 288 | resp.SetNotifyResult(NotifyFail(e.Error())) 289 | } 290 | } 291 | 292 | // DecodeReqInfo ... 293 | func (obj *paymentRefundedNotify) DecodeReqInfo(info string) util.Map { 294 | maps := util.Map{} 295 | dec, _ := obj.cipher.Decrypt(info) 296 | e := xml.Unmarshal(dec, &maps) 297 | if e != nil { 298 | log.Error(e) 299 | } 300 | return maps 301 | } 302 | 303 | /*Notifier 监听 */ 304 | type paymentScannedNotify struct { 305 | *Payment 306 | RequestHook 307 | } 308 | 309 | // ServeHTTP ... 310 | func (obj *paymentScannedNotify) ServeHTTP(w http.ResponseWriter, req *http.Request) { 311 | var e error 312 | var p util.Map 313 | 314 | if obj.RequestHook == nil { 315 | log.Error(xerrors.New("null notify callback")) 316 | return 317 | } 318 | requester := BuildRequester(req) 319 | resp := NotifyTypeResponder(requester.Type(), NotifySuccess()) 320 | defer func() { 321 | e = resp.Write(w) 322 | log.Error(e) 323 | }() 324 | 325 | if e = requester.Error(); e != nil { 326 | log.Error(e.Error()) 327 | resp.SetNotifyResult(NotifyFail(e.Error())) 328 | return 329 | } 330 | reqData := requester.ToMap() 331 | if util.ValidateSign(reqData, obj.GetKey()) { 332 | 333 | p, e = obj.RequestHook(requester) 334 | if e != nil { 335 | log.Error(e.Error()) 336 | resp.SetNotifyResult(NotifyFailDes(resp.NotifyResult(), e.Error())) 337 | } 338 | if !p.Has("prepay_id") { 339 | log.Error("null prepay_id") 340 | resp.SetNotifyResult(NotifyFailDes(resp.NotifyResult(), "null prepay_id")) 341 | } else { 342 | //公众账号ID appid String(32) 是 wx8888888888888888 微信分配的公众账号ID 343 | //商户号 mch_id String(32) 是 1900000109 微信支付分配的商户号 344 | //随机字符串 nonce_str String(32) 是 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 微信返回的随机字符串 345 | //预支付ID prepay_id String(64) 是 wx201410272009395522657a690389285100 调用统一下单接口生成的预支付ID 346 | //业务结果 result_code String(16) 是 SUCCESS SUCCESS/FAIL 347 | //错误描述 err_code_des String(128) 否 当result_code为FAIL时,商户展示给用户的错误提 348 | //签名 sign String(32) 是 C380BEC2BFD727A4B6845133519F3AD6 返回数据签名,签名生成算法 349 | res := resp.NotifyResult() 350 | res.AppID = obj.AppID 351 | res.MchID = obj.MchID 352 | res.NonceStr = util.GenerateNonceStr() 353 | res.PrepayID = p.GetString("prepay_id") 354 | res.Sign = util.GenSign(reqData, obj.GetKey()) 355 | } 356 | 357 | } 358 | 359 | } 360 | 361 | // NotifyResponder ... 362 | type NotifyResponder interface { 363 | SetNotifyResult(result *NotifyResult) 364 | NotifyResult() *NotifyResult 365 | Write(w http.ResponseWriter) error 366 | } 367 | 368 | type xmlNotify struct { 369 | notifyResult *NotifyResult 370 | } 371 | 372 | // NotifyResult ... 373 | func (obj *xmlNotify) NotifyResult() *NotifyResult { 374 | return obj.notifyResult 375 | } 376 | 377 | // SetNotifyResult ... 378 | func (obj *xmlNotify) SetNotifyResult(notifyResult *NotifyResult) { 379 | obj.notifyResult = notifyResult 380 | } 381 | 382 | // Write ... 383 | func (obj *xmlNotify) Write(w http.ResponseWriter) error { 384 | w.WriteHeader(http.StatusOK) 385 | header := w.Header() 386 | if val := header["Content-Type"]; len(val) == 0 { 387 | header["Content-Type"] = []string{"application/xml; charset=utf-8"} 388 | } 389 | if obj.notifyResult == nil { 390 | return xerrors.New("null notify result") 391 | } 392 | _, err := w.Write(obj.notifyResult.ToXML()) 393 | if err != nil { 394 | log.Error(err) 395 | return err 396 | } 397 | return nil 398 | } 399 | 400 | type jsonNotify struct { 401 | notifyResult *NotifyResult 402 | } 403 | 404 | // NotifyResult ... 405 | func (obj *jsonNotify) NotifyResult() *NotifyResult { 406 | return obj.notifyResult 407 | } 408 | 409 | // SetNotifyResult ... 410 | func (obj *jsonNotify) SetNotifyResult(notifyResult *NotifyResult) { 411 | obj.notifyResult = notifyResult 412 | } 413 | 414 | // Write ... 415 | func (obj *jsonNotify) Write(w http.ResponseWriter) error { 416 | w.WriteHeader(http.StatusOK) 417 | header := w.Header() 418 | if val := header["Content-Type"]; len(val) == 0 { 419 | header["Content-Type"] = []string{"application/json; charset=utf-8"} 420 | } 421 | if obj.notifyResult == nil { 422 | return xerrors.New("null notify result") 423 | } 424 | _, err := w.Write(obj.notifyResult.ToJSON()) 425 | if err != nil { 426 | log.Error(err) 427 | return err 428 | } 429 | return nil 430 | } 431 | 432 | // NotifyTypeResponder ... 433 | func NotifyTypeResponder(bodyType BodyType, notifyResult *NotifyResult) NotifyResponder { 434 | switch bodyType { 435 | case BodyTypeJSON: 436 | return &jsonNotify{ 437 | notifyResult: notifyResult, 438 | } 439 | case BodyTypeXML: 440 | return &xmlNotify{ 441 | notifyResult: notifyResult, 442 | } 443 | } 444 | return nil 445 | } 446 | 447 | // ToJSON ... 448 | func (obj *NotifyResult) ToJSON() []byte { 449 | bytes, e := jsoniter.Marshal(obj) 450 | if e != nil { 451 | log.Error(e) 452 | return nil 453 | } 454 | return bytes 455 | } 456 | 457 | // ToXML ... 458 | func (obj *NotifyResult) ToXML() []byte { 459 | bytes, e := xml.Marshal(obj) 460 | if e != nil { 461 | log.Error(e) 462 | return nil 463 | } 464 | return bytes 465 | } 466 | 467 | // NotifySuccess ... 468 | func NotifySuccess() *NotifyResult { 469 | return &NotifyResult{ 470 | ReturnCode: "SUCCESS", 471 | ReturnMsg: "OK", 472 | } 473 | } 474 | 475 | // NotifyFail ... 476 | func NotifyFail(msg string) *NotifyResult { 477 | return &NotifyResult{ 478 | ReturnCode: "FAIL", 479 | ReturnMsg: msg, 480 | } 481 | } 482 | 483 | // NotifyFailDes ... 484 | func NotifyFailDes(r *NotifyResult, msg string) *NotifyResult { 485 | r.ResultCode = "FAIL" 486 | r.ErrCodeDes = msg 487 | return r 488 | } 489 | -------------------------------------------------------------------------------- /official_account_button.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import "github.com/godcong/wego/util" 4 | 5 | /*Button Button */ 6 | type Button struct { 7 | util.Map 8 | } 9 | 10 | /*MatchRule MatchRule*/ 11 | type MatchRule struct { 12 | TagID string `json:"tag_id,omitempty"` 13 | Sex string `json:"sex,omitempty"` 14 | Country string `json:"country,omitempty"` 15 | Province string `json:"province,omitempty"` 16 | City string `json:"city,omitempty"` 17 | ClientPlatformType string `json:"client_platform_type,omitempty"` 18 | Language string `json:"language,omitempty"` 19 | } 20 | 21 | /*NewClickButton NewClickButton*/ 22 | func NewClickButton(name, key string) *Button { 23 | return newButton(EventTypeClick, util.Map{"name": name, "key": key}) 24 | 25 | } 26 | 27 | /*NewViewButton NewViewButton*/ 28 | func NewViewButton(name, url string) *Button { 29 | return newButton(EventTypeView, util.Map{"name": name, "url": url}) 30 | } 31 | 32 | /*NewSubButton NewSubButton*/ 33 | func NewSubButton(name string, sub []*Button) *Button { 34 | return newButton("", util.Map{"name": name, "key": "testkey", "sub_button": sub}) 35 | } 36 | 37 | func newButton(typ EventType, val util.Map) *Button { 38 | button := NewBaseButton() 39 | if typ != "" { 40 | button.Set("type", typ) 41 | } 42 | button.Join(val) 43 | return button 44 | } 45 | 46 | /*SetSub SetSub*/ 47 | func (b *Button) SetSub(name string, sub []*Button) *Button { 48 | b.Map = util.Map{} 49 | b.Set("name", name) 50 | b.Set("sub_button", sub) 51 | return b 52 | } 53 | 54 | /*NewBaseButton NewBaseButton */ 55 | func NewBaseButton() *Button { 56 | return &Button{ 57 | Map: make(util.Map), 58 | } 59 | } 60 | 61 | /*SetButtons SetButtons*/ 62 | func (b *Button) SetButtons(buttons []*Button) *Button { 63 | b.Set("button", buttons) 64 | return b 65 | } 66 | 67 | /*GetButtons GetButtons */ 68 | func (b *Button) GetButtons() []*Button { 69 | buttons := b.Get("button") 70 | if v0, b := buttons.([]*Button); b { 71 | return v0 72 | } 73 | return nil 74 | } 75 | 76 | /*GetMatchRule GetMatchRule*/ 77 | func (b *Button) GetMatchRule() *MatchRule { 78 | if mr := b.Get("matchrule"); mr != nil { 79 | return mr.(*MatchRule) 80 | } 81 | return nil 82 | } 83 | 84 | /*SetMatchRule SetMatchRule*/ 85 | func (b *Button) SetMatchRule(rule *MatchRule) *Button { 86 | b.Set("matchrule", rule) 87 | return b 88 | } 89 | 90 | func (b *Button) mapGet(name string) interface{} { 91 | return b.Get(name) 92 | } 93 | 94 | func (b *Button) mapSet(name string, v interface{}) util.Map { 95 | return b.Set(name, v) 96 | } 97 | 98 | /*AddButton AddButton*/ 99 | func (b *Button) AddButton(buttons *Button) *Button { 100 | if v := b.GetButtons(); v != nil { 101 | b.SetButtons(append(v, buttons)) 102 | } else { 103 | b.SetButtons([]*Button{buttons}) 104 | } 105 | return b 106 | } 107 | -------------------------------------------------------------------------------- /official_account_merchant.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | // MerchantCategoryInfo ... 4 | type MerchantCategoryInfo struct { 5 | Errcode int64 `json:"errcode"` 6 | Errmsg string `json:"errmsg"` 7 | Data Data `json:"data"` 8 | } 9 | 10 | // Data ... 11 | type Data struct { 12 | AllCategoryInfo AllCategoryInfo `json:"all_category_info"` 13 | } 14 | 15 | // AllCategoryInfo ... 16 | type AllCategoryInfo struct { 17 | Categories []Category `json:"categories"` 18 | } 19 | 20 | // Category ... 21 | type Category struct { 22 | ID int64 `json:"id"` 23 | Name string `json:"name"` 24 | Level int64 `json:"level"` 25 | Children []int64 `json:"children"` 26 | Father *int64 `json:"father,omitempty"` 27 | Qualify *Qualify `json:"qualify,omitempty"` 28 | Scene *int64 `json:"scene,omitempty"` 29 | SensitiveType *int64 `json:"sensitive_type,omitempty"` 30 | } 31 | 32 | // Qualify ... 33 | type Qualify struct { 34 | ExterList []ExterList `json:"exter_list"` 35 | } 36 | 37 | // ExterList ... 38 | type ExterList struct { 39 | InnerList []InnerList `json:"inner_list"` 40 | } 41 | 42 | // InnerList ... 43 | type InnerList struct { 44 | Name string `json:"name"` 45 | } 46 | 47 | // MerchantApplyInfo ... 48 | type MerchantApplyInfo struct { 49 | FirstCatID int64 `json:"first_catid"` 50 | SecondCatID int64 `json:"second_catid"` 51 | QualificationList string `json:"qualification_list"` 52 | HeadImgMediaID string `json:"headimg_mediaid"` 53 | Nickname string `json:"nickname"` 54 | Intro string `json:"intro"` 55 | OrgCode string `json:"org_code"` 56 | OtherFiles string `json:"other_files,omitempty"` 57 | } 58 | 59 | // MerchantGetCategory 拉取门店小程序类目 60 | // 请求方式:GET(请使用https协议) 61 | // https://api.weixin.qq.com/wxa/get_merchant_category?access_token=TOKEN 62 | func (obj *OfficialAccount) MerchantGetCategory(info *MerchantCategoryInfo) Responder { 63 | //TODO 64 | panic("todo") 65 | } 66 | 67 | //MerchantApply 创建门店小程序 68 | // 请求方式: POST(请使用https协议) 69 | // https://api.weixin.qq.com/wxa/apply_merchant?access_token=TOKEN 70 | func (obj *OfficialAccount) MerchantApply(info *MerchantApplyInfo) Responder { 71 | //TODO 72 | panic("todo") 73 | } 74 | -------------------------------------------------------------------------------- /official_account_user.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "context" 5 | "github.com/godcong/wego/util" 6 | jsoniter "github.com/json-iterator/go" 7 | "log" 8 | ) 9 | 10 | //UserUpdateRemark 设置用户备注名 11 | // http请求方式: POST(请使用https协议) 12 | // https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN 13 | // POST数据格式:JSON 14 | // POST数据例子: 15 | // { 16 | // "openid":"oDF3iY9ffA-hqb2vVvbr7qxf6A0Q", 17 | // "remark":"pangzi" 18 | // } 19 | // 成功: 20 | // {"errcode":0,"errmsg":"ok"} 21 | // 失败: 22 | // {"errcode":40013,"errmsg":"invalid appid"} 23 | func (obj *OfficialAccount) UserUpdateRemark(openid, remark string) Responder { 24 | log.Debug("OfficialAccount|UserUpdateRemark", openid, remark) 25 | u := util.URL(userInfoUpdateRemark) 26 | return obj.Client().Post(context.Background(), u, nil, util.Map{"openid": openid, "remark": remark}) 27 | } 28 | 29 | //UserInfo 获取用户信息 30 | // 接口调用请求说明 31 | // http请求方式: GET 32 | // https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN 33 | // 成功: 34 | // {"subscribe":1,"openid":"o6_bmjrPTlm6_2sgVt7hMZOPfL2M","nickname":"Band","sex":1,"language":"zh_CN","city":"广州","province":"广东","country":"中国","headimgurl":"http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0","subscribe_time":1382694957,"unionid":"o6_bmasdasdsad6_2sgVt7hMZOPfL""remark":"","groupid":0,"tagid_list":[128,2],"subscribe_scene":"ADD_SCENE_QR_CODE","qr_scene":98765,"qr_scene_str":""} 35 | func (obj *OfficialAccount) UserInfo(openid, lang string) (info *UserInfo, e error) { 36 | log.Debug("OfficialAccount|UserInfo", openid, lang) 37 | p := util.Map{"openid": openid} 38 | if lang != "" { 39 | p.Set("lang", lang) 40 | } 41 | u := util.URL(userInfo) 42 | resp := obj.Client().Get(context.Background(), u, p) 43 | if e = resp.Error(); e != nil { 44 | return nil, e 45 | } 46 | info = new(UserInfo) 47 | e = jsoniter.Unmarshal(resp.Bytes(), info) 48 | if e != nil { 49 | return nil, e 50 | } 51 | return info, nil 52 | } 53 | 54 | //UserBatchGet 批量获取用户基本信息 55 | // http请求方式: POST 56 | // https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN 57 | // 成功: 58 | // {"user_info_list":[{"subscribe":1,"openid":"oLyBi0tDnybg0WFkhKsn5HRetX1I","nickname":"sean","sex":1,"language":"zh_CN","city":"浦东新区","province":"上海","country":"中国","headimgurl":"http:\/\/thirdwx.qlogo.cn\/mmopen\/anblvjPKYbMGjBnTVxw5gEZiasF6LiaMHheNxN4vWJcfCLRl8gEX0L6M7sNjtMkFYx8PJRCS1lr9RGxadkFlBibpA\/132","subscribe_time":1521022410,"remark":"nishi123","groupid":101,"tagid_list":[101],"subscribe_scene":"ADD_SCENE_PROFILE_CARD","qr_scene":0,"qr_scene_str":""}]} 59 | // 失败: 60 | // {"errcode":40013,"errmsg":"invalid appid"} 61 | func (obj *OfficialAccount) UserBatchGet(openids []string, lang string) (infos []*UserInfo, e error) { 62 | log.Debug("User|BatchGet", openids, lang) 63 | u := util.URL(userInfoBatchGet) 64 | var list []*UserID 65 | for _, v := range openids { 66 | if lang != "" { 67 | list = append(list, &UserID{OpenID: v, Lang: lang}) 68 | } else { 69 | list = append(list, &UserID{OpenID: v}) 70 | } 71 | 72 | } 73 | resp := obj.Client().Post(context.Background(), u, nil, util.Map{"user_list": list}) 74 | if e = resp.Error(); e != nil { 75 | return nil, e 76 | } 77 | infoList := UserInfoList{} 78 | e = jsoniter.Unmarshal(resp.Bytes(), &infoList) 79 | if e != nil { 80 | return nil, e 81 | } 82 | return infoList.UserInfoList, nil 83 | } 84 | 85 | //UserGet 获取用户列表 86 | // http请求方式: GET(请使用https协议) 87 | // https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID 88 | func (obj *OfficialAccount) UserGet(nextOpenid string) Responder { 89 | log.Debug("OfficialAccount|UserGet", nextOpenid) 90 | u := util.URL(userGet) 91 | if nextOpenid == "" { 92 | return obj.Client().Get(context.Background(), u, nil) 93 | } 94 | return obj.Client().Get(context.Background(), u, util.Map{"next_openid": nextOpenid}) 95 | } 96 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | ) 7 | 8 | // PaymentOption ... 9 | type PaymentOption func(obj *Payment) 10 | 11 | // PaymentBodyType ... 12 | func PaymentBodyType(s BodyType) PaymentOption { 13 | return func(obj *Payment) { 14 | obj.BodyType = s 15 | } 16 | } 17 | 18 | // PaymentSubID ... 19 | func PaymentSubID(mchid, appid string) PaymentOption { 20 | return func(obj *Payment) { 21 | obj.subAppID = appid 22 | obj.subMchID = mchid 23 | } 24 | } 25 | 26 | // PaymentKey ... 27 | func PaymentKey(public, privite string) PaymentOption { 28 | return func(obj *Payment) { 29 | obj.publicKey = public 30 | obj.privateKey = privite 31 | } 32 | } 33 | 34 | // PaymentRemote ... 35 | func PaymentRemote(remote string) PaymentOption { 36 | return func(obj *Payment) { 37 | obj.remoteURL = remote 38 | } 39 | } 40 | 41 | // PaymentLocal ... 42 | func PaymentLocal(local string) PaymentOption { 43 | return func(obj *Payment) { 44 | obj.localHost = local 45 | } 46 | } 47 | 48 | // PaymentSandbox ... 49 | func PaymentSandbox(sandbox *Sandbox) PaymentOption { 50 | return func(obj *Payment) { 51 | obj.sandbox = sandbox 52 | } 53 | } 54 | 55 | // PaymentNotifyURL ... 56 | func PaymentNotifyURL(s string) PaymentOption { 57 | return func(obj *Payment) { 58 | obj.notifyURL = s 59 | } 60 | } 61 | 62 | // PaymentRefundedURL ... 63 | func PaymentRefundedURL(s string) PaymentOption { 64 | return func(obj *Payment) { 65 | obj.refundedURL = s 66 | } 67 | } 68 | 69 | // PaymentScannedURL ... 70 | func PaymentScannedURL(s string) PaymentOption { 71 | return func(obj *Payment) { 72 | obj.scannedURL = s 73 | } 74 | } 75 | 76 | // AccessTokenOption ... 77 | type AccessTokenOption func(obj *AccessToken) 78 | 79 | // AccessTokenRemote ... 80 | func AccessTokenRemote(url string) AccessTokenOption { 81 | return func(obj *AccessToken) { 82 | obj.remoteURL = url 83 | } 84 | } 85 | 86 | // AccessTokenURL ... 87 | func AccessTokenURL(url string) AccessTokenOption { 88 | return func(obj *AccessToken) { 89 | obj.tokenURL = url 90 | } 91 | } 92 | 93 | // AccessTokenKey ... 94 | func AccessTokenKey(key string) AccessTokenOption { 95 | return func(obj *AccessToken) { 96 | obj.tokenKey = key 97 | } 98 | } 99 | 100 | // OfficialAccountOption ... 101 | type OfficialAccountOption func(obj *OfficialAccount) 102 | 103 | // OfficialAccountOauth ... 104 | func OfficialAccountOauth(oauth *OAuthProperty) OfficialAccountOption { 105 | return func(obj *OfficialAccount) { 106 | obj.oauth = *oauth 107 | } 108 | } 109 | 110 | // OfficialAccountJSSDK ... 111 | func OfficialAccountJSSDK(jssdk *JSSDK) OfficialAccountOption { 112 | return func(obj *OfficialAccount) { 113 | obj.jssdk = jssdk 114 | } 115 | } 116 | 117 | // OfficialAccountAccessTokenProperty ... 118 | func OfficialAccountAccessTokenProperty(property *AccessTokenProperty) OfficialAccountOption { 119 | return func(obj *OfficialAccount) { 120 | obj.accessToken = NewAccessToken(property, AccessTokenKey(accessTokenKey), AccessTokenURL(accessToken)) 121 | } 122 | } 123 | 124 | // OfficialAccountAccessToken ... 125 | func OfficialAccountAccessToken(token *AccessToken) OfficialAccountOption { 126 | return func(obj *OfficialAccount) { 127 | obj.accessToken = token 128 | } 129 | } 130 | 131 | // OfficialAccountBodyType ... 132 | func OfficialAccountBodyType(bodyType BodyType) OfficialAccountOption { 133 | return func(obj *OfficialAccount) { 134 | obj.BodyType = bodyType 135 | } 136 | } 137 | 138 | // OfficialAccountRemote ... 139 | func OfficialAccountRemote(remote string) OfficialAccountOption { 140 | return func(obj *OfficialAccount) { 141 | obj.remoteURL = remote 142 | } 143 | } 144 | 145 | // OfficialAccountLocal ... 146 | func OfficialAccountLocal(local string) OfficialAccountOption { 147 | return func(obj *OfficialAccount) { 148 | obj.localHost = local 149 | } 150 | } 151 | 152 | // ClientOption ... 153 | type ClientOption func(obj *Client) 154 | 155 | // ClientContext ... 156 | func ClientContext(ctx context.Context) ClientOption { 157 | return func(obj *Client) { 158 | obj.context = ctx 159 | } 160 | } 161 | 162 | // ClientAccessToken ... 163 | func ClientAccessToken(token *AccessToken) ClientOption { 164 | return func(obj *Client) { 165 | obj.accessToken = token 166 | } 167 | } 168 | 169 | // ClientAccessTokenProperty ... 170 | func ClientAccessTokenProperty(property *AccessTokenProperty) ClientOption { 171 | return func(obj *Client) { 172 | obj.accessToken = NewAccessToken(property) 173 | } 174 | } 175 | 176 | // ClientSafeCert ... 177 | func ClientSafeCert(property *SafeCertProperty) ClientOption { 178 | return func(obj *Client) { 179 | cfg, e := property.Config() 180 | if e != nil { 181 | log.Errorf("ClientSafeCert err:%+v", e) 182 | return 183 | } 184 | obj.TLSConfig = cfg 185 | 186 | } 187 | } 188 | 189 | // ClientTLSConfig ... 190 | func ClientTLSConfig(config *tls.Config) ClientOption { 191 | return func(obj *Client) { 192 | obj.TLSConfig = config 193 | 194 | } 195 | } 196 | 197 | // ClientBodyType ... 198 | func ClientBodyType(bt BodyType) ClientOption { 199 | return func(obj *Client) { 200 | obj.BodyType = bt 201 | } 202 | } 203 | 204 | // SandboxOption ... 205 | type SandboxOption func(obj *Sandbox) 206 | 207 | // SandboxSubID ... 208 | func SandboxSubID(mch, app string) SandboxOption { 209 | return func(obj *Sandbox) { 210 | obj.subAppID = app 211 | obj.subMchID = mch 212 | } 213 | } 214 | 215 | // JSSDKOption ... 216 | type JSSDKOption func(obj *JSSDK) 217 | 218 | // JSSDKAccessTokenProperty ... 219 | func JSSDKAccessTokenProperty(property *AccessTokenProperty) JSSDKOption { 220 | return func(obj *JSSDK) { 221 | obj.accessToken = NewAccessToken(property, AccessTokenKey(accessTokenKey), AccessTokenURL(accessToken)) 222 | } 223 | } 224 | 225 | // JSSDKAccessToken ... 226 | func JSSDKAccessToken(token *AccessToken) JSSDKOption { 227 | return func(obj *JSSDK) { 228 | obj.accessToken = token 229 | } 230 | } 231 | 232 | // JSSDKSubAppID ... 233 | func JSSDKSubAppID(id string) JSSDKOption { 234 | return func(obj *JSSDK) { 235 | obj.subAppID = id 236 | } 237 | } 238 | 239 | // JSSDKURL ... 240 | func JSSDKURL(url string) JSSDKOption { 241 | return func(obj *JSSDK) { 242 | obj.url = url 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /property.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "github.com/godcong/wego/util" 7 | "github.com/json-iterator/go" 8 | "golang.org/x/xerrors" 9 | ) 10 | 11 | // NilPropertyProperty ... 12 | const NilPropertyProperty = "%T point is null" 13 | 14 | // SandboxProperty ... 15 | type SandboxProperty struct { 16 | AppID string 17 | AppSecret string 18 | MchID string 19 | Key string 20 | } 21 | 22 | // SafeCertProperty ... 23 | type SafeCertProperty struct { 24 | Cert []byte 25 | Key []byte 26 | RootCA []byte 27 | } 28 | 29 | // Config ... 30 | func (property *SafeCertProperty) Config() (config *tls.Config, e error) { 31 | cert, e := tls.X509KeyPair(property.Cert, property.Key) 32 | if e != nil { 33 | return &tls.Config{}, e 34 | } 35 | 36 | certPool := x509.NewCertPool() 37 | certPool.AppendCertsFromPEM(property.RootCA) 38 | tlsConfig := &tls.Config{ 39 | Certificates: []tls.Certificate{cert}, 40 | RootCAs: certPool, 41 | InsecureSkipVerify: true, //client端略过对证书的校验 42 | } 43 | tlsConfig.BuildNameToCertificate() 44 | return tlsConfig, nil 45 | } 46 | 47 | // PaymentProperty ... 48 | type PaymentProperty struct { 49 | AppID string `xml:"app_id"` 50 | AppSecret string `xml:"app_secret"` 51 | MchID string `xml:"mch_id"` 52 | Key string `xml:"key"` 53 | SafeCert *SafeCertProperty `xml:"safe_cert"` 54 | } 55 | 56 | // OAuthProperty ... 57 | type OAuthProperty struct { 58 | Scopes []string 59 | RedirectURI string 60 | } 61 | 62 | // OpenPlatformProperty ... 63 | type OpenPlatformProperty struct { 64 | AppID string 65 | AppSecret string 66 | Token string 67 | AesKey string 68 | } 69 | 70 | // OfficialAccountProperty ... 71 | type OfficialAccountProperty struct { 72 | AppID string 73 | AppSecret string 74 | Token string 75 | AesKey string 76 | //OAuth *OAuthProperty 77 | } 78 | 79 | // MiniProgramProperty ... 80 | type MiniProgramProperty struct { 81 | AppID string 82 | AppSecret string 83 | Token string 84 | AesKey string 85 | } 86 | 87 | // GrantTypeClient ... 88 | const GrantTypeClient string = "client_credential" 89 | 90 | // AccessTokenProperty ... 91 | type AccessTokenProperty struct { 92 | GrantType string `toml:"grant_type"` 93 | AppID string `toml:"app_id"` 94 | AppSecret string `toml:"app_secret"` 95 | } 96 | 97 | // ToMap ... 98 | func (obj *AccessTokenProperty) ToMap() util.Map { 99 | return util.Map{ 100 | "grant_type": obj.GrantType, 101 | "appid": obj.AppID, 102 | "secret": obj.AppSecret, 103 | } 104 | } 105 | 106 | // ToJSON ... 107 | func (obj *AccessTokenProperty) ToJSON() []byte { 108 | bytes, err := jsoniter.Marshal(obj) 109 | if err != nil { 110 | return nil 111 | } 112 | return bytes 113 | } 114 | 115 | // JSSDKProperty ... 116 | type JSSDKProperty struct { 117 | AppID string 118 | MchID string 119 | Key string 120 | } 121 | 122 | // Property 属性配置,各个接口用到的参数 123 | type Property struct { 124 | JSSDK *JSSDKProperty 125 | AccessToken *AccessTokenProperty 126 | OAuth *OAuthProperty 127 | OpenPlatform *OpenPlatformProperty 128 | OfficialAccount *OfficialAccountProperty 129 | MiniProgram *MiniProgramProperty 130 | Payment *PaymentProperty 131 | SafeCert *SafeCertProperty 132 | } 133 | 134 | // ParseProperty ... 135 | func ParseProperty(config *Config, v ...interface{}) (e error) { 136 | if config == nil { 137 | return xerrors.New("nil config") 138 | } 139 | 140 | for i := range v { 141 | switch t := v[i].(type) { 142 | case *Property: 143 | //TODO: 144 | case *SafeCertProperty: 145 | e = parseSafeCertProperty(config, t) 146 | case *JSSDKProperty: 147 | e = parseJSSDKProperty(config, t) 148 | case *AccessTokenProperty: 149 | e = parseAccessTokenProperty(config, t) 150 | case *PaymentProperty: 151 | e = parsePaymentProperty(config, t) 152 | case *OAuthProperty: 153 | e = parseOAuthProperty(config, t) 154 | case *OfficialAccountProperty: 155 | e = parseOfficialAccountProperty(config, t) 156 | default: 157 | e = xerrors.Errorf("wrong property point") 158 | } 159 | if e != nil { 160 | return e 161 | } 162 | } 163 | return nil 164 | } 165 | 166 | func parseAccessTokenProperty(config *Config, property *AccessTokenProperty) error { 167 | *property = AccessTokenProperty{ 168 | GrantType: GrantTypeClient, 169 | AppID: config.AppID, 170 | AppSecret: config.AppSecret, 171 | } 172 | return nil 173 | } 174 | 175 | func parseJSSDKProperty(config *Config, property *JSSDKProperty) error { 176 | //var token AccessTokenProperty 177 | //e := parseAccessTokenProperty(config, &token) 178 | //if e != nil { 179 | // return e 180 | //} 181 | *property = JSSDKProperty{ 182 | AppID: config.AppID, 183 | MchID: config.MchID, 184 | Key: config.MchKey, 185 | } 186 | 187 | return nil 188 | } 189 | 190 | func parsePaymentProperty(config *Config, property *PaymentProperty) error { 191 | *property = PaymentProperty{ 192 | AppID: config.AppID, 193 | AppSecret: config.AppSecret, 194 | MchID: config.MchID, 195 | Key: config.MchKey, 196 | } 197 | return nil 198 | } 199 | 200 | func parseSafeCertProperty(config *Config, property *SafeCertProperty) error { 201 | *property = SafeCertProperty{ 202 | Cert: config.PemCert, 203 | Key: config.PemKEY, 204 | RootCA: config.RootCA, 205 | } 206 | return nil 207 | } 208 | 209 | func parseOfficialAccountProperty(config *Config, property *OfficialAccountProperty) (e error) { 210 | *property = OfficialAccountProperty{ 211 | AppID: config.AppID, 212 | AppSecret: config.AppSecret, 213 | Token: config.Token, 214 | AesKey: config.AesKey, 215 | } 216 | return nil 217 | } 218 | 219 | func parseOAuthProperty(config *Config, property *OAuthProperty) error { 220 | *property = OAuthProperty{ 221 | Scopes: config.Scopes, 222 | RedirectURI: config.RedirectURI, 223 | } 224 | return nil 225 | } 226 | -------------------------------------------------------------------------------- /property_test.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import "testing" 4 | 5 | // TestLoadConfig ... 6 | func TestLoadConfig(t *testing.T) { 7 | config := LoadConfig("config.toml") 8 | t.Log(config) 9 | } 10 | -------------------------------------------------------------------------------- /request.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "github.com/godcong/wego/util" 7 | "github.com/json-iterator/go" 8 | "io" 9 | "net/http" 10 | "strings" 11 | ) 12 | 13 | // BodyType ... 14 | type BodyType string 15 | 16 | // BodyTypeNone ... 17 | const ( 18 | BodyTypeNone BodyType = "none" 19 | BodyTypeJSON BodyType = "json" 20 | BodyTypeXML BodyType = "xml" 21 | BodyTypeMultipart BodyType = "multipart" 22 | BodyTypeForm BodyType = "form" 23 | ) 24 | 25 | // RequestBody ... 26 | type RequestBody struct { 27 | BodyType BodyType 28 | BodyInstance interface{} 29 | RequestBuilder RequestBuilderFunc 30 | } 31 | 32 | // RequestBuilderFunc ... 33 | type RequestBuilderFunc func(method, url string, i interface{}) (*http.Request, error) 34 | 35 | //TODO 36 | var buildMultipart = buildNothing 37 | var buildForm = buildNothing 38 | 39 | var builder = map[BodyType]RequestBuilderFunc{ 40 | BodyTypeXML: buildXML, 41 | BodyTypeJSON: buildJSON, 42 | BodyTypeForm: buildForm, 43 | BodyTypeMultipart: buildMultipart, 44 | BodyTypeNone: buildNothing, 45 | } 46 | 47 | func buildXML(method, url string, i interface{}) (*http.Request, error) { 48 | request, e := http.NewRequest(method, url, xmlReader(i)) 49 | if e != nil { 50 | return nil, e 51 | } 52 | request.Header.Set("Content-Type", "application/json; charset=utf-8") 53 | return request, nil 54 | } 55 | 56 | // xmlReader ... 57 | func xmlReader(v interface{}) io.Reader { 58 | var reader io.Reader 59 | switch v := v.(type) { 60 | case string: 61 | log.Debug("toXMLReader|string", v) 62 | reader = strings.NewReader(v) 63 | case []byte: 64 | log.Debug("toXMLReader|[]byte", v) 65 | reader = bytes.NewReader(v) 66 | case util.Map: 67 | log.Debug("toXMLReader|util.Map", string(v.ToXML())) 68 | reader = bytes.NewReader(v.ToXML()) 69 | case io.Reader: 70 | log.Debug("toXMLReader|io.Reader") 71 | return v 72 | default: 73 | log.Debug("toXMLReader|default", v) 74 | if v0, e := xml.Marshal(v); e == nil { 75 | log.Debug("toXMLReader|v0", v0, e) 76 | reader = bytes.NewReader(v0) 77 | } 78 | } 79 | return reader 80 | } 81 | 82 | func buildJSON(method, url string, i interface{}) (*http.Request, error) { 83 | request, e := http.NewRequest(method, url, jsonReader(i)) 84 | if e != nil { 85 | return nil, e 86 | } 87 | request.Header.Set("Content-Type", "application/json; charset=utf-8") 88 | return request, nil 89 | } 90 | 91 | func buildNothing(method, url string, i interface{}) (*http.Request, error) { 92 | request, e := http.NewRequest(method, url, nil) 93 | if e != nil { 94 | return nil, e 95 | } 96 | return request, nil 97 | } 98 | 99 | // jsonReader ... 100 | func jsonReader(v interface{}) io.Reader { 101 | var reader io.Reader 102 | switch v := v.(type) { 103 | case string: 104 | log.Debug("jsonReader|string", v) 105 | reader = strings.NewReader(v) 106 | case []byte: 107 | log.Debug("jsonReader|[]byte", string(v)) 108 | reader = bytes.NewReader(v) 109 | case util.Map: 110 | log.Debug("jsonReader|util.Map", v.String()) 111 | reader = bytes.NewReader(v.ToJSON()) 112 | case io.Reader: 113 | log.Debug("jsonReader|io.Reader") 114 | return v 115 | default: 116 | log.Debug("jsonReader|default", v) 117 | if v0, e := jsoniter.Marshal(v); e == nil { 118 | reader = bytes.NewReader(v0) 119 | } 120 | } 121 | return reader 122 | } 123 | 124 | // buildBody ... 125 | func buildBody(v interface{}, tp BodyType) *RequestBody { 126 | build, b := builder[tp] 127 | if !b { 128 | build = buildNothing 129 | } 130 | return &RequestBody{ 131 | BodyType: tp, 132 | RequestBuilder: build, 133 | BodyInstance: v, 134 | } 135 | } 136 | 137 | /*Requester Requester */ 138 | type Requester interface { 139 | Type() BodyType 140 | BodyReader 141 | } 142 | 143 | // Request ... 144 | type Request struct { 145 | bytes []byte 146 | err error 147 | } 148 | 149 | // Type ... 150 | func (r *Request) Type() BodyType { 151 | return BodyTypeNone 152 | } 153 | 154 | // xmlResponse ... 155 | type xmlRequest struct { 156 | Request 157 | data util.Map 158 | } 159 | 160 | // Type ... 161 | func (r *xmlRequest) Type() BodyType { 162 | return BodyTypeXML 163 | } 164 | 165 | // XMLRequest ... 166 | func XMLRequest(bytes []byte) Requester { 167 | return &xmlRequest{ 168 | Request: Request{ 169 | bytes: bytes, 170 | }, 171 | } 172 | } 173 | 174 | // ToMap ... 175 | func (r *xmlRequest) ToMap() util.Map { 176 | maps, e := r.Result() 177 | if e != nil { 178 | return nil 179 | } 180 | return maps 181 | } 182 | 183 | // Unmarshal ... 184 | func (r *xmlRequest) Unmarshal(v interface{}) error { 185 | return xml.Unmarshal(r.bytes, v) 186 | } 187 | 188 | // Result ... 189 | func (r *xmlRequest) Result() (util.Map, error) { 190 | if r.data != nil { 191 | return r.data, nil 192 | } 193 | r.data = make(util.Map) 194 | e := r.Unmarshal(&r.data) 195 | return r.data, e 196 | } 197 | 198 | // jsonResponse ... 199 | type jsonRequest struct { 200 | Request 201 | data util.Map 202 | } 203 | 204 | // Type ... 205 | func (r *jsonRequest) Type() BodyType { 206 | return BodyTypeJSON 207 | } 208 | 209 | // JSONRequest ... 210 | func JSONRequest(bytes []byte) Requester { 211 | return &jsonRequest{ 212 | Request: Request{ 213 | bytes: bytes, 214 | }, 215 | } 216 | } 217 | 218 | // ToMap ... 219 | func (r *jsonRequest) ToMap() util.Map { 220 | maps, e := r.Result() 221 | if e != nil { 222 | return nil 223 | } 224 | return maps 225 | } 226 | 227 | // Unmarshal ... 228 | func (r *jsonRequest) Unmarshal(v interface{}) error { 229 | return jsoniter.Unmarshal(r.bytes, v) 230 | } 231 | 232 | // Result ... 233 | func (r *jsonRequest) Result() (util.Map, error) { 234 | r.data = make(util.Map) 235 | e := r.Unmarshal(&r.data) 236 | return r.data, e 237 | } 238 | 239 | // ToMap ... 240 | func (r *Request) ToMap() util.Map { 241 | return nil 242 | } 243 | 244 | // Unmarshal ... 245 | func (r *Request) Unmarshal(v interface{}) error { 246 | return r.err 247 | } 248 | 249 | // Result ... 250 | func (r *Request) Result() (util.Map, error) { 251 | return nil, r.err 252 | } 253 | 254 | // ErrRequest ... 255 | func ErrRequest(err error) Requester { 256 | return &Request{ 257 | bytes: nil, 258 | err: err, 259 | } 260 | } 261 | 262 | // Bytes ... 263 | func (r *Request) Bytes() []byte { 264 | return r.bytes 265 | } 266 | 267 | // Error ... 268 | func (r *Request) Error() error { 269 | return r.err 270 | } 271 | 272 | // BuildRequester ... 273 | func BuildRequester(req *http.Request) Requester { 274 | ct := req.Header.Get("Content-Type") 275 | body, err := readBody(req.Body) 276 | if err != nil { 277 | log.Error(body, err) 278 | return ErrRequest(err) 279 | } 280 | 281 | log.Debug("request:", string(body[:128]), len(body)) //max 128 char 282 | if strings.Index(ct, "xml") != -1 || 283 | bytes.Index(body, []byte("` 26 | 27 | /*CDATA xml cdata defines */ 28 | type CDATA struct { 29 | XMLName xml.Name 30 | Value string `xml:",cdata"` 31 | } 32 | 33 | /* error types */ 34 | var ( 35 | ErrorSignType = errors.New("sign type error") 36 | ErrorParameter = errors.New("JsonApiParameters() check error") 37 | ErrorToken = errors.New("EditAddressParameters() token is nil") 38 | ) 39 | 40 | /*RandomKind RandomKind */ 41 | type RandomKind int 42 | 43 | /*random kinds */ 44 | const ( 45 | RandomNum RandomKind = iota // 纯数字 46 | RandomLower // 小写字母 47 | RandomUpper // 大写字母 48 | RandomLowerNum // 数字、小写字母 49 | RandomUpperNum // 数字、大写字母 50 | RandomAll // 数字、大小写字母 51 | ) 52 | 53 | /*RandomString defines */ 54 | var ( 55 | RandomString = map[RandomKind]string{ 56 | RandomNum: "0123456789", 57 | RandomLower: "abcdefghijklmnopqrstuvwxyz", 58 | RandomUpper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 59 | RandomLowerNum: "0123456789abcdefghijklmnopqrstuvwxyz", 60 | RandomUpperNum: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", 61 | RandomAll: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 62 | } 63 | ) 64 | 65 | /*ParseNumber parse interface to number */ 66 | func ParseNumber(v interface{}) (float64, bool) { 67 | switch v0 := v.(type) { 68 | case float64: 69 | return v0, true 70 | case float32: 71 | return float64(v0), true 72 | } 73 | return 0, false 74 | } 75 | 76 | /*ParseInt parse interface to int64 */ 77 | func ParseInt(v interface{}) (int64, bool) { 78 | switch v0 := v.(type) { 79 | case int: 80 | return int64(v0), true 81 | case int32: 82 | return int64(v0), true 83 | case int64: 84 | return int64(v0), true 85 | case uint: 86 | return int64(v0), true 87 | case uint32: 88 | return int64(v0), true 89 | case uint64: 90 | return int64(v0), true 91 | case float64: 92 | return int64(v0), true 93 | case float32: 94 | return int64(v0), true 95 | default: 96 | } 97 | return 0, false 98 | } 99 | 100 | /*ParseString parse interface to string */ 101 | func ParseString(v interface{}) (string, bool) { 102 | switch v0 := v.(type) { 103 | case string: 104 | return v0, true 105 | case []byte: 106 | return string(v0), true 107 | case bytes.Buffer: 108 | return v0.String(), true 109 | default: 110 | } 111 | return "", false 112 | } 113 | 114 | /*Time get time string */ 115 | func Time(t ...time.Time) string { 116 | if t == nil { 117 | return strconv.Itoa(time.Now().Nanosecond()) 118 | } 119 | return strconv.Itoa(t[0].Nanosecond()) 120 | } 121 | 122 | /*GenerateNonceStr GenerateNonceStr */ 123 | func GenerateNonceStr() string { 124 | return GenerateUUID() 125 | } 126 | 127 | /*GenerateUUID GenerateUUID */ 128 | func GenerateUUID() string { 129 | s := uuid.NewV1().String() 130 | s = strings.Replace(s, "-", "", -1) 131 | run := ([]rune)(s)[:32] 132 | return string(run) 133 | } 134 | 135 | /*In check v is in source */ 136 | func In(source []string, v string) bool { 137 | size := len(source) 138 | for i := 0; i < size; i++ { 139 | if source[i] == v { 140 | return true 141 | } 142 | } 143 | 144 | return false 145 | } 146 | 147 | // CurrentTimeStampMS get current time with millisecond 148 | func CurrentTimeStampMS() int64 { 149 | return time.Now().UnixNano() / time.Millisecond.Nanoseconds() 150 | } 151 | 152 | // CurrentTimeStampNS get current time with nanoseconds 153 | func CurrentTimeStampNS() int64 { 154 | return time.Now().UnixNano() 155 | } 156 | 157 | // CurrentTimeStamp get current time with unix 158 | func CurrentTimeStamp() int64 { 159 | return time.Now().Unix() 160 | } 161 | 162 | // CurrentTimeStampString get current time to string 163 | func CurrentTimeStampString() string { 164 | return strconv.FormatInt(CurrentTimeStamp(), 10) 165 | } 166 | 167 | // GenSHA1 transfer strings to sha1 168 | func GenSHA1(text ...string) string { 169 | sort.Strings(text) 170 | s := strings.Join(text, "") 171 | return fmt.Sprintf("%x", sha1.Sum([]byte(s))) 172 | } 173 | 174 | // GenCRC32 ... 175 | func GenCRC32(data string) string { 176 | ieee := crc32.NewIEEE() 177 | _, _ = io.WriteString(ieee, data) 178 | return fmt.Sprintf("%X", ieee.Sum(nil)) 179 | } 180 | 181 | // GenMD5 transfer strings to md5 182 | func GenMD5(data string) string { 183 | m := md5.New() 184 | _, _ = io.WriteString(m, data) 185 | return fmt.Sprintf("%x", m.Sum(nil)) 186 | } 187 | 188 | // GenSHA256 ... 189 | func GenSHA256(data []byte, key string) string { 190 | m := hmac.New(sha256.New, []byte(key)) 191 | m.Write(data) 192 | return strings.ToUpper(fmt.Sprintf("%x", m.Sum(nil))) 193 | } 194 | 195 | func signatureSHA1(m Map) string { 196 | keys := m.SortKeys() 197 | var sign []string 198 | size := len(keys) 199 | for i := 0; i < size; i++ { 200 | if v := strings.TrimSpace(m.GetString(keys[i])); v != "" { 201 | log.Debug(keys[i], v) 202 | sign = append(sign, strings.Join([]string{keys[i], v}, "=")) 203 | } else if v, b := m.GetInt64(keys[i]); b { 204 | log.Debug(keys[i], v) 205 | sign = append(sign, strings.Join([]string{keys[i], strconv.FormatInt(v, 10)}, "=")) 206 | } 207 | } 208 | 209 | sb := strings.Join(sign, "&") 210 | return GenSHA1(sb) 211 | } 212 | 213 | //GenerateRandomString2 随机字符串 214 | func GenerateRandomString2(size int, kind int) []byte { 215 | ikind, kinds, result := kind, [][]int{{10, 48}, {26, 97}, {26, 65}}, make([]byte, size) 216 | isAll := kind > 2 || kind < 0 217 | 218 | rand.Seed(time.Now().UnixNano()) 219 | for i := 0; i < size; i++ { 220 | if isAll { // random ikind 221 | ikind = rand.Intn(3) 222 | } 223 | scope, base := kinds[ikind][0], kinds[ikind][1] 224 | result[i] = uint8(base + rand.Intn(scope)) 225 | } 226 | return result 227 | } 228 | 229 | //GenerateRandomString 随机字符串 230 | func GenerateRandomString(size int, kind ...RandomKind) string { 231 | s := RandomString[RandomAll] 232 | if kind != nil { 233 | if k, b := RandomString[kind[0]]; b == true { 234 | s = k 235 | } 236 | } 237 | var result []byte 238 | rand.Seed(time.Now().UnixNano()) 239 | lens := len(s) 240 | for i := 0; i < size; i++ { 241 | result = append(result, s[rand.Intn(lens)]) 242 | } 243 | return string(result) 244 | } 245 | 246 | // MustInt64 ... 247 | func MustInt64(src, def int64) int64 { 248 | if src != 0 { 249 | return src 250 | } 251 | return def 252 | } 253 | 254 | /*MapSortSplice MapSortSplice */ 255 | func MapSortSplice(data Map, ignore []string) string { 256 | var sign []string 257 | m := data.Expect(ignore) 258 | keys := m.SortKeys() 259 | size := len(keys) 260 | 261 | for i := 0; i < size; i++ { 262 | v := strings.TrimSpace(m.GetString(keys[i])) 263 | if len(v) > 0 { 264 | sign = append(sign, strings.Join([]string{keys[i], v}, "=")) 265 | } 266 | } 267 | 268 | log.Debug(strings.Join(sign, "&")) 269 | return strings.Join(sign, "&") 270 | } 271 | 272 | /*ToURLParams map to url params */ 273 | func ToURLParams(data Map, ignore []string) string { 274 | var sign []string 275 | m := data.Expect(ignore) 276 | keys := m.SortKeys() 277 | size := len(keys) 278 | for i := 0; i < size; i++ { 279 | v := strings.TrimSpace(m.GetString(keys[i])) 280 | if len(v) > 0 { 281 | sign = append(sign, strings.Join([]string{keys[i], v}, "=")) 282 | } 283 | } 284 | return strings.Join(sign, "&") 285 | } 286 | 287 | // AnyToMap convert interface to map 288 | func AnyToMap(v interface{}) (Map, error) { 289 | m := Map{} 290 | b, e := jsoniter.Marshal(v) 291 | if e != nil { 292 | return m, e 293 | } 294 | e = jsoniter.Unmarshal(b, &m) 295 | return m, e 296 | } 297 | 298 | /*XMLToMap Convert XML to MAP */ 299 | func XMLToMap(xml []byte) Map { 300 | m, err := xmlToMap(xml, false) 301 | if err != nil { 302 | return nil 303 | } 304 | return m 305 | } 306 | 307 | /*MapToXML Convert MAP to XML */ 308 | func MapToXML(m Map) ([]byte, error) { 309 | return mapToXML(m, false) 310 | } 311 | 312 | /*JSONToMap Convert JSON to MAP */ 313 | func JSONToMap(xml []byte) Map { 314 | m := Map{} 315 | err := jsoniter.Unmarshal(xml, &m) 316 | if err != nil { 317 | log.Error(err) 318 | } 319 | return m 320 | } 321 | 322 | func mapToXML(maps Map, needHeader bool) ([]byte, error) { 323 | 324 | buff := bytes.NewBuffer([]byte(CustomHeader)) 325 | if needHeader { 326 | buff.Write([]byte(xml.Header)) 327 | } 328 | 329 | enc := xml.NewEncoder(buff) 330 | err := marshalXML(maps, enc, xml.StartElement{Name: xml.Name{Local: "xml"}}) 331 | if err != nil { 332 | return nil, err 333 | } 334 | err = enc.Flush() 335 | if err != nil { 336 | return nil, err 337 | } 338 | return buff.Bytes(), nil 339 | } 340 | func xmlToMap(contentXML []byte, hasHeader bool) (Map, error) { 341 | m := make(Map) 342 | dec := xml.NewDecoder(bytes.NewReader(contentXML)) 343 | err := unmarshalXML(m, dec, xml.StartElement{Name: xml.Name{Local: "xml"}}, true) 344 | if err != nil { 345 | return nil, xerrors.Errorf("xml to map:%w", err) 346 | } 347 | 348 | return m, nil 349 | } 350 | 351 | // MustString ... 352 | func MustString(v, def string) string { 353 | if v == "" { 354 | return def 355 | } 356 | return v 357 | } 358 | 359 | // MustInt ... 360 | func MustInt(v string, def int) int { 361 | i, err := strconv.Atoi(v) 362 | if err == nil { 363 | return i 364 | } 365 | return def 366 | } 367 | -------------------------------------------------------------------------------- /util/function_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // BenchmarkGenerateRandomString ... 8 | func BenchmarkGenerateRandomString(b *testing.B) { 9 | 10 | b.Run("s1", func(b *testing.B) { 11 | 12 | b.ResetTimer() 13 | for i := 0; i < 100000; i++ { 14 | str := GenerateRandomString(64) 15 | _ = str 16 | } 17 | }) 18 | 19 | b.Run("s2", func(b *testing.B) { 20 | b.ResetTimer() 21 | for i := 0; i < 100000; i++ { 22 | str := GenerateRandomString2(64, -1) 23 | _ = str 24 | } 25 | }) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /util/log.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/godcong/go-trait" 5 | ) 6 | 7 | var log = trait.ZapSugar() 8 | -------------------------------------------------------------------------------- /util/map.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "errors" 7 | jsoniter "github.com/json-iterator/go" 8 | "net/url" 9 | "sort" 10 | "strconv" 11 | "strings" 12 | ) 13 | 14 | // MapAble ... 15 | type MapAble interface { 16 | ToMap() Map 17 | } 18 | 19 | // XMLAble ... 20 | type XMLAble interface { 21 | ToXML() []byte 22 | } 23 | 24 | // JSONAble ... 25 | type JSONAble interface { 26 | ToJSON() []byte 27 | } 28 | 29 | // ErrNilMap ... 30 | var ErrNilMap = errors.New("nil map") 31 | 32 | /*StringAble StringAble */ 33 | type StringAble interface { 34 | String() string 35 | } 36 | 37 | /*String String */ 38 | type String string 39 | 40 | /*String String */ 41 | func (s String) String() string { 42 | return string(s) 43 | } 44 | 45 | /*ToString ToString */ 46 | func ToString(s string) String { 47 | return String(s) 48 | } 49 | 50 | // Map ... 51 | type Map map[string]interface{} 52 | 53 | /*String transfer map to JSON string */ 54 | func (m Map) String() string { 55 | return string(m.ToJSON()) 56 | } 57 | 58 | // StructToMap ... 59 | func StructToMap(s interface{}, p Map) error { 60 | var err error 61 | buf := bytes.NewBuffer(nil) 62 | enc := jsoniter.NewEncoder(buf) 63 | err = enc.Encode(s) 64 | if err != nil { 65 | return err 66 | } 67 | dec := jsoniter.NewDecoder(buf) 68 | err = dec.Decode(&p) 69 | if err != nil { 70 | return err 71 | } 72 | return nil 73 | } 74 | 75 | /*MapMake make new map only if m is nil result a new map with nothing */ 76 | func MapMake(m Map) Map { 77 | if m == nil { 78 | return make(Map) 79 | } 80 | return m 81 | } 82 | 83 | /*ToMap transfer to map[string]interface{} or MapAble to GMap */ 84 | func ToMap(p interface{}) Map { 85 | switch v := p.(type) { 86 | case map[string]interface{}: 87 | return Map(v) 88 | case MapAble: 89 | return v.ToMap() 90 | } 91 | return nil 92 | } 93 | 94 | // CombineMaps ... 95 | func CombineMaps(p Map, m ...Map) Map { 96 | if p == nil { 97 | p = make(Map) 98 | } 99 | if m == nil { 100 | return p 101 | } 102 | 103 | for _, v := range m { 104 | p.join(v, true) 105 | } 106 | return p 107 | } 108 | 109 | /*Set set interface */ 110 | func (m Map) Set(key string, v interface{}) Map { 111 | return m.SetPath(strings.Split(key, "."), v) 112 | } 113 | 114 | // SetPath is the same as SetPath, but allows you to provide comment 115 | // information to the key, that will be reused by Marshal(). 116 | func (m Map) SetPath(keys []string, v interface{}) Map { 117 | subtree := m 118 | for _, intermediateKey := range keys[:len(keys)-1] { 119 | nextTree, exists := subtree[intermediateKey] 120 | if !exists { 121 | nextTree = make(Map) 122 | subtree[intermediateKey] = nextTree // add new element here 123 | } 124 | switch node := nextTree.(type) { 125 | case Map: 126 | subtree = node 127 | case []Map: 128 | // go to most recent element 129 | if len(node) == 0 { 130 | // create element if it does not exist 131 | subtree[intermediateKey] = append(node, make(Map)) 132 | } 133 | subtree = node[len(node)-1] 134 | } 135 | } 136 | subtree[keys[len(keys)-1]] = v 137 | return m 138 | } 139 | 140 | /*SetNil set interface if key is not exist */ 141 | func (m Map) SetNil(s string, v interface{}) Map { 142 | if !m.Has(s) { 143 | m.Set(s, v) 144 | } 145 | return m 146 | } 147 | 148 | /*SetHas set interface if key is exist */ 149 | func (m Map) SetHas(s string, v interface{}) Map { 150 | if m.Has(s) { 151 | m.Set(s, v) 152 | } 153 | return m 154 | } 155 | 156 | /*SetGet set value from map if key is exist */ 157 | func (m Map) SetGet(s string, v Map) Map { 158 | if v.Has(s) { 159 | m.Set(s, v[s]) 160 | } 161 | return m 162 | } 163 | 164 | /*Get get interface from map with out default */ 165 | func (m Map) Get(key string) interface{} { 166 | if key == "" { 167 | return nil 168 | } 169 | return m.GetPath(strings.Split(key, ".")) 170 | } 171 | 172 | /*GetD get interface from map with default */ 173 | func (m Map) GetD(s string, d interface{}) interface{} { 174 | if v := m.Get(s); v != nil { 175 | return v 176 | } 177 | return d 178 | } 179 | 180 | /*GetMap get map from map with out default */ 181 | func (m Map) GetMap(s string) Map { 182 | switch v := m.Get(s).(type) { 183 | case map[string]interface{}: 184 | return (Map)(v) 185 | case Map: 186 | return v 187 | default: 188 | return nil 189 | } 190 | } 191 | 192 | /*GetMapD get map from map with default */ 193 | func (m Map) GetMapD(s string, d Map) Map { 194 | if v := m.GetMap(s); v != nil { 195 | return v 196 | } 197 | return d 198 | } 199 | 200 | /*GetMapArray get map from map with out default */ 201 | func (m Map) GetMapArray(s string) []Map { 202 | switch v := m.Get(s).(type) { 203 | case []Map: 204 | return v 205 | case []map[string]interface{}: 206 | var sub []Map 207 | for _, mp := range v { 208 | sub = append(sub, (Map)(mp)) 209 | } 210 | return sub 211 | default: 212 | return nil 213 | } 214 | } 215 | 216 | /*GetMapArrayD get map from map with default */ 217 | func (m Map) GetMapArrayD(s string, d []Map) []Map { 218 | if v := m.GetMapArray(s); v != nil { 219 | return v 220 | } 221 | return d 222 | } 223 | 224 | // GetArray ... 225 | func (m Map) GetArray(s string) []interface{} { 226 | switch v := m.Get(s).(type) { 227 | case []interface{}: 228 | return v 229 | default: 230 | return nil 231 | } 232 | } 233 | 234 | // GetArrayD ... 235 | func (m Map) GetArrayD(s string, d []interface{}) []interface{} { 236 | switch v := m.Get(s).(type) { 237 | case []interface{}: 238 | return v 239 | default: 240 | return d 241 | } 242 | } 243 | 244 | /*GetBool get bool from map with out default */ 245 | func (m Map) GetBool(s string) bool { 246 | return m.GetBoolD(s, false) 247 | } 248 | 249 | /*GetBoolD get bool from map with default */ 250 | func (m Map) GetBoolD(s string, b bool) bool { 251 | if v, b := m.Get(s).(bool); b { 252 | return v 253 | } 254 | return b 255 | } 256 | 257 | /*GetNumber get float64 from map with out default */ 258 | func (m Map) GetNumber(s string) (float64, bool) { 259 | return ParseNumber(m.Get(s)) 260 | } 261 | 262 | /*GetNumberD get float64 from map with default */ 263 | func (m Map) GetNumberD(s string, d float64) float64 { 264 | n, b := ParseNumber(m.Get(s)) 265 | if b { 266 | return n 267 | } 268 | return d 269 | } 270 | 271 | /*GetInt64 get int64 from map with out default */ 272 | func (m Map) GetInt64(s string) (int64, bool) { 273 | return ParseInt(m.Get(s)) 274 | } 275 | 276 | /*GetInt64D get int64 from map with default */ 277 | func (m Map) GetInt64D(s string, d int64) int64 { 278 | i, b := ParseInt(m.Get(s)) 279 | if b { 280 | return i 281 | } 282 | return d 283 | } 284 | 285 | /*GetString get string from map with out default */ 286 | func (m Map) GetString(s string) string { 287 | if v, b := m.Get(s).(string); b { 288 | return v 289 | } 290 | return "" 291 | } 292 | 293 | /*GetStringD get string from map with default */ 294 | func (m Map) GetStringD(s string, d string) string { 295 | if v, b := m.Get(s).(string); b { 296 | return v 297 | } 298 | return d 299 | } 300 | 301 | /*GetBytes get bytes from map with default */ 302 | func (m Map) GetBytes(s string) []byte { 303 | if v, b := m.Get(s).([]byte); b { 304 | return v 305 | } 306 | return []byte(nil) 307 | } 308 | 309 | /*Delete delete if exist */ 310 | func (m Map) Delete(key string) bool { 311 | if key == "" { 312 | return false 313 | } 314 | return m.DeletePath(strings.Split(key, ".")) 315 | } 316 | 317 | // DeletePath ... 318 | func (m Map) DeletePath(keys []string) bool { 319 | if len(keys) == 0 { 320 | return false 321 | } 322 | subtree := m 323 | for _, intermediateKey := range keys[:len(keys)-1] { 324 | value, exists := subtree[intermediateKey] 325 | if !exists { 326 | return false 327 | } 328 | switch node := value.(type) { 329 | case Map: 330 | subtree = node 331 | case []Map: 332 | if len(node) == 0 { 333 | return false 334 | } 335 | subtree = node[len(node)-1] 336 | default: 337 | return false // cannot navigate through other node types 338 | } 339 | } 340 | // branch based on final node type 341 | if _, b := subtree[keys[len(keys)-1]]; !b { 342 | return false 343 | } 344 | delete(subtree, keys[len(keys)-1]) 345 | return true 346 | } 347 | 348 | /*Has check if key exist */ 349 | func (m Map) Has(key string) bool { 350 | if key == "" { 351 | return false 352 | } 353 | return m.HasPath(strings.Split(key, ".")) 354 | 355 | } 356 | 357 | // HasPath returns true if the given path of keys exists, false otherwise. 358 | func (m Map) HasPath(keys []string) bool { 359 | return m.GetPath(keys) != nil 360 | } 361 | 362 | // GetPath returns the element in the tree indicated by 'keys'. 363 | // If keys is of length zero, the current tree is returned. 364 | func (m Map) GetPath(keys []string) interface{} { 365 | if len(keys) == 0 { 366 | return m 367 | } 368 | subtree := m 369 | for _, intermediateKey := range keys[:len(keys)-1] { 370 | value, exists := subtree[intermediateKey] 371 | if !exists { 372 | return nil 373 | } 374 | switch node := value.(type) { 375 | case Map: 376 | subtree = node 377 | case []Map: 378 | if len(node) == 0 { 379 | return nil 380 | } 381 | subtree = node[len(node)-1] 382 | default: 383 | return nil // cannot navigate through other node types 384 | } 385 | } 386 | // branch based on final node type 387 | return subtree[keys[len(keys)-1]] 388 | } 389 | 390 | /*SortKeys 排列key */ 391 | func (m Map) SortKeys() []string { 392 | var keys sort.StringSlice 393 | for k := range m { 394 | keys = append(keys, k) 395 | } 396 | sort.Sort(keys) 397 | return keys 398 | } 399 | 400 | /*ToXML transfer map to XML */ 401 | func (m Map) ToXML() []byte { 402 | v, e := xml.Marshal(&m) 403 | if e != nil { 404 | log.Error(e) 405 | return []byte(nil) 406 | } 407 | return v 408 | } 409 | 410 | /*ParseXML parse XML bytes to map */ 411 | func (m Map) ParseXML(b []byte) { 412 | m.Join(XMLToMap(b)) 413 | } 414 | 415 | /*ToJSON transfer map to JSON */ 416 | func (m Map) ToJSON() []byte { 417 | v, e := jsoniter.Marshal(m) 418 | if e != nil { 419 | log.Error(e) 420 | return []byte(nil) 421 | } 422 | return v 423 | } 424 | 425 | /*ParseJSON parse JSON bytes to map */ 426 | func (m Map) ParseJSON(b []byte) Map { 427 | tmp := Map{} 428 | if e := jsoniter.Unmarshal(b, &tmp); e == nil { 429 | m.Join(tmp) 430 | } 431 | return m 432 | } 433 | 434 | // URLValues ... 435 | func (m Map) URLValues() url.Values { 436 | val := url.Values{} 437 | for key, value := range m { 438 | m.Set(key, value) 439 | } 440 | return val 441 | } 442 | 443 | /*URLEncode transfer map to url encode */ 444 | func (m Map) URLEncode() string { 445 | var buf strings.Builder 446 | keys := m.SortKeys() 447 | size := len(keys) 448 | for i := 0; i < size; i++ { 449 | vs := m[keys[i]] 450 | keyEscaped := url.QueryEscape(keys[i]) 451 | switch val := vs.(type) { 452 | case string: 453 | if buf.Len() > 0 { 454 | buf.WriteByte('&') 455 | } 456 | buf.WriteString(keyEscaped) 457 | buf.WriteByte('=') 458 | buf.WriteString(url.QueryEscape(val)) 459 | case []string: 460 | for _, v := range val { 461 | if buf.Len() > 0 { 462 | buf.WriteByte('&') 463 | } 464 | buf.WriteString(keyEscaped) 465 | buf.WriteByte('=') 466 | buf.WriteString(url.QueryEscape(v)) 467 | } 468 | } 469 | } 470 | 471 | return buf.String() 472 | } 473 | 474 | func (m Map) join(source Map, replace bool) Map { 475 | for k, v := range source { 476 | if replace || !m.Has(k) { 477 | m.Set(k, v) 478 | } 479 | } 480 | return m 481 | } 482 | 483 | // Append ... 484 | func (m Map) Append(p Map) Map { 485 | for k, v := range p { 486 | if m.Has(k) { 487 | m.Set(k, []interface{}{m.Get(k), v}) 488 | } else { 489 | m.Set(k, v) 490 | } 491 | } 492 | return m 493 | } 494 | 495 | /*ReplaceJoin insert map s to m with replace */ 496 | func (m Map) ReplaceJoin(s Map) Map { 497 | return m.join(s, true) 498 | } 499 | 500 | /*Join insert map s to m with out replace */ 501 | func (m Map) Join(s Map) Map { 502 | return m.join(s, false) 503 | } 504 | 505 | /*Only get map with keys */ 506 | func (m Map) Only(keys []string) Map { 507 | p := Map{} 508 | size := len(keys) 509 | for i := 0; i < size; i++ { 510 | p.Set(keys[i], m.Get(keys[i])) 511 | } 512 | 513 | return p 514 | } 515 | 516 | /*Expect get map expect keys */ 517 | func (m Map) Expect(keys []string) Map { 518 | p := m.Clone() 519 | size := len(keys) 520 | for i := 0; i < size; i++ { 521 | p.Delete(keys[i]) 522 | } 523 | 524 | return p 525 | } 526 | 527 | /*Clone copy a map */ 528 | func (m Map) Clone() Map { 529 | v := deepCopy(m) 530 | return (v).(Map) 531 | } 532 | 533 | func deepCopy(value interface{}) interface{} { 534 | if valueMap, ok := value.(Map); ok { 535 | newMap := make(Map) 536 | for k, v := range valueMap { 537 | newMap[k] = deepCopy(v) 538 | } 539 | return newMap 540 | } else if valueSlice, ok := value.([]Map); ok { 541 | newSlice := make([]interface{}, len(valueSlice)) 542 | for k, v := range valueSlice { 543 | newSlice[k] = deepCopy(v) 544 | } 545 | return newSlice 546 | } 547 | 548 | return value 549 | } 550 | 551 | /*SignatureSHA1 make sha1 from map */ 552 | func (m Map) SignatureSHA1() string { 553 | return signatureSHA1(m) 554 | } 555 | 556 | //Range range all maps 557 | func (m Map) Range(f func(key string, value interface{}) bool) { 558 | for k, v := range m { 559 | if !f(k, v) { 560 | return 561 | } 562 | } 563 | } 564 | 565 | //Check check all input keys 566 | //return -1 if all is exist 567 | //return index when not found 568 | func (m Map) Check(s ...string) int { 569 | size := len(s) 570 | for i := 0; i < size; i++ { 571 | if !m.Has(s[i]) { 572 | return i 573 | } 574 | } 575 | 576 | return -1 577 | } 578 | 579 | // GoMap trans return a map[string]interface from Map 580 | func (m Map) GoMap() map[string]interface{} { 581 | return (map[string]interface{})(m) 582 | } 583 | 584 | // ToMap implements MapAble 585 | func (m Map) ToMap() Map { 586 | return m 587 | } 588 | 589 | // MarshalXML ... 590 | func (m Map) MarshalXML(e *xml.Encoder, start xml.StartElement) error { 591 | if len(m) == 0 { 592 | return ErrNilMap 593 | } 594 | if start.Name.Local == "root" { 595 | return marshalXML(m, e, xml.StartElement{Name: xml.Name{Local: "root"}}) 596 | } 597 | return marshalXML(m, e, xml.StartElement{Name: xml.Name{Local: "xml"}}) 598 | } 599 | 600 | // UnmarshalXML ... 601 | func (m Map) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 602 | if start.Name.Local == "root" { 603 | return unmarshalXML(m, d, xml.StartElement{Name: xml.Name{Local: "root"}}, false) 604 | } 605 | return unmarshalXML(m, d, xml.StartElement{Name: xml.Name{Local: "xml"}}, false) 606 | } 607 | 608 | func marshalXML(maps Map, e *xml.Encoder, start xml.StartElement) error { 609 | if maps == nil { 610 | return errors.New("map is nil") 611 | } 612 | err := e.EncodeToken(start) 613 | if err != nil { 614 | return err 615 | } 616 | for k, v := range maps { 617 | err := convertXML(k, v, e, xml.StartElement{Name: xml.Name{Local: k}}) 618 | if err != nil { 619 | return err 620 | } 621 | } 622 | return e.EncodeToken(start.End()) 623 | } 624 | 625 | func unmarshalXML(maps Map, d *xml.Decoder, start xml.StartElement, needCast bool) error { 626 | current := "" 627 | var data interface{} 628 | last := "" 629 | arrayTmp := make(Map) 630 | arrayTag := "" 631 | var ele []string 632 | 633 | for t, err := d.Token(); err == nil; t, err = d.Token() { 634 | switch token := t.(type) { 635 | // 处理元素开始(标签) 636 | case xml.StartElement: 637 | if strings.ToLower(token.Name.Local) == "xml" || 638 | strings.ToLower(token.Name.Local) == "root" { 639 | continue 640 | } 641 | ele = append(ele, token.Name.Local) 642 | current = strings.Join(ele, ".") 643 | //log.Debug("EndElement", current) 644 | //log.Debug("EndElement", last) 645 | //log.Debug("EndElement", arrayTag) 646 | if current == last { 647 | arrayTag = current 648 | tmp := maps.Get(arrayTag) 649 | switch tmp.(type) { 650 | case []interface{}: 651 | arrayTmp.Set(arrayTag, tmp) 652 | default: 653 | arrayTmp.Set(arrayTag, []interface{}{tmp}) 654 | } 655 | maps.Delete(arrayTag) 656 | } 657 | log.Debug("StartElement", ele) 658 | // 处理元素结束(标签) 659 | case xml.EndElement: 660 | name := token.Name.Local 661 | // fmt.Printf("This is the end: %s\n", name) 662 | if strings.ToLower(name) == "xml" || 663 | strings.ToLower(name) == "root" { 664 | break 665 | } 666 | last = strings.Join(ele, ".") 667 | //log.Debug("EndElement", current) 668 | //log.Debug("EndElement", last) 669 | //log.Debug("EndElement", arrayTag) 670 | 671 | if current == last { 672 | if data != nil { 673 | log.Debug("CharData", data) 674 | maps.Set(current, data) 675 | } else { 676 | //m.Set(current, nil) 677 | } 678 | data = nil 679 | } 680 | if last == arrayTag { 681 | arr := arrayTmp.GetArray(arrayTag) 682 | if arr != nil { 683 | if v := maps.Get(arrayTag); v != nil { 684 | maps.Set(arrayTag, append(arr, v)) 685 | } else { 686 | maps.Set(arrayTag, arr) 687 | } 688 | } else { 689 | //exception doing 690 | maps.Set(arrayTag, []interface{}{maps.Get(arrayTag)}) 691 | } 692 | arrayTmp.Delete(arrayTag) 693 | arrayTag = "" 694 | } 695 | 696 | ele = ele[:len(ele)-1] 697 | //log.Debug("EndElement", ele) 698 | // 处理字符数据(这里就是元素的文本) 699 | case xml.CharData: 700 | if needCast { 701 | data, err = strconv.Atoi(string(token)) 702 | if err == nil { 703 | continue 704 | } 705 | 706 | data, err = strconv.ParseFloat(string(token), 64) 707 | if err == nil { 708 | continue 709 | } 710 | 711 | data, err = strconv.ParseBool(string(token)) 712 | if err == nil { 713 | continue 714 | } 715 | } 716 | 717 | data = string(token) 718 | //log.Debug("CharData", data) 719 | // 异常处理(Log输出) 720 | default: 721 | log.Debug(token) 722 | } 723 | 724 | } 725 | 726 | return nil 727 | } 728 | 729 | func convertXML(k string, v interface{}, e *xml.Encoder, start xml.StartElement) error { 730 | var err error 731 | switch v1 := v.(type) { 732 | case Map: 733 | return marshalXML(v1, e, xml.StartElement{Name: xml.Name{Local: k}}) 734 | case map[string]interface{}: 735 | return marshalXML(v1, e, xml.StartElement{Name: xml.Name{Local: k}}) 736 | case string: 737 | if _, err := strconv.ParseInt(v1, 10, 0); err != nil { 738 | err = e.EncodeElement( 739 | CDATA{Value: v1}, xml.StartElement{Name: xml.Name{Local: k}}) 740 | return err 741 | } 742 | err = e.EncodeElement(v1, xml.StartElement{Name: xml.Name{Local: k}}) 743 | return err 744 | case float64: 745 | if v1 == float64(int64(v1)) { 746 | err = e.EncodeElement(int64(v1), xml.StartElement{Name: xml.Name{Local: k}}) 747 | return err 748 | } 749 | err = e.EncodeElement(v1, xml.StartElement{Name: xml.Name{Local: k}}) 750 | return err 751 | case bool: 752 | err = e.EncodeElement(v1, xml.StartElement{Name: xml.Name{Local: k}}) 753 | return err 754 | case []interface{}: 755 | size := len(v1) 756 | for i := 0; i < size; i++ { 757 | err := convertXML(k, v1[i], e, xml.StartElement{Name: xml.Name{Local: k}}) 758 | if err != nil { 759 | return err 760 | } 761 | } 762 | if len(v1) == 1 { 763 | return convertXML(k, "", e, xml.StartElement{Name: xml.Name{Local: k}}) 764 | } 765 | default: 766 | log.Error(v1) 767 | } 768 | return nil 769 | } 770 | -------------------------------------------------------------------------------- /util/map_test.go: -------------------------------------------------------------------------------- 1 | package util_test 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/godcong/wego/util" 6 | "testing" 7 | ) 8 | 9 | // TestMap_Set ... 10 | func TestMap_Set(t *testing.T) { 11 | m := make(util.Map) 12 | m.Set("one.two.three", "abc") 13 | 14 | t.Log(m.Get("one.two.three") == "abc") 15 | } 16 | 17 | // TestMap_Delete ... 18 | func TestMap_Delete(t *testing.T) { 19 | m := make(util.Map) 20 | m.Set("one.two.three", "abc") 21 | if !m.Has("one.two") { 22 | t.Error("one.two") 23 | } 24 | m.Set("one.two.ab", "ddd") 25 | if m.GetString("one.two.three") != "abc" { 26 | t.Error("one.two.three") 27 | } 28 | 29 | if !m.Delete("one.two.ab") { 30 | t.Error("one.two.ab") 31 | } 32 | } 33 | 34 | // TestMap_Expect ... 35 | func TestMap_Expect(t *testing.T) { 36 | m := make(util.Map) 37 | m.Set("one.two.three", "abc") 38 | m.Set("one.two.ab", "ddd") 39 | t.Log(m) 40 | t.Log(m.Expect([]string{"one.two.ab"})) 41 | t.Log(m) 42 | 43 | } 44 | 45 | // TestMap_UnmarshalXML ... 46 | func TestMap_UnmarshalXML(t *testing.T) { 47 | json := `{ 48 | "card": { 49 | "card_type": "GROUPON", 50 | "groupon": { 51 | "base_info": { 52 | "logo_url": "http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0", 53 | "brand_name": "微信餐厅", 54 | "code_type": "CODE_TYPE_TEXT", 55 | "title": "132元双人火锅套餐", 56 | "color": "Color010", 57 | "notice": "使用时向服务员出示此券", 58 | "service_phone": "020-88888888", 59 | "description": "不可与其他优惠同享\n如需团购券发票,请在消费时向商户提出\n店内均可使用,仅限堂食", 60 | "date_info": { 61 | "type": "DATE_TYPE_FIX_TIME_RANGE", 62 | "begin_timestamp": 1397577600, 63 | "end_timestamp": 1472724261 64 | }, 65 | "sku": { 66 | "quantity": 500000 67 | }, 68 | "use_limit": 100, 69 | "get_limit": 3, 70 | "use_custom_code": false, 71 | "bind_openid": false, 72 | "can_share": true, 73 | "can_give_friend": true, 74 | "location_id_list": [123,12321,345345], 75 | "center_title": "顶部居中按钮", 76 | "center_sub_title": "按钮下方的wording", 77 | "center_url": "www.qq.com", 78 | "custom_url_name": "立即使用", 79 | "custom_url": "http://www.qq.com", 80 | "custom_url_sub_title": "6个汉字tips", 81 | "promotion_url_name": "更多优惠", 82 | "promotion_url": "http://www.qq.com", 83 | "source": "大众点评" 84 | }, 85 | "advanced_info": { 86 | "use_condition": { 87 | "accept_category": "鞋类", 88 | "reject_category": "阿迪达斯", 89 | "can_use_with_other_discount": true 90 | }, 91 | "abstract": { 92 | "abstract": "微信餐厅推出多种新季菜品,期待您的光临", 93 | "icon_url_list": [ 94 | "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sj\n piby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0" 95 | ] 96 | }, 97 | "text_image_list": [ 98 | { 99 | "image_url": "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sjpiby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0", 100 | "text": "此菜品精选食材,以独特的烹饪方法,最大程度地刺激食 客的味蕾" 101 | }, 102 | { 103 | "image_url": "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sj piby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0", 104 | "text": "此菜品迎合大众口味,老少皆宜,营养均衡" 105 | } 106 | ], 107 | "time_limit": [ 108 | { 109 | "type": "MONDAY", 110 | "begin_hour": 0, 111 | "end_hour": 10, 112 | "begin_minute": 10, 113 | "end_minute": 59 114 | }, 115 | { 116 | "type": "HOLIDAY" 117 | } 118 | ], 119 | "business_service": [ 120 | "BIZ_SERVICE_FREE_WIFI", 121 | "BIZ_SERVICE_WITH_PET", 122 | "BIZ_SERVICE_FREE_PARK", 123 | "BIZ_SERVICE_DELIVER" 124 | ] 125 | }, 126 | "deal_detail": "以下锅底2选1(有菌王锅、麻辣锅、大骨锅、番茄锅、清补 凉锅、酸菜鱼锅可选):\n大锅1份 12元\n小锅2份 16元 " 127 | } 128 | } 129 | }` 130 | m := util.JSONToMap([]byte(json)) 131 | t.Log(m) 132 | x := m.ToXML() 133 | t.Log(string(x)) 134 | t.Log(util.XMLToMap(x)) 135 | } 136 | 137 | // BenchmarkMap_ToXML ... 138 | func BenchmarkMap_ToXML(b *testing.B) { 139 | json := `{ 140 | "card": { 141 | "card_type": "GROUPON", 142 | "groupon": { 143 | "base_info": { 144 | "logo_url": "http://mmbiz.qpic.cn/mmbiz/iaL1LJM1mF9aRKPZJkmG8xXhiaHqkKSVMMWeN3hLut7X7hicFNjakmxibMLGWpXrEXB33367o7zHN0CwngnQY7zb7g/0", 145 | "brand_name": "微信餐厅", 146 | "code_type": "CODE_TYPE_TEXT", 147 | "title": "132元双人火锅套餐", 148 | "color": "Color010", 149 | "notice": "使用时向服务员出示此券", 150 | "service_phone": "020-88888888", 151 | "description": "不可与其他优惠同享\n如需团购券发票,请在消费时向商户提出\n店内均可使用,仅限堂食", 152 | "date_info": { 153 | "type": "DATE_TYPE_FIX_TIME_RANGE", 154 | "begin_timestamp": 1397577600, 155 | "end_timestamp": 1472724261 156 | }, 157 | "sku": { 158 | "quantity": 500000 159 | }, 160 | "use_limit": 100, 161 | "get_limit": 3, 162 | "use_custom_code": false, 163 | "bind_openid": false, 164 | "can_share": true, 165 | "can_give_friend": true, 166 | "location_id_list": [123,12321,345345], 167 | "center_title": "顶部居中按钮", 168 | "center_sub_title": "按钮下方的wording", 169 | "center_url": "www.qq.com", 170 | "custom_url_name": "立即使用", 171 | "custom_url": "http://www.qq.com", 172 | "custom_url_sub_title": "6个汉字tips", 173 | "promotion_url_name": "更多优惠", 174 | "promotion_url": "http://www.qq.com", 175 | "source": "大众点评" 176 | }, 177 | "advanced_info": { 178 | "use_condition": { 179 | "accept_category": "鞋类", 180 | "reject_category": "阿迪达斯", 181 | "can_use_with_other_discount": true 182 | }, 183 | "abstract": { 184 | "abstract": "微信餐厅推出多种新季菜品,期待您的光临", 185 | "icon_url_list": [ 186 | "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sj\n piby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0" 187 | ] 188 | }, 189 | "text_image_list": [ 190 | { 191 | "image_url": "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sjpiby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0", 192 | "text": "此菜品精选食材,以独特的烹饪方法,最大程度地刺激食 客的味蕾" 193 | }, 194 | { 195 | "image_url": "http://mmbiz.qpic.cn/mmbiz/p98FjXy8LacgHxp3sJ3vn97bGLz0ib0Sfz1bjiaoOYA027iasqSG0sj piby4vce3AtaPu6cIhBHkt6IjlkY9YnDsfw/0", 196 | "text": "此菜品迎合大众口味,老少皆宜,营养均衡" 197 | } 198 | ], 199 | "time_limit": [ 200 | { 201 | "type": "MONDAY", 202 | "begin_hour": 0, 203 | "end_hour": 10, 204 | "begin_minute": 10, 205 | "end_minute": 59 206 | }, 207 | { 208 | "type": "HOLIDAY" 209 | } 210 | ], 211 | "business_service": [ 212 | "BIZ_SERVICE_FREE_WIFI", 213 | "BIZ_SERVICE_WITH_PET", 214 | "BIZ_SERVICE_FREE_PARK", 215 | "BIZ_SERVICE_DELIVER" 216 | ] 217 | }, 218 | "deal_detail": "以下锅底2选1(有菌王锅、麻辣锅、大骨锅、番茄锅、清补 凉锅、酸菜鱼锅可选):\n大锅1份 12元\n小锅2份 16元 " 219 | } 220 | } 221 | }` 222 | 223 | m := util.JSONToMap([]byte(json)) 224 | b.Log(m) 225 | x := m.ToXML() 226 | b.Log(string(x)) 227 | b.ResetTimer() 228 | for i := 0; i < 1000; i++ { 229 | m2 := util.Map{} 230 | _ = xml.Unmarshal(x, &m2) 231 | b.Log(m2) 232 | } 233 | 234 | } 235 | 236 | // TestMap_MarshalXML ... 237 | func TestMap_MarshalXML(t *testing.T) { 238 | json := []byte(`{"appid":"wx1ad61aeef1903b93","bank_type":"CMB_DEBIT","cash_fee":"200","fee_type":"CNY","is_subscribe":"N","mch_id":"1498009232","nonce_str":"7cda1edf536f11e88cb200163e04155d","openid":"oE_gl0bQ7iJ2g3OBMQPWRiBSoiks","out_trade_no":"8195400821515968","result_code":"SUCCESS","return_code":"SUCCESS","sign":"BE9EA07614C09FA73A683071877D9DDB","time_end":"20180509175821","total_fee":"200","trade_type":"JSAPI","transaction_id":"4200000155201805096015992498"}`) 239 | m := util.JSONToMap(json) 240 | t.Log(string(m.ToXML())) 241 | } 242 | 243 | // TestStructToMap ... 244 | func TestStructToMap(t *testing.T) { 245 | src := struct { 246 | One string 247 | Two string 248 | Three int 249 | Four uint64 250 | }{ 251 | One: "a", 252 | Two: "b", 253 | Three: 10, 254 | Four: 100, 255 | } 256 | p := util.Map{} 257 | e := util.StructToMap(src, p) 258 | t.Log(p, e) 259 | } 260 | -------------------------------------------------------------------------------- /util/net.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | ) 7 | 8 | /*GetServerIP 获取服务端IP */ 9 | func GetServerIP() string { 10 | adds, err := net.InterfaceAddrs() 11 | if err != nil { 12 | return "127.0.0.1" 13 | } 14 | 15 | for _, address := range adds { 16 | // 检查ip地址判断是否回环地址 17 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 18 | if ipnet.IP.To4() != nil { 19 | return ipnet.IP.String() 20 | } 21 | 22 | } 23 | } 24 | return "127.0.0.1" 25 | } 26 | 27 | /*GetClientIP 取得客户端IP */ 28 | func GetClientIP(r *http.Request) string { 29 | ip, _, err := net.SplitHostPort(r.RemoteAddr) 30 | if err == nil && ip != "127.0.0.1" { 31 | return ip 32 | } 33 | ip = r.Header.Get("X-Forwarded-For") 34 | if ip == "" { 35 | return "127.0.0.1" 36 | } 37 | return ip 38 | } 39 | -------------------------------------------------------------------------------- /util/sign.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/hmac" 5 | "crypto/md5" 6 | "crypto/sha256" 7 | "fmt" 8 | "io" 9 | "strings" 10 | ) 11 | 12 | /*SignFunc sign函数定义 */ 13 | type SignFunc func(data, key string) string 14 | 15 | // FieldSign ... 16 | const FieldSign = "sign" 17 | 18 | // FieldSignType ... 19 | const FieldSignType = "sign_type" 20 | 21 | // FieldLimit ... 22 | const FieldLimit = "limit" 23 | 24 | /*HMACSHA256 定义:HMAC-SHA256 */ 25 | const HMACSHA256 = "HMAC-SHA256" 26 | 27 | /*MD5 定义:MD5 */ 28 | const MD5 = "MD5" 29 | 30 | // SignSHA256 make sign with hmac-sha256 31 | func SignSHA256(data, key string) string { 32 | m := hmac.New(sha256.New, []byte(key)) 33 | m.Write([]byte(data)) 34 | return strings.ToUpper(fmt.Sprintf("%x", m.Sum(nil))) 35 | } 36 | 37 | // SignMD5 make sign with md5 38 | func SignMD5(data, key string) string { 39 | m := md5.New() 40 | _, _ = io.WriteString(m, data) 41 | 42 | return strings.ToUpper(fmt.Sprintf("%x", m.Sum(nil))) 43 | } 44 | 45 | // GenSignWithIgnore ... 46 | func GenSignWithIgnore(p Map, key string, ignore []string) string { 47 | return GenSign(p, key, ignore...) 48 | } 49 | 50 | // MapSignFunc ... 51 | func MapSignFunc(p Map) SignFunc { 52 | if p.GetString("sign_type") == HMACSHA256 { 53 | return SignSHA256 54 | } 55 | return SignMD5 56 | } 57 | 58 | // GenSign make sign from map data 59 | func GenSign(p Map, key string, ignores ...string) string { 60 | log.Debug("sign:", p, key, ignores) 61 | exp := append(ignores[:], FieldSign) 62 | m := p.Expect(exp) 63 | keys := m.SortKeys() 64 | var sign []string 65 | size := len(keys) 66 | for i := 0; i < size; i++ { 67 | v := strings.TrimSpace(m.GetString(keys[i])) 68 | if len(v) > 0 { 69 | sign = append(sign, strings.Join([]string{keys[i], v}, "=")) 70 | } 71 | } 72 | 73 | sign = append(sign, strings.Join([]string{"key", key}, "=")) 74 | sb := strings.Join(sign, "&") 75 | return MapSignFunc(p)(sb, key) 76 | } 77 | 78 | // ValidateSign check the sign validate 79 | func ValidateSign(p Map, key string) bool { 80 | if !p.Has("sign") { 81 | return false 82 | } 83 | sign := p.GetString("sign") 84 | newSign := GenSign(p, key) 85 | log.Debug(sign, newSign) 86 | 87 | if strings.Compare(sign, newSign) == 0 { 88 | return true 89 | } 90 | return false 91 | } 92 | -------------------------------------------------------------------------------- /util/url.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "strings" 4 | 5 | /*URL 拼接地址 */ 6 | func URL(prefix string, uris ...string) string { 7 | end := len(prefix) 8 | if end > 1 && prefix[end-1] == '/' { 9 | prefix = prefix[:end-1] 10 | } 11 | 12 | var url = []string{prefix} 13 | for _, v := range uris { 14 | url = append(url, TrimSlash(v)) 15 | } 16 | return strings.Join(url, "/") 17 | } 18 | 19 | // TrimSlash ... 20 | func TrimSlash(s string) string { 21 | if size := len(s); size > 1 { 22 | if s[size-1] == '/' { 23 | s = s[:size-1] 24 | } 25 | if s[0] == '/' { 26 | s = s[1:] 27 | } 28 | } 29 | return s 30 | } 31 | -------------------------------------------------------------------------------- /util/url_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import "testing" 4 | 5 | var urlTest = []struct { 6 | Prefix string 7 | URI string 8 | Success string 9 | }{ 10 | { 11 | Prefix: "http://localhost", 12 | URI: "/localaddress", 13 | Success: "http://localhost/localaddress", 14 | }, 15 | { 16 | Prefix: "http://localhost/", 17 | URI: "localaddress", 18 | Success: "http://localhost/localaddress", 19 | }, 20 | { 21 | Prefix: "http://localhost", 22 | URI: "localaddress", 23 | Success: "http://localhost/localaddress", 24 | }, 25 | { 26 | Prefix: "http://localhost/", 27 | URI: "/localaddress", 28 | Success: "http://localhost/localaddress", 29 | }, 30 | } 31 | 32 | // TestURL ... 33 | func TestURL(t *testing.T) { 34 | for _, val := range urlTest { 35 | res := URL(val.Prefix, val.URI) 36 | if res != val.Success { 37 | t.Error(val.Prefix, val.URI, res, val.Success) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /wechat_user.go: -------------------------------------------------------------------------------- 1 | package wego 2 | 3 | /*WechatUser WechatUser */ 4 | type WechatUser struct { 5 | City string `json:"city"` 6 | Country string `json:"country"` 7 | HeadImgURL string `json:"headimgurl"` 8 | Language string `json:"language"` 9 | Nickname string `json:"nickname"` 10 | OpenID string `json:"openid"` 11 | Privilege []string `json:"privilege"` 12 | Province string `json:"province"` 13 | Sex uint `json:"sex"` 14 | Subscribe int `json:"subscribe"` 15 | SubscribeTime uint32 `json:"subscribe_time"` 16 | UnionID string `json:"unionid"` 17 | Remark string `json:"remark"` 18 | GroupID int `json:"groupid"` 19 | TagIDList []int `json:"tagid_list"` 20 | SubscribeScene string `json:"subscribe_scene"` 21 | QrScene int `json:"qr_scene"` 22 | QrSceneStr string `json:"qr_scene_str"` 23 | } 24 | 25 | /*WechatUserID WechatUserID */ 26 | type WechatUserID struct { 27 | OpenID string `json:"openid"` 28 | Lang string `json:"lang,omitempty"` 29 | } 30 | --------------------------------------------------------------------------------