mirror of
https://github.com/silenceper/wechat.git
synced 2026-02-04 12:52:27 +08:00
171 lines
5.3 KiB
Go
171 lines
5.3 KiB
Go
package oauth
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
|
||
"github.com/silenceper/wechat/v2/officialaccount/context"
|
||
"github.com/silenceper/wechat/v2/util"
|
||
)
|
||
|
||
const (
|
||
redirectOauthURL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"
|
||
webAppRedirectOauthURL = "https://open.weixin.qq.com/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"
|
||
accessTokenURL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"
|
||
refreshAccessTokenURL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"
|
||
userInfoURL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=%s"
|
||
checkAccessTokenURL = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s"
|
||
)
|
||
|
||
// Oauth 保存用户授权信息
|
||
type Oauth struct {
|
||
*context.Context
|
||
}
|
||
|
||
// NewOauth 实例化授权信息
|
||
func NewOauth(context *context.Context) *Oauth {
|
||
auth := new(Oauth)
|
||
auth.Context = context
|
||
return auth
|
||
}
|
||
|
||
// GetRedirectURL 获取跳转的url地址
|
||
func (oauth *Oauth) GetRedirectURL(redirectURI, scope, state string) (string, error) {
|
||
// url encode
|
||
urlStr := url.QueryEscape(redirectURI)
|
||
return fmt.Sprintf(redirectOauthURL, oauth.AppID, urlStr, scope, state), nil
|
||
}
|
||
|
||
// GetWebAppRedirectURL 获取网页应用跳转的url地址
|
||
func (oauth *Oauth) GetWebAppRedirectURL(redirectURI, scope, state string) (string, error) {
|
||
urlStr := url.QueryEscape(redirectURI)
|
||
return fmt.Sprintf(webAppRedirectOauthURL, oauth.AppID, urlStr, scope, state), nil
|
||
}
|
||
|
||
// Redirect 跳转到网页授权
|
||
func (oauth *Oauth) Redirect(writer http.ResponseWriter, req *http.Request, redirectURI, scope, state string) error {
|
||
location, err := oauth.GetRedirectURL(redirectURI, scope, state)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
http.Redirect(writer, req, location, http.StatusFound)
|
||
return nil
|
||
}
|
||
|
||
// ResAccessToken 获取用户授权access_token的返回结果
|
||
type ResAccessToken struct {
|
||
util.CommonError
|
||
|
||
AccessToken string `json:"access_token"`
|
||
ExpiresIn int64 `json:"expires_in"`
|
||
RefreshToken string `json:"refresh_token"`
|
||
OpenID string `json:"openid"`
|
||
Scope string `json:"scope"`
|
||
|
||
// IsSnapShotUser 是否为快照页模式虚拟账号,只有当用户是快照页模式虚拟账号时返回,值为1
|
||
// 公众号文档 https://developers.weixin.qq.com/community/minihome/doc/000c2c34068880629ced91a2f56001
|
||
IsSnapShotUser int `json:"is_snapshotuser"`
|
||
|
||
// UnionID 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段。
|
||
// 公众号文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842
|
||
UnionID string `json:"unionid"`
|
||
}
|
||
|
||
// GetUserAccessToken 通过网页授权的code 换取access_token(区别于context中的access_token)
|
||
func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err error) {
|
||
urlStr := fmt.Sprintf(accessTokenURL, oauth.AppID, oauth.AppSecret, code)
|
||
var response []byte
|
||
response, err = util.HTTPGet(urlStr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = json.Unmarshal(response, &result)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if result.ErrCode != 0 {
|
||
err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// RefreshAccessToken 刷新access_token
|
||
func (oauth *Oauth) RefreshAccessToken(refreshToken string) (result ResAccessToken, err error) {
|
||
urlStr := fmt.Sprintf(refreshAccessTokenURL, oauth.AppID, refreshToken)
|
||
var response []byte
|
||
response, err = util.HTTPGet(urlStr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = json.Unmarshal(response, &result)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if result.ErrCode != 0 {
|
||
err = fmt.Errorf("GetUserAccessToken error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||
return
|
||
}
|
||
return
|
||
}
|
||
|
||
// CheckAccessToken 检验access_token是否有效
|
||
func (oauth *Oauth) CheckAccessToken(accessToken, openID string) (b bool, err error) {
|
||
urlStr := fmt.Sprintf(checkAccessTokenURL, accessToken, openID)
|
||
var response []byte
|
||
response, err = util.HTTPGet(urlStr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
var result util.CommonError
|
||
err = json.Unmarshal(response, &result)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if result.ErrCode != 0 {
|
||
b = false
|
||
return
|
||
}
|
||
b = true
|
||
return
|
||
}
|
||
|
||
// UserInfo 用户授权获取到用户信息
|
||
type UserInfo struct {
|
||
util.CommonError
|
||
|
||
OpenID string `json:"openid"`
|
||
Nickname string `json:"nickname"`
|
||
Sex int32 `json:"sex"`
|
||
Province string `json:"province"`
|
||
City string `json:"city"`
|
||
Country string `json:"country"`
|
||
HeadImgURL string `json:"headimgurl"`
|
||
Privilege []string `json:"privilege"`
|
||
Unionid string `json:"unionid"`
|
||
}
|
||
|
||
// GetUserInfo 如果scope为 snsapi_userinfo 则可以通过此方法获取到用户基本信息
|
||
func (oauth *Oauth) GetUserInfo(accessToken, openID, lang string) (result UserInfo, err error) {
|
||
if lang == "" {
|
||
lang = "zh_CN"
|
||
}
|
||
urlStr := fmt.Sprintf(userInfoURL, accessToken, openID, lang)
|
||
var response []byte
|
||
response, err = util.HTTPGet(urlStr)
|
||
if err != nil {
|
||
return
|
||
}
|
||
err = json.Unmarshal(response, &result)
|
||
if err != nil {
|
||
return
|
||
}
|
||
if result.ErrCode != 0 {
|
||
err = fmt.Errorf("GetUserInfo error : errcode=%v , errmsg=%v", result.ErrCode, result.ErrMsg)
|
||
return
|
||
}
|
||
return
|
||
}
|