From 13e5b6b44f352d1269202ff40587e2fc99b30f75 Mon Sep 17 00:00:00 2001 From: gazebo Date: Tue, 4 Sep 2018 15:52:29 +0800 Subject: [PATCH] - weixinsnsapi added. --- platformapi/weixinapi/weixinapi.go | 8 + platformapi/weixinsnsapi/weixinsnsapi.go | 207 ++++++++++++++++++ platformapi/weixinsnsapi/weixinsnsapi_test.go | 31 +++ 3 files changed, 246 insertions(+) create mode 100644 platformapi/weixinsnsapi/weixinsnsapi.go create mode 100644 platformapi/weixinsnsapi/weixinsnsapi_test.go diff --git a/platformapi/weixinapi/weixinapi.go b/platformapi/weixinapi/weixinapi.go index f5926c79..cb26739e 100644 --- a/platformapi/weixinapi/weixinapi.go +++ b/platformapi/weixinapi/weixinapi.go @@ -54,6 +54,14 @@ func New(appID, secret string, config ...*platformapi.APIConfig) *API { } } +func (a *API) GetAppID() string { + return a.appID +} + +func (a *API) GetSecret() string { + return a.secret +} + func (a *API) SetToken(newToken string) bool { curToken := a.GetToken() if curToken != newToken { diff --git a/platformapi/weixinsnsapi/weixinsnsapi.go b/platformapi/weixinsnsapi/weixinsnsapi.go new file mode 100644 index 00000000..b65ba9d6 --- /dev/null +++ b/platformapi/weixinsnsapi/weixinsnsapi.go @@ -0,0 +1,207 @@ +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 string `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: utils.Interface2String(result["sex"]), + Province: utils.Interface2String(result["province"]), + City: utils.Interface2String(result["city"]), + Country: utils.Interface2String(result["country"]), + HeadImgURL: utils.Interface2String(result["headimgurl"]), + Privilege: utils.Interface2String(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 +} diff --git a/platformapi/weixinsnsapi/weixinsnsapi_test.go b/platformapi/weixinsnsapi/weixinsnsapi_test.go new file mode 100644 index 00000000..e94148e8 --- /dev/null +++ b/platformapi/weixinsnsapi/weixinsnsapi_test.go @@ -0,0 +1,31 @@ +package weixinsnsapi + +import ( + "testing" + + "git.rosy.net.cn/baseapi" + + "go.uber.org/zap" +) + +var ( + api *API + sugarLogger *zap.SugaredLogger +) + +func init() { + logger, _ := zap.NewDevelopment() + sugarLogger = logger.Sugar() + baseapi.Init(sugarLogger) + + // sandbox + api = New("wxbf235770edaabc5c", "ba32b269a068a5b72486a0beafd171e8") +} + +func TestRefreshToken(t *testing.T) { + result, err := api.RefreshToken("code") + if err != nil || result.ExpiresIn != 7200 { + t.Fatal(err.Error()) + } + sugarLogger.Debug(result) +}