package weixinapi import ( "net/http" "strings" "sync" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) const ( prodURL = "https://api.weixin.qq.com/cgi-bin" ) const ( actionGetToken = "token" ) const ( ResponseCodeBusy = -1 ResponseCodeSuccess = 0 ) type TokenInfo struct { AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` } type ErrorInfo struct { ErrCode int `json:"errcode"` ErrMsg string `json:"errmsg"` } type API struct { token string appID string secret string client *http.Client config *platformapi.APIConfig locker sync.RWMutex } 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) 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 { a.locker.Lock() defer a.locker.Unlock() a.token = newToken return true } return false } func (a *API) GetToken() string { a.locker.RLock() defer a.locker.RUnlock() return a.token } 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 { params2["appid"] = a.appID params2["secret"] = a.secret } else { accessToken := a.GetToken() if accessToken == "" { panic("token is empty!") } params2["access_token"] = accessToken } 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() (tokenInfo *TokenInfo, err error) { result, err := a.AccessAPI(actionGetToken, utils.Params2Map("grant_type", "client_credential"), "") if err != nil { return nil, err } tokenInfo = &TokenInfo{ AccessToken: utils.Interface2String(result["access_token"]), ExpiresIn: int(utils.MustInterface2Int64(result["expires_in"])), } // update my token too. a.SetToken(tokenInfo.AccessToken) return tokenInfo, nil } func (a *API) MessageTemplateSend(userOpneID, templateID, downloadURL string, miniProgram, data interface{}) (err error) { bodyJson := map[string]interface{}{ "touser": userOpneID, "template_id": templateID, "url": downloadURL, "data": data, } if downloadURL != "" { bodyJson["url"] = downloadURL } if miniProgram != nil { bodyJson["miniprogram"] = miniProgram } _, err = a.AccessAPI("message/template/send", nil, string(utils.MustMarshal(bodyJson))) return err }