package weimobapi import ( "bytes" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "sync" "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) const ( ResponseCodeSuccess = "0" ResponseCodeExceedCallFrequency = "8000103" ) const ( prodURL = "https://dopen.weimob.com/api/1_0/ec" mediaURL = "https://dopen.weimob.com/media/1_0/ec" authURL = "https://dopen.weimob.com/fuwu/b" accessTokenAPI = "oauth2/token" uploadImgAPI = "goodsImage/uploadImg" ) const ( // sku KeySkuID = "skuId" KeyOuterSkuCode = "outerSkuCode" KeyImageURL = "imageUrl" KeySalePrice = "salePrice" KeyOriginalPrice = "originalPrice" KeyCostPrice = "costPrice" KeyEditStockNum = "editStockNum" KeyB2cSku = "b2cSku" KeySkuAttrMap = "skuAttrMap" ) type TokenInfo struct { TokenType string `json:"token_type"` AccessToken string `json:"access_token"` ExpiresIn int `json:"expires_in"` RefreshToken string `json:"refresh_token"` RefreshTokenExpiresIn int `json:"refresh_token_expires_in"` Scope string `json:"scope"` PublicAccountID string `json:"public_account_id"` BusinessID string `json:"business_id"` } type API struct { token *TokenInfo appID string appSecret string client *http.Client config *platformapi.APIConfig locker sync.RWMutex } var ( exceedLimitCodes = map[string]int{ ResponseCodeExceedCallFrequency: 1, } canRetryCodes = map[string]int{} ) func New(token *TokenInfo, appID, appSecret string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { curConfig = *config[0] } return &API{ token: token, appID: appID, appSecret: appSecret, client: &http.Client{Timeout: curConfig.ClientTimeout}, config: &curConfig, } } func (a *API) SetToken(newToken *TokenInfo) (retVal bool) { a.locker.Lock() defer a.locker.Unlock() a.token = newToken retVal = true return retVal } func (a *API) GetToken() *TokenInfo { a.locker.RLock() defer a.locker.RUnlock() return a.token } func (a *API) MustGetToken() *TokenInfo { token := a.GetToken() if token == nil { panic("token is nil") } return token } func (a *API) AccessAPI(apiStr string, apiParams map[string]interface{}) (retVal interface{}, err error) { err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { var request *http.Request if apiStr == accessTokenAPI { fullURL := utils.GenerateGetURL(authURL, apiStr, utils.MergeMaps(apiParams, map[string]interface{}{ "client_id": a.appID, "client_secret": a.appSecret, })) request, _ = http.NewRequest(http.MethodPost, fullURL, nil) } else { if apiStr == uploadImgAPI { fullURL := utils.GenerateGetURL(mediaURL, apiStr, utils.Params2Map("accesstoken", a.MustGetToken().AccessToken)) var b bytes.Buffer w := multipart.NewWriter(&b) imgData := apiParams["file"].([]byte) imgName, _ := apiParams["name"].(string) if fw, err := w.CreateFormFile("file", imgName); err != nil { panic(err.Error()) } else { fw.Write(imgData) } w.Close() // b.WriteString(utils.Map2URLValues(params).Encode()) request, _ = http.NewRequest(http.MethodPost, fullURL, &b) request.Header.Set("Content-Type", w.FormDataContentType()) } else { fullURL := utils.GenerateGetURL(prodURL, apiStr, utils.Params2Map("accesstoken", a.MustGetToken().AccessToken)) var body io.Reader if apiParams != nil { apiParamsBytes, err := json.Marshal(apiParams) if err != nil { panic(fmt.Sprintf("Error when marshal %v, error:%v", apiParams, err)) } body = bytes.NewReader(apiParamsBytes) } request, _ = http.NewRequest(http.MethodPost, fullURL, body) request.Header.Set("Content-Type", "application/json; charset=utf-8") request.Header.Set("Content-Encoding", "gzip, deflate") request.Header.Set("User-Agent", "weimob-golang-api") // request.Close = true //todo 为了性能考虑还是不要关闭 } } return request }, a.config, func(jsonResult1 map[string]interface{}) (errLevel string, err error) { // baseapi.SugarLogger.Debug(utils.Format4Output(jsonResult1, false)) code := ResponseCodeSuccess errMsg := "" if apiStr == accessTokenAPI { // token相关出错到不了这里,因为当取token发生错误时,HTTP CODE不是200 if errCode, ok := jsonResult1["error"]; ok { code = getStringCode(errCode) errMsg = jsonResult1["error_description"].(string) } if code == ResponseCodeSuccess { retVal = jsonResult1 return platformapi.ErrLevelSuccess, nil } } else { if errMap, ok := jsonResult1["code"].(map[string]interface{}); ok { code = getStringCode(errMap["errcode"]) errMsg = errMap["errmsg"].(string) } if code == ResponseCodeSuccess { retVal = jsonResult1["data"] return platformapi.ErrLevelSuccess, nil } } newErr := utils.NewErrorCode(errMsg, code) if _, ok := exceedLimitCodes[code]; ok { return platformapi.ErrLevelExceedLimit, newErr } else if _, ok := canRetryCodes[code]; ok { return platformapi.ErrLevelRecoverableErr, newErr } else { baseapi.SugarLogger.Debugf("weimob AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) return platformapi.ErrLevelCodeIsNotOK, newErr } }) return retVal, err } func getStringCode(code interface{}) string { if codeStr, ok := code.(string); ok { return codeStr } return utils.Int64ToStr(utils.Interface2Int64WithDefault(code, 0)) } // redirectURL只是作为校验,要求转入得到此CODE时给的redirectURL,不包括参数部分 func (a *API) RefreshTokenByCode(code, redirectURL string) (retVal *TokenInfo, err error) { result, err := a.AccessAPI(accessTokenAPI, map[string]interface{}{ "grant_type": "authorization_code", "code": code, "redirect_uri": redirectURL, }) if err == nil { retVal = map2TokenInfo(result.(map[string]interface{})) a.SetToken(retVal) return retVal, nil } return nil, err } func (a *API) RefreshTokenByRefreshToken() (retVal *TokenInfo, err error) { curToken := a.GetToken() if curToken != nil { result, err := a.AccessAPI(accessTokenAPI, map[string]interface{}{ "grant_type": "refresh_token", "refresh_token": curToken.RefreshToken, }) if err == nil { retVal = map2TokenInfo(result.(map[string]interface{})) a.SetToken(retVal) return retVal, nil } return nil, err } return nil, fmt.Errorf("刷新TOKEN要求已经有TOKEN") } func map2TokenInfo(mapData map[string]interface{}) *TokenInfo { return &TokenInfo{ TokenType: utils.Interface2String(mapData["token_type"]), AccessToken: utils.Interface2String(mapData["access_token"]), ExpiresIn: int(utils.MustInterface2Int64(mapData["expires_in"])), RefreshToken: utils.Interface2String(mapData["refresh_token"]), RefreshTokenExpiresIn: int(utils.MustInterface2Int64(mapData["refresh_token_expires_in"])), Scope: utils.Interface2String(mapData["scope"]), PublicAccountID: utils.Interface2String(mapData["public_account_id"]), BusinessID: utils.Interface2String(mapData["business_id"]), } }