package weixinsnsapi import ( "errors" "net/http" "strings" "sync" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) const ( prodURL = "https://api.weixin.qq.com/sns" ) const ( actionGetToken = "oauth2/access_token" actionRefreshToken = "oauth2/refresh_token" ) const ( ResponseCodeBusy = -1 ResponseCodeSuccess = 0 ) type TokenInfo struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token"` OpenID string `json:"openid"` Scope string `json:"scope"` } type ErrorInfo struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } type API struct { tokenInfo *TokenInfo appID string secret string client *http.Client config *platformapi.APIConfig locker sync.RWMutex } type UserInfo struct { OpenID string `json:"openid"` NickName string `json:"nickname"` Sex int `json:"sex"` Province string `json:"province"` City string `json:"city"` Country string `json:"country"` HeadImgURL string `json:"headimgurl"` Privilege interface{} `json:"privilege"` UnionID string `json:"unionid"` } var ( ErrCodeAndRefreshTokenAllEmpty = errors.New("code and refresh are all empty") ) func New(appID, secret string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { curConfig = *config[0] } return &API{ appID: appID, secret: secret, client: &http.Client{Timeout: curConfig.ClientTimeout}, config: &curConfig, } } func (a *API) SetToken(newToken *TokenInfo) bool { a.locker.Lock() defer a.locker.Unlock() a.tokenInfo = newToken return true } func (a *API) GetToken() *TokenInfo { a.locker.RLock() defer a.locker.RUnlock() return a.tokenInfo } func (a *API) AccessAPI(action string, params map[string]interface{}, body string) (retVal map[string]interface{}, err error) { if params != nil && body != "" { panic("params and body can not all non-empty") } params2 := make(map[string]interface{}) for k, v := range params { params2[k] = v } if action == actionGetToken || action == actionRefreshToken { params2["appid"] = a.appID params2["secret"] = a.secret } else { accessToken := a.GetToken() if accessToken == nil { panic("token is empty!") } params2["access_token"] = accessToken.AccessToken // params2["openid"] = accessToken.OpenID } fullURL := utils.GenerateGetURL(prodURL, action, params2) // baseapi.SugarLogger.Debug(fullURL) err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { var request *http.Request if body == "" { request, _ = http.NewRequest(http.MethodGet, fullURL, nil) } else { request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(body)) } request.Close = true // todo try to fix EOF error when accessing weixin api. return request }, a.config, func(response *http.Response) (result string, err error) { jsonResult1, err := utils.HTTPResponse2Json(response) if err != nil { return platformapi.ErrLevelGeneralFail, platformapi.ErrResponseDataFormatWrong } var errInfo *ErrorInfo // 微信的返回值,在错误与正常情况下,结构是完全不一样的 if errCode, ok := jsonResult1["errcode"]; ok { errInfo = &ErrorInfo{ ErrCode: int(utils.MustInterface2Int64(errCode)), ErrMsg: jsonResult1["errmsg"].(string), } if errInfo.ErrCode == 0 { retVal = jsonResult1 } } else { retVal = jsonResult1 } if retVal != nil { return platformapi.ErrLevelSuccess, nil } newErr := utils.NewErrorIntCode(errInfo.ErrMsg, errInfo.ErrCode) if errInfo.ErrCode == ResponseCodeBusy { return platformapi.ErrLevelRecoverableErr, newErr } return platformapi.ErrLevelCodeIsNotOK, newErr }) return retVal, err } func (a *API) RefreshToken(code string) (tokenInfo *TokenInfo, err error) { var result map[string]interface{} if code != "" { result, err = a.AccessAPI(actionGetToken, utils.Params2Map("grant_type", "authorization_code", "code", code), "") } else { token := a.GetToken() if token != nil { result, err = a.AccessAPI(actionRefreshToken, utils.Params2Map("grant_type", "refresh_token", "refresh_token", token.RefreshToken), "") } else { return nil, ErrCodeAndRefreshTokenAllEmpty } } if err != nil { return nil, err } tokenInfo = &TokenInfo{ AccessToken: utils.Interface2String(result["access_token"]), ExpiresIn: int(utils.MustInterface2Int64(result["expires_in"])), RefreshToken: utils.Interface2String(result["refresh_token"]), OpenID: utils.Interface2String(result["openid"]), Scope: utils.Interface2String(result["scope"]), } // update my token too. a.SetToken(tokenInfo) return tokenInfo, nil } func (a *API) GetUserInfo(openid string) (*UserInfo, error) { result, err := a.AccessAPI("userinfo", utils.Params2Map("openid", openid), "") if err == nil { retVal := &UserInfo{ OpenID: utils.Interface2String(result["openid"]), NickName: utils.Interface2String(result["nickname"]), Sex: int(utils.MustInterface2Int64(result["sex"])), Province: utils.Interface2String(result["province"]), City: utils.Interface2String(result["city"]), Country: utils.Interface2String(result["country"]), HeadImgURL: utils.Interface2String(result["headimgurl"]), Privilege: result["privilege"], UnionID: utils.Interface2String(result["unionid"]), } return retVal, nil } return nil, err } func (a *API) IsOpenIDValid(openid string) (bool, error) { _, err := a.AccessAPI("auth", utils.Params2Map("openid", openid), "") if err == nil { return true, nil } return false, err }