1
0
mirror of https://github.com/silenceper/wechat.git synced 2026-02-09 07:02:27 +08:00
Files
wechat/officialaccount/oauth/oauth.go

204 lines
6.9 KiB
Go

package oauth
import (
ctx2 "context"
"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"`
}
// GetUserInfoByCodeContext 通过网页授权的 code 换取用户的信息
func (oauth *Oauth) GetUserInfoByCodeContext(ctx ctx2.Context, code string) (result UserInfo, err error) {
var (
token ResAccessToken
)
if token, err = oauth.GetUserAccessTokenContext(ctx, code); err != nil {
return
}
return oauth.GetUserInfoContext(ctx, token.AccessToken, token.OpenID, "")
}
// GetUserAccessToken 通过网页授权的 code 换取 access_token(区别于 context 中的 access_token)
func (oauth *Oauth) GetUserAccessToken(code string) (result ResAccessToken, err error) {
return oauth.GetUserAccessTokenContext(ctx2.Background(), code)
}
// GetUserAccessTokenContext 通过网页授权的 code 换取 access_token(区别于 context 中的 access_token) with context
func (oauth *Oauth) GetUserAccessTokenContext(ctx ctx2.Context, code string) (result ResAccessToken, err error) {
urlStr := fmt.Sprintf(accessTokenURL, oauth.AppID, oauth.AppSecret, code)
var response []byte
response, err = util.HTTPGetContext(ctx, 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) {
return oauth.RefreshAccessTokenContext(ctx2.Background(), refreshToken)
}
// RefreshAccessTokenContext 刷新 access_token with context
func (oauth *Oauth) RefreshAccessTokenContext(ctx ctx2.Context, refreshToken string) (result ResAccessToken, err error) {
urlStr := fmt.Sprintf(refreshAccessTokenURL, oauth.AppID, refreshToken)
var response []byte
response, err = util.HTTPGetContext(ctx, 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) {
return oauth.CheckAccessTokenContext(ctx2.Background(), accessToken, openID)
}
// CheckAccessTokenContext 检验 access_token 是否有效 with context
func (oauth *Oauth) CheckAccessTokenContext(ctx ctx2.Context, accessToken, openID string) (b bool, err error) {
urlStr := fmt.Sprintf(checkAccessTokenURL, accessToken, openID)
var response []byte
response, err = util.HTTPGetContext(ctx, 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) {
return oauth.GetUserInfoContext(ctx2.Background(), accessToken, openID, lang)
}
// GetUserInfoContext 如果 scope 为 snsapi_userinfo 则可以通过此方法获取到用户基本信息 with context
func (oauth *Oauth) GetUserInfoContext(ctx ctx2.Context, 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.HTTPGetContext(ctx, 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
}