255 lines
7.6 KiB
Go
255 lines
7.6 KiB
Go
package weimobapi
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"sync"
|
||
|
||
"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"
|
||
KeyAvailableStockNum = "availableStockNum"
|
||
KeySkuList = "skuList"
|
||
KeyB2cSku = "b2cSku"
|
||
KeySkuAttrMap = "skuAttrMap"
|
||
|
||
KeyField = "field"
|
||
KeySort = "sort"
|
||
|
||
KeyGoodsClassifyId = "goodsClassifyId"
|
||
KeySearch = "search"
|
||
KeyGoodsStatus = "goodsStatus"
|
||
KeyUpdateStartTime = "updateStartTime"
|
||
KeyUpdateEndTime = "updateEndTime"
|
||
)
|
||
|
||
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(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
|
||
if jsonResult1 == nil {
|
||
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
|
||
}
|
||
// 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 {
|
||
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"]),
|
||
}
|
||
}
|