├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── mi ├── mi.go ├── qrcode.go └── token.go ├── mp ├── content.go ├── mp.go ├── qrcode.go ├── session.go ├── token.go └── trace.go ├── mx ├── mx.go ├── ticket.go └── token.go ├── qq └── qq.go ├── ums ├── token.go └── ums.go ├── wechat ├── token.go └── wechat.go ├── weibo └── weibo.go ├── wxwork ├── token.go └── wxwork.go └── x └── cache ├── cache.go └── string.go /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tiantour/union/3d6b8e1a71ae0687ba186759d01801d0f52e9b87/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 tiantour 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 | # union 2 | union login component developed by go. support Alipay, WeChat, QQ, Weibo, UmsPay, Mini Program, Wxwork 3 | 4 | ### alipay 5 | ```golang 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | 11 | "github.com/tiantour/union/mi" 12 | ) 13 | 14 | func main() { 15 | mi.AppID = "your AppID" 16 | mi.PrivatePath = "your PrivateKey path" 17 | mi.PublicPath = "your PublicKey path" 18 | user, err := mi.NewMI().User("your code", "your content") 19 | fmt.Println(user, err) 20 | } 21 | ``` 22 | 23 | ### wechat 24 | ```golang 25 | package main 26 | 27 | import ( 28 | "fmt" 29 | 30 | "github.com/tiantour/union/wechat" 31 | ) 32 | 33 | func main() { 34 | wechat.AppID = "your AppID" 35 | wechat.AppSecret = "your AppSecret" 36 | user, err := wechat.NewWechat().User("your code") 37 | fmt.Println(user, err) 38 | } 39 | ``` 40 | 41 | ### qq 42 | 43 | ```golang 44 | package main 45 | 46 | import ( 47 | "fmt" 48 | 49 | "github.com/tiantour/union/qq" 50 | ) 51 | 52 | func main() { 53 | qq.AppID = "your AppID" 54 | user, err := qq.NewQQ().User("your AccessToken", "your OpenID") 55 | fmt.Println(user, err) 56 | } 57 | ``` 58 | 59 | ### weibo 60 | 61 | ```golang 62 | package main 63 | 64 | import ( 65 | "fmt" 66 | 67 | "github.com/tiantour/union/weibo" 68 | ) 69 | 70 | func main() { 71 | weibo.AppID = "your AppID" 72 | user, err := weibo.NewWeibo().User("your accessToken", "your UID") 73 | fmt.Println(user, err) 74 | } 75 | ``` 76 | 77 | ### umsPay 78 | 79 | ```golang 80 | package main 81 | 82 | import ( 83 | "fmt" 84 | 85 | "github.com/tiantour/union/ums" 86 | ) 87 | 88 | func main() { 89 | ums.AppID = "your AppID" 90 | ums.AppKey = "your AppKey" 91 | user, err := ums.NewToken().Access() 92 | fmt.Println(user, err) 93 | } 94 | ``` 95 | 96 | ### mini program 97 | 98 | ```golang 99 | package main 100 | 101 | import ( 102 | "fmt" 103 | 104 | "github.com/tiantour/union/mp" 105 | ) 106 | 107 | func main() { 108 | mp.AppID = "your AppID" 109 | mp.AppSecret = "your AppSecret" 110 | 111 | // new 112 | data, err := mp.NewSession().Get("your code") 113 | fmt.Println(data, err) 114 | 115 | // old 116 | data, err := mp.NewMP().User(encryptedData, iv) 117 | fmt.Println(data, err) 118 | 119 | } 120 | ``` 121 | 122 | ### wxwork 123 | 124 | ```golang 125 | package main 126 | 127 | import ( 128 | "fmt" 129 | 130 | "github.com/tiantour/union/wxwork" 131 | ) 132 | 133 | func main() { 134 | wxwork.CorpID = "your CorpID" 135 | wxwork.CorpSecret = "your CorpSecret" 136 | user, err := mp.NewWxwork().User(code) 137 | fmt.Println(user, err) 138 | } 139 | ``` -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tiantour/union 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/dgraph-io/ristretto v0.1.1 7 | github.com/duke-git/lancet/v2 v2.1.17 8 | github.com/google/go-querystring v1.1.0 9 | github.com/tiantour/rsae v1.0.7 10 | ) 11 | 12 | require ( 13 | github.com/cespare/xxhash/v2 v2.1.1 // indirect 14 | github.com/dustin/go-humanize v1.0.0 // indirect 15 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect 16 | github.com/pkg/errors v0.9.1 // indirect 17 | golang.org/x/crypto v0.6.0 // indirect 18 | golang.org/x/exp v0.0.0-20221208152030-732eee02a75a // indirect 19 | golang.org/x/sys v0.5.0 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 2 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= 7 | github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= 8 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= 9 | github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= 10 | github.com/duke-git/lancet/v2 v2.1.17 h1:4u9oAGgmTPTt2D7AcjjLp0ubbcaQlova8xeTIuyupDw= 11 | github.com/duke-git/lancet/v2 v2.1.17/go.mod h1:hNcc06mV7qr+crH/0nP+rlC3TB0Q9g5OrVnO8/TGD4c= 12 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 13 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 14 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 15 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 16 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 17 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 18 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 19 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 20 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 21 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 22 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 25 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 26 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 27 | github.com/tiantour/rsae v1.0.7 h1:YJT2VooKdIJ5tGlRSE142vWoHi5VGaf5Em6mLnD1bjI= 28 | github.com/tiantour/rsae v1.0.7/go.mod h1:abvpQcHzEFogLsdFHN1H0qFTvgmT9R8/PQnZoWUjOcY= 29 | golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= 30 | golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= 31 | golang.org/x/exp v0.0.0-20221208152030-732eee02a75a h1:4iLhBPcpqFmylhnkbY3W0ONLUYYkDAW9xMFLfxgsvCw= 32 | golang.org/x/exp v0.0.0-20221208152030-732eee02a75a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 33 | golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 34 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 35 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 37 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 38 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 39 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 40 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 41 | -------------------------------------------------------------------------------- /mi/mi.go: -------------------------------------------------------------------------------- 1 | package mi 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "os" 8 | 9 | "github.com/tiantour/rsae" 10 | ) 11 | 12 | var ( 13 | AppID string // AppID appid 14 | 15 | AesKey string // AesKey aes key 16 | 17 | PrivatePath string // PrivatePath private path 18 | 19 | PublicPath string // PublicPath public path 20 | ) 21 | 22 | type ( 23 | // MI mi 24 | MI struct{} 25 | 26 | // User user 27 | User struct { 28 | UserID string `json:"user_id,omitempty"` // 是 用户ID 29 | NickName string `json:"nickName,omitempty"` // 是 用户暱称 30 | Avatar string `json:"avatar,omitempty"` // 是 用户头像 31 | Gender string `json:"gender,omitempty"` // 否 用户性别 32 | CountryCode string `json:"countryCode,omitempty"` // 国家编码 33 | Province string `json:"province,omitempty"` // 是 省份 34 | City string `json:"city,omitempty"` // 是 城市 35 | } 36 | 37 | // Request request 38 | Request struct { 39 | AppID string `json:"app_id,omitempty" url:"app_id,omitempty"` // 是 应用ID 40 | Method string `json:"method,omitempty" url:"method,omitempty"` // 是 接口名称 41 | Format string `json:"format,omitempty" url:"format,omitempty"` // 否 JSON 42 | Charset string `json:"charset,omitempty" url:"charset,omitempty"` // 是 utf-8 43 | SignType string `json:"sign_type,omitempty" url:"sign_type,omitempty"` // 是 RSA2 44 | Sign string `json:"sign,omitempty" url:"sign,omitempty"` // 是 签名 45 | TimeStamp string `json:"timestamp,omitempty" url:"timestamp,omitempty"` // 是 时间 46 | Version string `json:"version,omitempty" url:"version,omitempty"` // 是 1.0 47 | AuthToken string `json:"auth_token,omitempty" url:"auth_token,omitempty"` // 是 用户授权 48 | AppAuthToken string `json:"app_auth_token,omitempty" url:"app_auth_token,omitempty"` // 否 应用授权 49 | GrantType string `json:"grant_type,omitempty" url:"grant_type,omitempty"` // 是 值为authorization_code时,代表用code换取;值为refresh_token时,代表用refresh_token换取 50 | Code string `json:"code,omitempty" url:"code,omitempty"` // 否 授权码 51 | RefreshToken string `json:"refresh_token,omitempty" url:"refresh_token,omitempty"` // 否 刷新令牌 52 | BizContent string `json:"biz_content,omitempty" url:"biz_content,omitempty"` // 请求参数的集合 53 | } 54 | 55 | // Response response 56 | Response struct { 57 | Code string `json:"code,omitempty"` // 是 网关返回码 58 | Msg string `json:"msg,omitempty"` // 是 网关返回码描述 59 | SubCode string `json:"sub_code,omitempty"` // 否 业务返回码 60 | SubMsg string `json:"sub_msg,omitempty"` // 是 业务返回码描述 61 | Sign string `json:"sign,omitempty"` // 是 签名 62 | *Next 63 | } 64 | 65 | // Next next 66 | Next struct { 67 | UserID string `json:"user_id,omitempty"` // 是 支付宝用户的唯一userId 68 | AccessToken string `json:"access_token,omitempty"` // 是 访问令牌。通过该令牌调用需要授权类接口 69 | ExpiresIn int32 `json:"expires_in,omitempty"` // 是 访问令牌的有效时间,单位是秒。 70 | RefreshToken string `json:"refresh_token,omitempty"` // 是 刷新令牌。通过该令牌可以刷新access_token 71 | ReExpiresIn int32 `json:"re_expires_in,omitempty"` // 是 刷新令牌的有效时间,单位是秒。 72 | Moblie string `json:"mobile"` // 手机号 73 | Response string `json:"response,omitempty"` // 否 内容 74 | QrCodeURL string `json:"qr_code_url,omitempty"` // 二维码图片链接地址 75 | } 76 | 77 | // Result result 78 | Result struct { 79 | AlipaySystemOauthTokenResponse *Response `json:"alipay_system_oauth_token_response,omitempty"` // 内容 80 | AlipayUserInfoShareResponse *Response `json:"alipay_user_info_share_response,omitempty"` // 内容 81 | AlipayOpenAppQrcodeCreateResponse *Response `json:"alipay_open_app_qrcode_create_response,omitempty"` // 内容 82 | Sign string `json:"sign,omitempty"` // 签名 83 | } 84 | ) 85 | 86 | // NewMI new mi 87 | func NewMI() *MI { 88 | return &MI{} 89 | } 90 | 91 | // User user 92 | func (m *MI) User(code, content string) (*User, error) { 93 | response, err := NewToken().Access(code) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | user := User{} 99 | err = json.Unmarshal([]byte(content), &user) 100 | if err != nil { 101 | return nil, err 102 | } 103 | user.UserID = response.UserID 104 | 105 | return &user, err 106 | } 107 | 108 | // Phone phone 109 | func (m *MI) Phone(content string) (*Response, error) { 110 | data := Response{} 111 | err := json.Unmarshal([]byte(content), &data) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | publicKey, err := os.ReadFile(PublicPath) 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | origdata := fmt.Sprintf(`"%s"`, data.Response) 122 | ok, err := rsae.NewRSA().Verify(origdata, data.Sign, publicKey) 123 | if !ok { 124 | return nil, err 125 | } 126 | 127 | iv := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 128 | ciphertext, err := rsae.NewBase64().Decode(data.Response) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | key, err := rsae.NewBase64().Decode(AesKey) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | body, err := rsae.NewAES().Decrypt(ciphertext, key, iv) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | result := Response{} 144 | err = json.Unmarshal(body, &result) 145 | if err != nil { 146 | return nil, err 147 | } 148 | 149 | if result.Code != "10000" { 150 | return nil, errors.New(result.Msg) 151 | } 152 | return &result, nil 153 | } 154 | -------------------------------------------------------------------------------- /mi/qrcode.go: -------------------------------------------------------------------------------- 1 | package mi 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/datetime" 8 | "github.com/duke-git/lancet/v2/netutil" 9 | "github.com/google/go-querystring/query" 10 | ) 11 | 12 | // QR qr 13 | type QR struct { 14 | URLParam string `json:"url_param,omitempty"` // 小程序中能访问到的页面路径。 15 | QueryParam string `json:"query_param,omitempty"` // 小程序的启动参数,打开小程序的query,在小程序onLaunch的方法中获取。 16 | Describe int `json:"describe,omitempty"` // 对应的二维码描述。 17 | } 18 | 19 | // Generate qrcode generate 20 | func (q *QR) Generate(content string) (*Response, error) { 21 | args := &Request{ 22 | AppID: AppID, 23 | Method: "alipay.open.app.qrcode.create", 24 | Format: "JSON", 25 | Charset: "utf-8", 26 | SignType: "RSA2", 27 | TimeStamp: datetime.GetNowDateTime(), 28 | Version: "1.0", 29 | BizContent: content, 30 | } 31 | 32 | tmp, err := query.Values(args) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | sign, err := NewToken().Sign(&tmp, PrivatePath) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | client := netutil.NewHttpClient() 43 | resp, err := client.SendRequest(&netutil.HttpRequest{ 44 | RawURL: fmt.Sprintf("https://openapi.alipay.com/gateway.do?%s", sign), 45 | Method: "GET", 46 | }) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | result := Result{} 52 | err = client.DecodeResponse(resp, &result) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | response := result.AlipayOpenAppQrcodeCreateResponse 58 | if response.Code != "10000" { 59 | return nil, errors.New(response.Msg) 60 | } 61 | return response, nil 62 | } 63 | -------------------------------------------------------------------------------- /mi/token.go: -------------------------------------------------------------------------------- 1 | package mi 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/url" 7 | "os" 8 | 9 | "github.com/duke-git/lancet/v2/datetime" 10 | "github.com/duke-git/lancet/v2/netutil" 11 | "github.com/google/go-querystring/query" 12 | "github.com/tiantour/rsae" 13 | ) 14 | 15 | // Token token 16 | type Token struct{} 17 | 18 | // NewToken new token 19 | func NewToken() *Token { 20 | return &Token{} 21 | } 22 | 23 | // Access access token 24 | func (t *Token) Access(code string) (*Response, error) { 25 | args := &Request{ 26 | AppID: AppID, 27 | Method: "alipay.system.oauth.token", 28 | Format: "JSON", 29 | Charset: "utf-8", 30 | SignType: "RSA2", 31 | TimeStamp: datetime.GetNowDateTime(), 32 | Version: "1.0", 33 | GrantType: "authorization_code", 34 | Code: code, 35 | } 36 | 37 | tmp, err := query.Values(args) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | sign, err := t.Sign(&tmp, PrivatePath) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | client := netutil.NewHttpClient() 48 | resp, err := client.SendRequest(&netutil.HttpRequest{ 49 | RawURL: fmt.Sprintf("https://openapi.alipay.com/gateway.do?%s", sign), 50 | Method: "GET", 51 | }) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | result := Result{} 57 | err = client.DecodeResponse(resp, &result) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | response := result.AlipaySystemOauthTokenResponse 63 | if response.Code != "" { 64 | return nil, errors.New(response.Msg) 65 | } 66 | return response, nil 67 | } 68 | 69 | // Sign trade sign 70 | func (t *Token) Sign(args *url.Values, privatePath string) (string, error) { 71 | query, err := url.QueryUnescape(args.Encode()) 72 | if err != nil { 73 | return "", err 74 | } 75 | 76 | privateKey, err := os.ReadFile(privatePath) 77 | if err != nil { 78 | return "", err 79 | } 80 | 81 | sign, err := rsae.NewRSA().Sign(query, privateKey) 82 | if err != nil { 83 | return "", err 84 | } 85 | args.Add("sign", sign) 86 | 87 | return args.Encode(), nil 88 | } 89 | 90 | // Verify verify 91 | func (t *Token) Verify(args url.Values, publicPath string) error { 92 | sign := args.Get("sign") 93 | args.Del("sign") 94 | args.Del("sign_type") 95 | 96 | query, err := url.QueryUnescape(args.Encode()) 97 | if err != nil { 98 | return err 99 | } 100 | 101 | publicKey, err := os.ReadFile(publicPath) 102 | if err != nil { 103 | return err 104 | } 105 | 106 | ok, err := rsae.NewRSA().Verify(query, sign, publicKey) 107 | if !ok { 108 | return err 109 | } 110 | return nil 111 | } 112 | -------------------------------------------------------------------------------- /mp/content.go: -------------------------------------------------------------------------------- 1 | package mp 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | ) 10 | 11 | type ( 12 | // Image image 13 | Image struct { 14 | Media []byte `json:"media"` // 媒体 15 | } 16 | 17 | // Message message 18 | Message struct { 19 | Content string `json:"content"` // 内容 20 | } 21 | ) 22 | 23 | type Content struct{} 24 | 25 | func NewContent() *Content { 26 | return new(Content) 27 | } 28 | 29 | // Image image 30 | func (c *Content) Image(args *Image) ([]byte, error) { 31 | token, err := NewToken().Access() 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | body, err := json.Marshal(args) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | client := netutil.NewHttpClient() 42 | resp, err := client.SendRequest(&netutil.HttpRequest{ 43 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/wxa/img_sec_check?access_token=%s", token), 44 | Method: "POST", 45 | Body: body, 46 | }) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | result := Error{} 52 | err = client.DecodeResponse(resp, &result) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | if result.ErrCode != 0 { 58 | return nil, errors.New(result.ErrMsg) 59 | } 60 | return body, err 61 | } 62 | 63 | // Message message 64 | func (c *Content) Message(args *Message) ([]byte, error) { 65 | token, err := NewToken().Access() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | body, err := json.Marshal(args) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | client := netutil.NewHttpClient() 76 | resp, err := client.SendRequest(&netutil.HttpRequest{ 77 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/wxa/msg_sec_check?access_token=%s", token), 78 | Method: "POST", 79 | Body: body, 80 | }) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | result := Error{} 86 | err = client.DecodeResponse(resp, &result) 87 | if err != nil { 88 | return nil, err 89 | } 90 | 91 | if result.ErrCode != 0 { 92 | return nil, errors.New(result.ErrMsg) 93 | } 94 | return body, err 95 | } 96 | -------------------------------------------------------------------------------- /mp/mp.go: -------------------------------------------------------------------------------- 1 | package mp 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | "github.com/tiantour/rsae" 10 | ) 11 | 12 | // https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/user-info/phone-number/getPhoneNumber.html 13 | 14 | var ( 15 | AppID string // AppID appid 16 | 17 | AppSecret string // AppSecret app secret 18 | 19 | SessionKey string // SessionKey sessionKey 20 | ) 21 | 22 | type ( 23 | // MP mp 24 | MP struct{} 25 | 26 | // Error Error 27 | Error struct { 28 | ErrCode int `json:"errcode"` // 错误代码 29 | ErrMsg string `json:"errmsg"` // 错误消息 30 | } 31 | 32 | // User user 33 | User struct { 34 | NickName string `json:"nickName"` // 用户昵称 35 | Gender int `json:"gender"` // 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 36 | Language string `json:"language"` // 语言 37 | City string `json:"city"` // 普通用户个人资料填写的城市 38 | Province string `json:"province"` // 用户个人资料填写的省份 39 | Country string `json:"country"` // 国家,如中国为CN 40 | AvatarURL string `json:"avatarUrl"` // 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。 41 | OpenID string `json:"openid,omitempty"` // 用户的唯一标识 42 | UnionID string `json:"unionid,omitempty"` // 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制) 43 | OpenGID string `json:"openGId"` // 群对当前小程序的唯一 ID 44 | Watermark Watermark `json:"watermark"` // 水印 45 | } 46 | 47 | // Phone phone 48 | Phone struct { 49 | PhoneNumber string `json:"phoneNumber"` // 用户绑定的手机号 50 | PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号 51 | CountryCode string `json:"countryCode"` // 区号 52 | Watermark Watermark `json:"watermark"` // 水印 53 | } 54 | 55 | // Watermark watermark 56 | Watermark struct { 57 | AppID string `json:"appid,omitempty"` 58 | TimeStamp int `json:"timestamp,omitempty"` 59 | } 60 | 61 | // WP wechat mobile 62 | WP struct { 63 | Error 64 | PhoneInfo Phone `json:"phone_info"` // 手机 65 | } 66 | ) 67 | 68 | // NewMP new mini program 69 | func NewMP() *MP { 70 | return &MP{} 71 | } 72 | 73 | // User user 74 | func (m *MP) User(encryptedData, iv string) (*User, error) { 75 | encryptedByte, err := rsae.NewBase64().Decode(encryptedData) 76 | if err != nil { 77 | return nil, err 78 | } 79 | 80 | sessionByte, err := rsae.NewBase64().Decode(SessionKey) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | ivByte, err := rsae.NewBase64().Decode(iv) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | body, err := rsae.NewAES().Decrypt(encryptedByte, sessionByte, ivByte) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | data := User{} 96 | err = json.Unmarshal(body, &data) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | if data.Watermark.AppID != AppID { 102 | return nil, errors.New("appid not match") 103 | } 104 | return &data, nil 105 | } 106 | 107 | // Phone phone 108 | func (m *MP) Phone(code string) (*Phone, error) { 109 | token, err := NewToken().Access() 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | body, err := json.Marshal(map[string]string{ 115 | "code": code, 116 | }) 117 | if err != nil { 118 | return nil, err 119 | } 120 | 121 | client := netutil.NewHttpClient() 122 | resp, err := client.SendRequest(&netutil.HttpRequest{ 123 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s", token), 124 | Method: "POST", 125 | Body: body, 126 | }) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | result := WP{} 132 | err = client.DecodeResponse(resp, &result) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | if result.ErrCode != 0 { 138 | return nil, errors.New(result.ErrMsg) 139 | } 140 | 141 | return &result.PhoneInfo, nil 142 | } 143 | -------------------------------------------------------------------------------- /mp/qrcode.go: -------------------------------------------------------------------------------- 1 | package mp 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | ) 10 | 11 | // QR qr 12 | type QR struct { 13 | Scene string `json:"scene"` // 场景 14 | Page string `json:"page"` // 页面 15 | Width int `json:"width"` // 宽度 16 | AutoColor bool `json:"auto_color"` // 默认颜色 17 | LineColor map[string]interface{} `json:"line_color"` // 线条颜色 18 | } 19 | 20 | // NewQR new qr 21 | func NewQR() *QR { 22 | return &QR{} 23 | } 24 | 25 | // Generate qr generate 26 | func (q *QR) Generate(args *QR) ([]byte, error) { 27 | token, err := NewToken().Access() 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | body, err := json.Marshal(args) 33 | if err != nil { 34 | return nil, err 35 | } 36 | 37 | client := netutil.NewHttpClient() 38 | resp, err := client.SendRequest(&netutil.HttpRequest{ 39 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=%s", token), 40 | Method: "POST", 41 | Body: body, 42 | }) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | result := Error{} 48 | err = client.DecodeResponse(resp, &result) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | if result.ErrCode != 0 { 54 | return nil, errors.New(result.ErrMsg) 55 | } 56 | return nil, err 57 | } 58 | -------------------------------------------------------------------------------- /mp/session.go: -------------------------------------------------------------------------------- 1 | package mp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/netutil" 8 | ) 9 | 10 | // Session session 11 | type Session struct { 12 | OpenID string `json:"openid"` // openid 13 | SessionKey string `json:"session_key"` // session 14 | UnionID string `json:"unionid"` // unionid 15 | Error 16 | } 17 | 18 | // NewSession new session 19 | func NewSession() *Session { 20 | return &Session{} 21 | } 22 | 23 | // Get get 24 | func (s *Session) Get(code string) (*Session, error) { 25 | client := netutil.NewHttpClient() 26 | resp, err := client.SendRequest(&netutil.HttpRequest{ 27 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", AppID, AppSecret, code), 28 | Method: "GET", 29 | }) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | result := Session{} 35 | err = client.DecodeResponse(resp, &result) 36 | if err != nil { 37 | return nil, err 38 | } 39 | 40 | if result.ErrCode != 0 { 41 | return nil, errors.New(result.ErrMsg) 42 | } 43 | return &result, err 44 | } 45 | -------------------------------------------------------------------------------- /mp/token.go: -------------------------------------------------------------------------------- 1 | package mp 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | "github.com/tiantour/union/x/cache" 10 | ) 11 | 12 | // https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/mp-access-token/getAccessToken.html 13 | 14 | // Token token 15 | type Token struct { 16 | AccessToken string `json:"access_token"` // 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 17 | ExpiresIn int `json:"expires_in"` // access_token接口调用凭证超时时间,单位(秒) 18 | RefreshToken string `json:"refresh_token"` // 用户刷新access_token 19 | OpenID string `json:"openid"` // 用户唯一标识 20 | Scope string `json:"scope"` // 用户授权的作用域,使用逗号(,)分隔 21 | Error 22 | } 23 | 24 | // NewToken new token 25 | func NewToken() *Token { 26 | return &Token{} 27 | } 28 | 29 | // Access access token 30 | func (t *Token) Access() (string, error) { 31 | token, ok := cache.NewString().Get(AppID) 32 | if ok && token != "" { 33 | return token.(string), nil 34 | } 35 | 36 | result, err := t.Get() 37 | if err != nil { 38 | return "", err 39 | } 40 | 41 | _ = cache.NewString().Set(AppID, result.AccessToken, 1, 7200*time.Second) 42 | return result.AccessToken, nil 43 | } 44 | 45 | func (t *Token) Get() (*Token, error) { 46 | client := netutil.NewHttpClient() 47 | resp, err := client.SendRequest(&netutil.HttpRequest{ 48 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", AppID, AppSecret), 49 | Method: "GET", 50 | }) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | result := Token{} 56 | err = client.DecodeResponse(resp, &result) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | if result.ErrCode != 0 { 62 | return nil, errors.New(result.ErrMsg) 63 | } 64 | return &result, err 65 | } 66 | -------------------------------------------------------------------------------- /mp/trace.go: -------------------------------------------------------------------------------- 1 | package mp 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/duke-git/lancet/v2/netutil" 10 | ) 11 | 12 | type ( 13 | Trace struct{} 14 | 15 | TraceRequest struct { 16 | OpenID string `json:"openid,omitempty"` // 是 用户openid 17 | SenderPhone string `json:"sender_phone,omitempty"` // 否 寄件人手机号 18 | ReceiverPhone string `json:"receiver_phone,omitempty"` // 是 收件人手机号,部分运力需要用户手机号作为查单依据 19 | DeliveryID string `json:"delivery_id,omitempty"` // 否 运力id(运单号所属运力公司id),该字段从 get_delivery_list 获取。 20 | WaybillID string `json:"waybill_id,omitempty"` // 是 运单号 21 | TransID string `json:"trans_id,omitempty"` // 是 微信支付id 22 | OrderTetailTath string `json:"order_detail_path,omitempty"` // 否 点击落地页商品卡片跳转路径(建议为订单详情页path),不传默认跳转小程序首页。 23 | GoodsInfo DetailList `json:"goods_info,omitempty"` // 是 商品信息 24 | } 25 | 26 | TraceResponse struct { 27 | Error 28 | WaybillToken string `json:"waybill_token,omitempty"` // 运单token 29 | WaybillInfo *WaybillInfo `json:"waybill_info,omitempty"` // 运单信息 30 | ShopInfo *ShopInfo `json:"shop_info,omitempty"` // 店铺信息 31 | DeliveryInfo *DeliveryInfo `json:"delivery_info,omitempty"` // 运力信息 32 | } 33 | 34 | ShopInfo struct { 35 | GoodsInfo DetailList `json:"goods_info,omitempty"` // 店铺信息 36 | } 37 | 38 | DetailList struct { 39 | DetailList []*DetailItem `json:"detail_list,omitempty"` // 是 商品信息 40 | } 41 | 42 | DetailItem struct { 43 | GoodsName string `json:"goods_name,omitempty"` // 是 商品名称 44 | GoodsImgURL string `json:"goods_img_url,omitempty"` // 是 商品图片url 45 | GoodsDesc string `json:"goods_desc,omitempty"` // 否 商品详情描述,不传默认取“商品名称”值,最多40汉字 46 | } 47 | 48 | WaybillInfo struct { 49 | Status int32 `json:"status,omitempty"` // 是 运单状态,见运单状态 50 | WaybillID string `json:"waybill_id,omitempty"` // 是 运单号 51 | } 52 | 53 | DeliveryInfo struct { 54 | DeliveryID string `json:"delivery_id,omitempty"` // 是 运力公司 id 55 | DeliveryName string `json:"delivery_name,omitempty"` // 否 运力公司名称 56 | } 57 | ) 58 | 59 | func NewTrace() *Trace { 60 | return &Trace{} 61 | } 62 | 63 | func (t *Trace) Waybill(args *TraceRequest) (*TraceResponse, error) { 64 | token, err := NewToken().Access() 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | body, err := json.Marshal(args) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | request := &netutil.HttpRequest{ 75 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/trace_waybill?access_token=%s", token), 76 | Method: "POST", 77 | Body: body, 78 | Headers: http.Header{ 79 | "Content-Type": []string{"application/json; charset=utf-8"}, 80 | }, 81 | } 82 | 83 | client := netutil.NewHttpClient() 84 | resp, err := client.SendRequest(request) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | result := TraceResponse{} 90 | err = client.DecodeResponse(resp, &result) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | if result.ErrCode != 0 { 96 | return nil, errors.New(result.ErrMsg) 97 | } 98 | return &result, err 99 | } 100 | 101 | // Query query trace 102 | func (t *Trace) Query(waybillToken string) (*TraceResponse, error) { 103 | token, err := NewToken().Access() 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | body, err := json.Marshal(map[string]string{ 109 | "waybill_token": waybillToken, 110 | }) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | request := &netutil.HttpRequest{ 116 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/express/delivery/open_msg/query_trace?access_token=%s", token), 117 | Method: "POST", 118 | Body: body, 119 | Headers: http.Header{ 120 | "Content-Type": []string{"application/json; charset=utf-8"}, 121 | }, 122 | } 123 | 124 | client := netutil.NewHttpClient() 125 | resp, err := client.SendRequest(request) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | result := TraceResponse{} 131 | err = client.DecodeResponse(resp, &result) 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | if result.ErrCode != 0 { 137 | return nil, errors.New(result.ErrMsg) 138 | } 139 | return &result, err 140 | } 141 | -------------------------------------------------------------------------------- /mx/mx.go: -------------------------------------------------------------------------------- 1 | package mx 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/netutil" 8 | ) 9 | 10 | // https://developers.weixin.qq.com/doc/offiaccount/User_Management/Get_users_basic_information_UnionID.html#UinonId 11 | 12 | var ( 13 | AppID string // AppID appid 14 | 15 | AppSecret string // AppSecret app secret 16 | ) 17 | 18 | type ( 19 | MX struct{} 20 | 21 | Error struct { 22 | ErrCode int `json:"errcode,omitempty"` 23 | ErrMSG string `json:"errmsg,omitempty"` 24 | } 25 | 26 | User struct { 27 | Subscribe int `json:"subscribe,omitempty"` 28 | OpenID string `json:"openid,omitempty"` 29 | Language string `json:"language,omitempty"` 30 | SubscribeTime int `json:"subscribe_time,omitempty"` 31 | UnionID string `json:"unionid,omitempty"` 32 | Remark string `json:"remark,omitempty"` 33 | GroupID int `json:"groupid,omitempty"` 34 | TagIDList []int `json:"tagid_list,omitempty"` 35 | SubscribeBcene string `json:"subscribe_scene,omitempty"` 36 | QRScene int `json:"qr_scene,omitempty"` 37 | QRSceneStr string `json:"qr_scene_str,omitempty"` 38 | Error 39 | } 40 | ) 41 | 42 | func NewMX() *MX { 43 | return new(MX) 44 | } 45 | 46 | func (m *MX) User(openID string) (*User, error) { 47 | token, err := NewToken().Access() 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | client := netutil.NewHttpClient() 53 | resp, err := client.SendRequest(&netutil.HttpRequest{ 54 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN", token, openID), 55 | Method: "GET", 56 | }) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | result := User{} 62 | err = client.DecodeResponse(resp, &result) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | if result.ErrCode != 0 { 68 | return nil, errors.New(result.ErrMSG) 69 | } 70 | return &result, err 71 | 72 | } 73 | -------------------------------------------------------------------------------- /mx/ticket.go: -------------------------------------------------------------------------------- 1 | package mx 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | ) 10 | 11 | // https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html 12 | 13 | type ( 14 | Ticket struct { 15 | ActionName string `json:"action_name"` 16 | ExpireSeconds int `json:"expire_seconds"` 17 | ActionInfo *Action `json:"action_info"` 18 | Ticket string `json:"ticket"` 19 | URL string `json:"url"` 20 | Error 21 | } 22 | 23 | Action struct { 24 | Scene *Scene `json:"scene"` 25 | } 26 | 27 | Scene struct { 28 | SceneID int `json:"scene_id"` 29 | SceneSTR string `json:"scene_str"` 30 | } 31 | ) 32 | 33 | func NewTicket() *Ticket { 34 | return new(Ticket) 35 | } 36 | 37 | func (t *Ticket) Get(args *Ticket) (*Ticket, error) { 38 | token, err := NewToken().Access() 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | body, err := json.Marshal(args) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | client := netutil.NewHttpClient() 49 | resp, err := client.SendRequest(&netutil.HttpRequest{ 50 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", token), 51 | Method: "POST", 52 | Body: body, 53 | }) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | result := Ticket{} 59 | err = client.DecodeResponse(resp, &result) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | if result.ErrCode != 0 { 65 | return nil, errors.New(result.ErrMSG) 66 | } 67 | return &result, err 68 | } 69 | -------------------------------------------------------------------------------- /mx/token.go: -------------------------------------------------------------------------------- 1 | package mx 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | "github.com/tiantour/union/x/cache" 10 | ) 11 | 12 | type Token struct { 13 | AccessToken string `json:"access_token,omitempty"` 14 | ExpiresIN int `json:"expires_in,omitempty"` 15 | Error 16 | } 17 | 18 | func NewToken() *Token { 19 | return new(Token) 20 | } 21 | 22 | // Access access token 23 | func (t *Token) Access() (string, error) { 24 | token, ok := cache.NewString().Get(AppID) 25 | if ok && token != "" { 26 | return token.(string), nil 27 | } 28 | 29 | result, err := t.Get() 30 | if err != nil { 31 | return "", err 32 | } 33 | 34 | _ = cache.NewString().Set(AppID, result.AccessToken, 1, 7200*time.Second) 35 | return result.AccessToken, nil 36 | } 37 | 38 | func (t *Token) Get() (*Token, error) { 39 | client := netutil.NewHttpClient() 40 | resp, err := client.SendRequest(&netutil.HttpRequest{ 41 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", AppID, AppSecret), 42 | Method: "GET", 43 | }) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | result := Token{} 49 | err = client.DecodeResponse(resp, &result) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | if result.ErrCode != 0 { 55 | return nil, errors.New(result.ErrMSG) 56 | } 57 | return &result, err 58 | } 59 | -------------------------------------------------------------------------------- /qq/qq.go: -------------------------------------------------------------------------------- 1 | package qq 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/netutil" 8 | ) 9 | 10 | var ( 11 | AppID string // AppID appID 12 | ) 13 | 14 | type ( 15 | // QQ qq 16 | QQ struct{} 17 | 18 | // User user 19 | User struct { 20 | Ret int `json:"ret"` // 返回码 21 | Msg string `json:"msg"` // 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。 22 | NickName string `json:"nickname"` // 用户在QQ空间的昵称。 23 | FigureURL string `json:"figureurl"` // 大小为30×30像素的QQ空间头像URL。 24 | FigureURL1 string `json:"figureurl_1"` // 大小为50×50像素的QQ空间头像URL。 25 | FigureURL2 string `json:"figureurl_2"` // 大小为100×100像素的QQ空间头像URL。 26 | FigureURLQQ1 string `json:"figureurl_qq_1"` // 大小为40×40像素的QQ头像URL。 27 | FigureURLQQ2 string `json:"figureurl_qq_2"` // 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100x100的头像,但40x40像素则是一定会有。 28 | Gender string `json:"gender"` // 性别。 如果获取不到则默认返回"男" 29 | ISYellowVip string `json:"is_yellow_vip"` // 标识用户是否为黄钻用户(0:不是;1:是)。 30 | Vip string `json:"vip"` // 标识用户是否为黄钻用户(0:不是;1:是) 31 | YelloVipLevel string `json:"yellow_vip_level"` // 黄钻等级 32 | Level string `json:"level"` // 黄钻等级 33 | IsYellowYearVip string `json:"is_yellow_year_vip"` // 标识是否为年费黄钻用户(0:不是; 1:是) 34 | } 35 | ) 36 | 37 | // NewQQ new qq 38 | func NewQQ() *QQ { 39 | return &QQ{} 40 | } 41 | 42 | // User user 43 | func (q *QQ) User(accessToken, openID string) (*User, error) { 44 | client := netutil.NewHttpClient() 45 | resp, err := client.SendRequest(&netutil.HttpRequest{ 46 | RawURL: fmt.Sprintf("https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=%s&openid=%s", accessToken, AppID, openID), 47 | Method: "GET", 48 | }) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | result := User{} 54 | err = client.DecodeResponse(resp, &result) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | if result.Ret != 0 { 60 | return nil, errors.New(result.Msg) 61 | } 62 | return &result, err 63 | } 64 | -------------------------------------------------------------------------------- /ums/token.go: -------------------------------------------------------------------------------- 1 | package ums 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "errors" 7 | "net/http" 8 | "time" 9 | 10 | "github.com/duke-git/lancet/v2/netutil" 11 | "github.com/duke-git/lancet/v2/random" 12 | "github.com/tiantour/rsae" 13 | "github.com/tiantour/union/x/cache" 14 | ) 15 | 16 | // Token token 17 | type Token struct{} 18 | 19 | // NewToken new token 20 | func NewToken() *Token { 21 | return &Token{} 22 | } 23 | 24 | // Access access token 25 | func (t *Token) Access() (string, error) { 26 | token, ok := cache.NewString().Get(AppID) 27 | if ok && token != "" { 28 | return token.(string), nil 29 | } 30 | 31 | result, err := t.Get() 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | _ = cache.NewString().Set(AppID, result.AccessToken, 1, 7200*time.Second) 37 | return result.AccessToken, nil 38 | } 39 | 40 | func (t *Token) Get() (*Response, error) { 41 | data := &Request{ 42 | AppID: AppID, 43 | Timestamp: time.Now().Format("20060102150405"), 44 | Nonce: random.RandString(32), 45 | SignMethod: "SHA256", 46 | } 47 | sign := rsae.NewSHA().SHA256(AppID + data.Timestamp + data.Nonce + AppKey) 48 | data.Signature = string(hex.EncodeToString(sign)) 49 | 50 | body, err := json.Marshal(data) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | header := http.Header{} 56 | header.Add("Accept", "application/json") 57 | header.Add("Content-Type", "application/json;charset=utf-8") 58 | 59 | client := netutil.NewHttpClient() 60 | resp, err := client.SendRequest(&netutil.HttpRequest{ 61 | RawURL: "https://api-mop.chinaums.com/v1/token/access", 62 | Method: "POST", 63 | Body: body, 64 | Headers: header, 65 | }) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | result := Response{} 71 | err = client.DecodeResponse(resp, &result) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | if result.ErrCode != "0000" { 77 | return nil, errors.New(result.ErrInfo) 78 | } 79 | return &result, nil 80 | } 81 | -------------------------------------------------------------------------------- /ums/ums.go: -------------------------------------------------------------------------------- 1 | package ums 2 | 3 | var ( 4 | // AppID appid 5 | AppID string 6 | 7 | // AppKey app key 8 | AppKey string 9 | ) 10 | 11 | type ( 12 | // Request request 13 | Request struct { 14 | AppID string `json:"appId,omitempty"` // 是 AppId 15 | Timestamp string `json:"timestamp,omitempty"` // 是 时间戳 16 | Nonce string `json:"nonce,omitempty"` // 是 随机数 17 | SignMethod string `json:"signMethod,omitempty"` // 是 签名方法 18 | Signature string `json:"signature,omitempty"` // 是 签名 19 | } 20 | 21 | // Response response 22 | Response struct { 23 | ErrCode string `json:"errCode,omitempty"` // 是 错误代码 24 | ErrInfo string `json:"errInfo,omitempty"` // 是 错误说明 25 | AccessToken string `json:"accessToken,omitempty"` // 是 授权令牌 26 | ExpiresIn int `json:"expiresIn,omitempty"` // 是 失效时间 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /wechat/token.go: -------------------------------------------------------------------------------- 1 | package wechat 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/netutil" 8 | ) 9 | 10 | /* 11 | 12 | 微信网页授权 13 | 14 | https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 15 | 16 | */ 17 | 18 | // Token token 19 | type Token struct { 20 | AccessToken string `json:"access_token"` // 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 21 | ExpiresIn int `json:"expires_in"` // access_token接口调用凭证超时时间,单位(秒) 22 | RefreshToken string `json:"refresh_token"` // 用户刷新access_token 23 | OpenID string `json:"openid"` // 用户唯一标识 24 | Scope string `json:"scope"` // 用户授权的作用域,使用逗号(,)分隔 25 | Error 26 | } 27 | 28 | // NewToken new token 29 | func NewToken() *Token { 30 | return &Token{} 31 | } 32 | 33 | func (t *Token) Access(code string) (*Token, error) { 34 | client := netutil.NewHttpClient() 35 | resp, err := client.SendRequest(&netutil.HttpRequest{ 36 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code", AppID, AppSecret, code), 37 | Method: "GET", 38 | }) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | result := Token{} 44 | err = client.DecodeResponse(resp, &result) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | if result.ErrCode != 0 { 50 | return nil, errors.New(result.ErrMsg) 51 | } 52 | return &result, err 53 | } 54 | -------------------------------------------------------------------------------- /wechat/wechat.go: -------------------------------------------------------------------------------- 1 | package wechat 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/netutil" 8 | ) 9 | 10 | var ( 11 | AppID string // AppID appid 12 | 13 | AppSecret string // AppSecret app secret 14 | ) 15 | 16 | type ( 17 | // Wechat wechat 18 | Wechat struct{} 19 | 20 | Error struct { 21 | ErrCode int `json:"errcode"` // 错误代码 22 | ErrMsg string `json:"errmsg"` // 错误消息 23 | } 24 | 25 | // User user 26 | User struct { 27 | OpenID string `json:"openid"` // 用户的唯一标识 28 | NickName string `json:"nickname"` // 用户昵称 29 | Sex int `json:"sex"` // 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 30 | Province string `json:"province"` // 用户个人资料填写的省份 31 | City string `json:"city"` // 普通用户个人资料填写的城市 32 | Country string `json:"country"` // 国家,如中国为CN 33 | HeadImgURL string `json:"headimgurl"` // 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。 34 | Privilege []string `json:"privilege"` // 用户特权信息,json 数组,如微信沃卡用户为(chinaunicom) 35 | UnionID string `json:"unionid"` // 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。详见:获取用户个人信息(UnionID机制) 36 | Language string `json:"language"` // 语言 37 | Error 38 | } 39 | ) 40 | 41 | // NewWechat new wechat 42 | func NewWechat() *Wechat { 43 | return &Wechat{} 44 | } 45 | 46 | // User user 47 | func (w *Wechat) User(code string) (*User, error) { 48 | token, err := NewToken().Access(code) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | client := netutil.NewHttpClient() 54 | resp, err := client.SendRequest(&netutil.HttpRequest{ 55 | RawURL: fmt.Sprintf("https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s", token.AccessToken, token.OpenID), 56 | Method: "GET", 57 | }) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | result := User{} 63 | err = client.DecodeResponse(resp, &result) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | if result.ErrCode != 0 { 69 | return nil, errors.New(result.ErrMsg) 70 | } 71 | return &result, err 72 | } 73 | -------------------------------------------------------------------------------- /weibo/weibo.go: -------------------------------------------------------------------------------- 1 | package weibo 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/duke-git/lancet/v2/netutil" 7 | ) 8 | 9 | var ( 10 | AppID string // AppID appID 11 | ) 12 | 13 | type ( 14 | // Weibo weibo 15 | Weibo struct{} 16 | 17 | // User user 18 | User struct { 19 | ID int64 `json:"id"` // 用户UID 20 | IDStr string `json:"idstr"` // 字符串型的用户UID 21 | ScreenName string `json:"screen_name"` //用户昵称 22 | Name string `json:"name"` // 友好显示名称 23 | Province int `json:"province"` // 用户所在省级ID 24 | City int `json:"city"` // 用户所在城市ID 25 | Location string `json:"location"` // 用户所在地 26 | Description string `json:"description"` // 用户个人描述 27 | URL string `json:"url"` // 用户博客地址 28 | ProfileImageURL string `json:"profile_image_url"` // 用户头像地址(中图),50×50像素 29 | ProfileURL string `json:"profile_url"` // 用户的微博统一URL地址 30 | Domain string `json:"domain"` // 用户的个性化域名 31 | Weihao string `json:"weihao"` // 用户的微号 32 | Gender string `json:"gender"` // 性别,m:男、f:女、n:未知 33 | FollowersCount int `json:"followers_count"` // 粉丝数 34 | FriendsCount int `json:"friends_count"` // 关注数 35 | StatusesCount int `json:"statuses_count"` // 微博数 36 | FavouritesCount int `json:"favourites_count"` // 收藏数 37 | CreatedAt string `json:"created_at"` // 用户创建(注册)时间 38 | Following bool `json:"following"` // 暂未支持 39 | AllowAllActMsg bool `json:"allow_all_act_msg"` // 是否允许所有人给我发私信,true:是,false:否 40 | GeoEnabled bool `json:"geo_enabled"` // 是否允许标识用户的地理位置,true:是,false:否 41 | Verified bool `json:"verified"` // 是否是微博认证用户,即加V用户,true:是,false:否 42 | VerifiedType int `json:"verified_type"` // 暂未支持 43 | Remark string `json:"remark"` // 用户备注信息,只有在查询用户关系时才返回此字段 44 | Status interface{} `json:"status"` // 用户的最近一条微博信息字段 详细 45 | AllowAllComment bool `json:"allow_all_comment"` // 是否允许所有人对我的微博进行评论,true:是,false:否 46 | AvatarLarge string `json:"avatar_large"` // 用户头像地址(大图),180×180像素 47 | AvatarHD string `json:"avatar_hd"` // 用户头像地址(高清),高清头像原图 48 | VerifiedReason string `json:"verified_reason"` // 认证原因 49 | FollowMe bool `json:"follow_me"` // 该用户是否关注当前登录用户,true:是,false:否 50 | OnlineStatus int `json:"online_status"` // 用户的在线状态,0:不在线、1:在线 51 | BiFollowersCount int `json:"bi_followers_count"` // 用户的互粉数 52 | Lang string `json:"lang"` // 用户当前的语言版本,zh-cn:简体中文,zh-tw:繁体中文,en:英语 53 | } 54 | ) 55 | 56 | // NewWeibo new weibo 57 | func NewWeibo() *Weibo { 58 | return &Weibo{} 59 | } 60 | 61 | // User user 62 | func (w *Weibo) User(accessToken, uID string) (*User, error) { 63 | client := netutil.NewHttpClient() 64 | resp, err := client.SendRequest(&netutil.HttpRequest{ 65 | RawURL: fmt.Sprintf("https://api.weibo.com/2/users/show.json?source=%s&access_token=%s&uid=%s", AppID, accessToken, uID), 66 | Method: "GET", 67 | }) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | result := User{} 73 | err = client.DecodeResponse(resp, &result) 74 | if err != nil { 75 | return nil, err 76 | } 77 | return &result, err 78 | } 79 | -------------------------------------------------------------------------------- /wxwork/token.go: -------------------------------------------------------------------------------- 1 | package wxwork 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/duke-git/lancet/v2/netutil" 9 | "github.com/tiantour/union/x/cache" 10 | ) 11 | 12 | /* 13 | 企业微信网页授权 https://work.weixin.qq.com/api/doc/90000/90135/91020 14 | */ 15 | 16 | // Token token 17 | type Token struct { 18 | AccessToken string `json:"access_token"` // 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同 19 | ExpiresIn int `json:"expires_in"` // access_token接口调用凭证超时时间,单位(秒) 20 | RefreshToken string `json:"refresh_token"` // 用户刷新access_token 21 | OpenID string `json:"openid"` // 用户唯一标识 22 | Scope string `json:"scope"` // 用户授权的作用域,使用逗号(,)分隔 23 | Error 24 | } 25 | 26 | // NewToken new token 27 | func NewToken() *Token { 28 | return &Token{} 29 | } 30 | 31 | // Access token 32 | func (t *Token) Access() (string, error) { 33 | token, ok := cache.NewString().Get(CorpID) 34 | if ok && token != "" { 35 | return token.(string), nil 36 | } 37 | 38 | result, err := t.Get() 39 | if err != nil { 40 | return "", err 41 | } 42 | 43 | _ = cache.NewString().Set(CorpID, result.AccessToken, 1, 7200*time.Second) 44 | return result.AccessToken, nil 45 | } 46 | 47 | func (t *Token) Get() (*Token, error) { 48 | client := netutil.NewHttpClient() 49 | resp, err := client.SendRequest(&netutil.HttpRequest{ 50 | RawURL: fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=%s&corpsecret=%s", CorpID, CorpSecret), 51 | Method: "GET", 52 | }) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | result := Token{} 58 | err = client.DecodeResponse(resp, &result) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | if result.ErrCode != 0 { 64 | return nil, errors.New(result.ErrMsg) 65 | } 66 | return &result, err 67 | } 68 | -------------------------------------------------------------------------------- /wxwork/wxwork.go: -------------------------------------------------------------------------------- 1 | package wxwork 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/duke-git/lancet/v2/netutil" 8 | ) 9 | 10 | var ( 11 | CorpID string // CorpID corpid 12 | 13 | CorpSecret string // CorpSecret corp secret 14 | ) 15 | 16 | type ( 17 | // Wxwork Wxwork 18 | Wxwork struct{} 19 | 20 | Error struct { 21 | ErrCode int `json:"errcode"` // 错误代码 22 | ErrMsg string `json:"errmsg"` // 错误消息 23 | } 24 | 25 | // User user 26 | User struct { 27 | UserID string `json:"UserId"` // 成员UserID 28 | DeviceID string `json:"DeviceId"` // 手机设备号 29 | OpenID string `json:"OpenId"` // 非企业成员的标识,对当前企业唯一 30 | ExternalUserID string `json:"external_userid"` // 外部联系人id,当且仅当用户是企业的客户,且跟进人在应用的可见范围内时返回 31 | Error 32 | } 33 | ) 34 | 35 | // NewWxwork new wxwork 36 | func NewWxwork() *Wxwork { 37 | return &Wxwork{} 38 | } 39 | 40 | // User user 41 | func (w *Wxwork) User(code string) (*User, error) { 42 | token, err := NewToken().Access() 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | client := netutil.NewHttpClient() 48 | resp, err := client.SendRequest(&netutil.HttpRequest{ 49 | RawURL: fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=%s&code=%s", token, code), 50 | Method: "GET", 51 | }) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | result := User{} 57 | err = client.DecodeResponse(resp, &result) 58 | if err != nil { 59 | return nil, err 60 | } 61 | 62 | if result.ErrCode != 0 { 63 | return nil, errors.New(result.ErrMsg) 64 | } 65 | return &result, err 66 | } 67 | -------------------------------------------------------------------------------- /x/cache/cache.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/dgraph-io/ristretto" 7 | ) 8 | 9 | var cache *ristretto.Cache 10 | 11 | func init() { 12 | var err error 13 | cache, err = ristretto.NewCache(&ristretto.Config{ 14 | NumCounters: 1e7, // number of keys to track frequency of (10M). 15 | MaxCost: 1 << 30, // maximum cost of cache (1GB). 16 | BufferItems: 64, // number of keys per Get buffer. 17 | IgnoreInternalCost: true, 18 | }) 19 | if err != nil { 20 | log.Fatal(err) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /x/cache/string.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | ) 7 | 8 | type String struct{} 9 | 10 | func NewString() *String { 11 | return &String{} 12 | } 13 | 14 | func (s *String) Set(key, value string, cost int64, ttl time.Duration) bool { 15 | cache.Wait() 16 | return cache.SetWithTTL(key, value, cost, ttl) 17 | } 18 | 19 | func (s *String) Get(key string) (interface{}, bool) { 20 | cache.Wait() 21 | return cache.Get(key) 22 | } 23 | 24 | func (s *String) Key(key string) string { 25 | return fmt.Sprintf("string:data:bind:%s", key) 26 | } 27 | --------------------------------------------------------------------------------