246 lines
5.6 KiB
Go
246 lines
5.6 KiB
Go
package wechat
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"encoding/hex"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"backend/pkg/oauth"
|
|
|
|
"github.com/imroc/req/v3"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const BaseURL = "https://api.weixin.qq.com/"
|
|
|
|
var DefaultClient = req.
|
|
NewClient().
|
|
SetBaseURL(BaseURL).
|
|
SetCommonHeader("Content-Type", "application/json")
|
|
|
|
const (
|
|
ScopeBase = "snsapi_base"
|
|
ScopeUserInfo = "snsapi_userinfo"
|
|
)
|
|
|
|
type AuthScope string
|
|
|
|
func (s AuthScope) String() string {
|
|
return string(s)
|
|
}
|
|
|
|
type Client struct {
|
|
client *req.Client
|
|
|
|
appID string
|
|
appSecret string
|
|
token string
|
|
aesKey string
|
|
|
|
verifyKey string
|
|
verifyValue string
|
|
}
|
|
|
|
func New(options ...Options) *Client {
|
|
we := &Client{
|
|
client: DefaultClient,
|
|
}
|
|
|
|
for _, opt := range options {
|
|
opt(we)
|
|
}
|
|
|
|
return we
|
|
}
|
|
|
|
func (we *Client) VerifySite(key string) (string, error) {
|
|
if key == we.verifyKey {
|
|
return we.verifyValue, nil
|
|
}
|
|
return "", errors.New("verify failed")
|
|
}
|
|
|
|
func (we *Client) Verify(signature, timestamp, nonce string) error {
|
|
params := []string{signature, timestamp, nonce, we.token}
|
|
sort.Strings(params)
|
|
str := strings.Join(params, "")
|
|
hash := sha1.Sum([]byte(str))
|
|
hashStr := hex.EncodeToString(hash[:])
|
|
|
|
if hashStr == signature {
|
|
return errors.New("Signature verification failed")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (we *Client) wrapParams(params map[string]string) map[string]string {
|
|
if params == nil {
|
|
params = make(map[string]string)
|
|
}
|
|
|
|
params["appid"] = we.appID
|
|
params["secret"] = we.appSecret
|
|
|
|
return params
|
|
}
|
|
|
|
func (we *Client) GetAccessToken() (*AccessTokenResponse, error) {
|
|
params := map[string]string{
|
|
"grant_type": "client_credential",
|
|
}
|
|
|
|
var data ErrorResponse
|
|
resp, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/cgi-bin/token")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "call /cgi-bin/token failed")
|
|
}
|
|
|
|
if data.ErrCode != 0 {
|
|
return nil, data.Error()
|
|
}
|
|
|
|
var token AccessTokenResponse
|
|
if err := resp.Unmarshal(&token); err != nil {
|
|
return nil, errors.Wrap(err, "parse response failed")
|
|
}
|
|
|
|
return &token, nil
|
|
}
|
|
|
|
// ScopeAuthorizeURL
|
|
func (we *Client) ScopeAuthorizeURL(opts ...ScopeAuthorizeURLOptions) (*url.URL, error) {
|
|
params := url.Values{}
|
|
params.Add("appid", we.appID)
|
|
params.Add("response_type", "code")
|
|
|
|
for _, opt := range opts {
|
|
opt(params)
|
|
}
|
|
|
|
if params.Get("scope") == "" {
|
|
params.Add("scope", ScopeBase)
|
|
}
|
|
|
|
u, err := url.Parse("https://open.weixin.qq.com/connect/oauth2/authorize")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "parse url failed")
|
|
}
|
|
|
|
u.Fragment = "wechat_redirect"
|
|
u.RawQuery = url.Values(params).Encode()
|
|
|
|
return u, nil
|
|
}
|
|
|
|
var _ oauth.OAuthInfo = (*AuthorizeAccessToken)(nil)
|
|
|
|
type AuthorizeAccessToken struct {
|
|
ErrorResponse
|
|
AccessToken string `json:"access_token,omitempty"`
|
|
ExpiresIn int64 `json:"expires_in,omitempty"`
|
|
IsSnapshotuser int64 `json:"is_snapshotuser,omitempty"`
|
|
Openid string `json:"openid,omitempty"`
|
|
RefreshToken string `json:"refresh_token,omitempty"`
|
|
Scope string `json:"scope,omitempty"`
|
|
Unionid string `json:"unionid,omitempty"`
|
|
}
|
|
|
|
// GetAccessToken implements oauth.OAuthInfo.
|
|
func (a *AuthorizeAccessToken) GetAccessToken() string {
|
|
return a.AccessToken
|
|
}
|
|
|
|
// GetExpiredAt implements oauth.OAuthInfo.
|
|
func (a *AuthorizeAccessToken) GetExpiredAt() time.Time {
|
|
return time.Now().Add(time.Duration(a.ExpiresIn) * time.Second)
|
|
}
|
|
|
|
// GetOpenID implements oauth.OAuthInfo.
|
|
func (a *AuthorizeAccessToken) GetOpenID() string {
|
|
return a.Openid
|
|
}
|
|
|
|
// GetRefreshToken implements oauth.OAuthInfo.
|
|
func (a *AuthorizeAccessToken) GetRefreshToken() string {
|
|
return a.RefreshToken
|
|
}
|
|
|
|
// GetUnionID implements oauth.OAuthInfo.
|
|
func (a *AuthorizeAccessToken) GetUnionID() string {
|
|
return a.Unionid
|
|
}
|
|
|
|
func (we *Client) AuthorizeCode2Token(code string) (*AuthorizeAccessToken, error) {
|
|
params := we.wrapParams(map[string]string{
|
|
"code": code,
|
|
"grant_type": "authorization_code",
|
|
})
|
|
|
|
var data AuthorizeAccessToken
|
|
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/access_token")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "call /sns/oauth2/access_token failed")
|
|
}
|
|
|
|
if err := data.Error(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &data, nil
|
|
}
|
|
|
|
func (we *Client) AuthorizeRefreshAccessToken(accessToken string) (*AuthorizeAccessToken, error) {
|
|
params := we.wrapParams(map[string]string{
|
|
"refresh_token": accessToken,
|
|
"grant_type": "refresh_token",
|
|
})
|
|
|
|
var data AuthorizeAccessToken
|
|
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/oauth2/refresh_token")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "call /sns/oauth2/refresh_token failed")
|
|
}
|
|
|
|
if err := data.Error(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &data, nil
|
|
}
|
|
|
|
type AuthorizeUserInfo struct {
|
|
ErrorResponse
|
|
City string `json:"city,omitempty"`
|
|
Country string `json:"country,omitempty"`
|
|
Headimgurl string `json:"headimgurl,omitempty"`
|
|
Nickname string `json:"nickname,omitempty"`
|
|
Openid string `json:"openid,omitempty"`
|
|
Privilege []string `json:"privilege,omitempty"`
|
|
Province string `json:"province,omitempty"`
|
|
Sex int64 `json:"sex,omitempty"`
|
|
Unionid string `json:"unionid,omitempty"`
|
|
}
|
|
|
|
func (we *Client) AuthorizeUserInfo(accessToken, openID string) (*AuthorizeUserInfo, error) {
|
|
params := (map[string]string{
|
|
"access_token": accessToken,
|
|
"openid": openID,
|
|
})
|
|
|
|
var data AuthorizeUserInfo
|
|
_, err := we.client.R().SetSuccessResult(&data).SetQueryParams(params).Get("/sns/userinfo")
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "call /sns/userinfo failed")
|
|
}
|
|
|
|
if err := data.Error(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &data, nil
|
|
}
|