├── .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 | [](http://godoc.org/github.com/godcong/wego)
4 | [](https://github.com/godcong/wego/blob/master/LICENSE)
5 | [](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 |
--------------------------------------------------------------------------------