350 lines
9.3 KiB
Go
350 lines
9.3 KiB
Go
package jdapi
|
||
|
||
import (
|
||
"crypto/md5"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"sort"
|
||
"strconv"
|
||
"time"
|
||
|
||
"git.rosy.net.cn/baseapi/platform/common"
|
||
|
||
"git.rosy.net.cn/baseapi/utils"
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
const (
|
||
clientTimeout = time.Second * 10
|
||
sleepSecondWhenLimited = 6 * time.Second
|
||
maxRetryCountWhenNetworkException = 3
|
||
maxRetryCountWhenReachLimited = 10
|
||
)
|
||
|
||
const (
|
||
JD_PARAM_JSON = "jd_param_json"
|
||
)
|
||
|
||
const (
|
||
// JDErrorCodeSuccess 操作成功
|
||
JDErrorCodeSuccess = "0"
|
||
// JDerrorCodeAccessFailed 操作失败,请重试,如还不能正常提供服务,可以提交工单(https://opengd.jd.com)详细说明一下,开放平台跟进回复。
|
||
JDerrorCodeAccessFailed = "-1"
|
||
// JDerrorCodeFailedCanAutoRetry 操作失败,系统会自动重试,如还不能正常提供服务,可以提交工单(https://opengd.jd.com)详细说明一下,开放平台跟进回复。
|
||
JDerrorCodeFailedCanAutoRetry = "-10000"
|
||
JDerrorCodeFailedAccessDB = "10000"
|
||
// JDerrorCodeMissingMandatoryParam 请求参数中缺失必填项信息,请检查,如不清楚,请访问到家开放平台(https://opendj.jd.com)API分组有接口详细说明。
|
||
JDerrorCodeMissingMandatoryParam = "10005"
|
||
JDerrorCodeInvalidToken = "10013"
|
||
JDerrorCodeInvalidSign = "10014"
|
||
JDerrorCodeAbnormalParam = "10015"
|
||
JDerrorCodeMethodNotExist = "10018"
|
||
JDErrorCodeExceedLimit = "10032"
|
||
|
||
JDErrorCodeTimeout = "100022"
|
||
JDErrorCodeTomcateFailed = "100023"
|
||
JDErrorCodeLoadUnexpected = "100024"
|
||
)
|
||
|
||
const (
|
||
jdAPIURL = "https://openo2o.jd.com/djapi/%s"
|
||
signKey = "sign"
|
||
AllPage = 0
|
||
DefaultPageSize = 50
|
||
)
|
||
|
||
type JDAPI struct {
|
||
token string
|
||
appKey string
|
||
appSecret string
|
||
sugarLogger *zap.SugaredLogger
|
||
client *http.Client
|
||
}
|
||
|
||
var (
|
||
ErrStrInnerCodeIsNotOk = "JD result inner code is not ok"
|
||
|
||
exceedLimitCodes = map[string]int{
|
||
JDErrorCodeExceedLimit: 1,
|
||
}
|
||
|
||
// todo replace all magic number
|
||
canRetryCodes = map[string]int{
|
||
JDerrorCodeFailedCanAutoRetry: 1,
|
||
JDerrorCodeAccessFailed: 1,
|
||
JDerrorCodeFailedAccessDB: 1,
|
||
JDErrorCodeTimeout: 1,
|
||
JDErrorCodeTomcateFailed: 1,
|
||
JDErrorCodeLoadUnexpected: 1,
|
||
}
|
||
|
||
jdResultInnerCodeKeys = []string{"code", "retCode", "errorCode"}
|
||
|
||
jdResultInnerCodeOK = map[string]int{
|
||
"None": 1,
|
||
"0": 1,
|
||
"1": 1,
|
||
"200": 1,
|
||
"190005": 1, // 部分失败
|
||
}
|
||
|
||
jdResultNoPageInnerDataKeys = []string{"result", "data"}
|
||
jdResultPageInner2DataKeys = []string{"result", "resultList"}
|
||
jdResultPageTotalCountKeys = []string{"totalCount", "count"}
|
||
)
|
||
|
||
type PageResultParser func(map[string]interface{}, int) ([]interface{}, int)
|
||
|
||
func (j *JDAPI) signParams(jdParams map[string]string) string {
|
||
var keys []string
|
||
for k := range jdParams {
|
||
if k != "app_secret" && k != signKey {
|
||
keys = append(keys, k)
|
||
}
|
||
}
|
||
|
||
sort.Strings(keys)
|
||
allStr := ""
|
||
for _, k := range keys {
|
||
allStr += k + jdParams[k]
|
||
}
|
||
allStr = jdParams["app_secret"] + allStr + jdParams["app_secret"]
|
||
|
||
return fmt.Sprintf("%X", md5.Sum([]byte(allStr)))
|
||
}
|
||
|
||
func genGetURL(baseURL, apiStr string, params map[string]string) string {
|
||
fullURL := ""
|
||
|
||
if params != nil {
|
||
for k, v := range params {
|
||
if fullURL == "" {
|
||
fullURL = "?"
|
||
} else {
|
||
fullURL += "&"
|
||
}
|
||
fullURL += k + "=" + url.QueryEscape(v)
|
||
}
|
||
}
|
||
|
||
return fmt.Sprintf(baseURL, apiStr) + fullURL
|
||
}
|
||
|
||
func NewJDAPI(token, appKey, appSecret string, sugarLogger *zap.SugaredLogger) *JDAPI {
|
||
return &JDAPI{token, appKey, appSecret, sugarLogger, &http.Client{Timeout: clientTimeout}}
|
||
}
|
||
|
||
func (j *JDAPI) AccessJDQuery(apiStr string, jdParams map[string]string) (retVal map[string]interface{}, err error) {
|
||
params := make(map[string]string)
|
||
params["v"] = "1.0"
|
||
params["format"] = "json"
|
||
params["app_key"] = j.appKey
|
||
params["app_secret"] = j.appSecret
|
||
params["token"] = j.token
|
||
|
||
if jdParams == nil {
|
||
jdParams = make(map[string]string, 0)
|
||
}
|
||
jdParamStr, err := json.Marshal(jdParams)
|
||
if err != nil {
|
||
j.sugarLogger.Errorf("Error when marshal %v, error:%v", jdParams, err)
|
||
return nil, err
|
||
}
|
||
params["jd_param_json"] = string(jdParamStr)
|
||
params["timestamp"] = utils.GetCurTimeStr()
|
||
sign := j.signParams(params)
|
||
params[signKey] = sign
|
||
|
||
url, _ := url.Parse(genGetURL(jdAPIURL, apiStr, params))
|
||
|
||
apiAccess := &common.AccessPlatformAPIWithRetryParams{
|
||
MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited,
|
||
MaxRecoverableRetryCount: maxRetryCountWhenNetworkException,
|
||
SleepSecondWhenExceedLimit: sleepSecondWhenLimited,
|
||
Client: j.client,
|
||
Request: &http.Request{
|
||
Method: "GET",
|
||
URL: url,
|
||
},
|
||
SugarLogger: j.sugarLogger,
|
||
}
|
||
|
||
err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (errLevel string, err error) {
|
||
jsonResult1, err := utils.HttpResponse2Json(response)
|
||
|
||
if err != nil {
|
||
j.sugarLogger.Warnf("HttpResponse2Json return:%v", err)
|
||
return common.PAErrorLevelGeneralFail, err
|
||
}
|
||
|
||
code := jsonResult1["code"].(string)
|
||
if code == JDErrorCodeSuccess {
|
||
retVal = jsonResult1
|
||
return common.PAErrorLevelSuccess, nil
|
||
}
|
||
j.sugarLogger.Debugf("jd code is:%s", code)
|
||
if _, ok := exceedLimitCodes[code]; ok {
|
||
return common.PAErrorLevelExceedLimit, nil
|
||
} else if _, ok := canRetryCodes[code]; ok {
|
||
return common.PAErrorLevelRecoverable, nil
|
||
} else {
|
||
return common.PAErrorLevelGeneralFail, utils.NewErrorCode(jsonResult1["msg"].(string), code)
|
||
}
|
||
|
||
})
|
||
|
||
return retVal, err
|
||
}
|
||
|
||
func (j *JDAPI) AccessJDQueryNoPage(apiStr string, jdParams map[string]string, keyToRemove, keyToKeep []string) (interface{}, error) {
|
||
jsonResult, err := j.AccessJDQuery(apiStr, jdParams)
|
||
if err != nil {
|
||
return jsonResult, err
|
||
}
|
||
|
||
var data map[string]interface{}
|
||
err = utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data)
|
||
|
||
if err != nil {
|
||
return jsonResult, err
|
||
}
|
||
|
||
innerCode := ""
|
||
for _, innerCodeKey := range jdResultInnerCodeKeys {
|
||
if innerCode2, ok := data[innerCodeKey]; ok {
|
||
innerCode = forceInnerCode2Str(innerCode2)
|
||
break
|
||
}
|
||
}
|
||
|
||
if _, ok := jdResultInnerCodeOK[innerCode]; ok {
|
||
for _, innerDataKey := range jdResultNoPageInnerDataKeys {
|
||
if innerData, ok := data[innerDataKey]; ok {
|
||
return utils.DictKeysMan(innerData, keyToRemove, keyToKeep), nil
|
||
}
|
||
}
|
||
panic("Can not find inner data")
|
||
} else {
|
||
return jsonResult, utils.NewErrorCode(ErrStrInnerCodeIsNotOk, innerCode)
|
||
}
|
||
}
|
||
|
||
func NormalJDQueryHavePageResultParser(data map[string]interface{}, totalCount int) ([]interface{}, int) {
|
||
var result map[string]interface{}
|
||
var retVal []interface{}
|
||
|
||
tempResult := data["result"]
|
||
if resultStr, ok := tempResult.(string); ok {
|
||
if err := utils.UnmarshalUseNumber([]byte(resultStr), &tempResult); err != nil {
|
||
panic("Wrong format")
|
||
}
|
||
}
|
||
|
||
result = tempResult.(map[string]interface{})
|
||
|
||
if totalCount == 0 {
|
||
for _, totalCountKey := range jdResultPageTotalCountKeys {
|
||
if totalCount2, ok := result[totalCountKey]; ok {
|
||
totalCountInt64, _ := totalCount2.(json.Number).Int64()
|
||
totalCount = int(totalCountInt64)
|
||
if totalCount == 0 {
|
||
return make([]interface{}, 0), 0
|
||
}
|
||
break
|
||
}
|
||
}
|
||
if totalCount == 0 {
|
||
panic("can not find totalCount key")
|
||
}
|
||
}
|
||
|
||
for _, inner2ResultKey := range jdResultPageInner2DataKeys {
|
||
if inner2Result, ok := result[inner2ResultKey]; ok {
|
||
|
||
if inner2ResultStr, ok := inner2Result.(string); ok {
|
||
err := utils.UnmarshalUseNumber([]byte(inner2ResultStr), &retVal)
|
||
if err != nil {
|
||
panic("can not unmarshal inner2result")
|
||
}
|
||
} else {
|
||
retVal = inner2Result.([]interface{})
|
||
}
|
||
|
||
return retVal, totalCount
|
||
}
|
||
}
|
||
|
||
panic("wrong format")
|
||
}
|
||
|
||
func (j *JDAPI) AccessJDQueryHavePage(apiStr string, jdParams map[string]string, keyToRemove, keyToKeep []string, pageResultParser PageResultParser) ([]interface{}, error) {
|
||
if pageResultParser == nil {
|
||
pageResultParser = NormalJDQueryHavePageResultParser
|
||
}
|
||
|
||
localJdParams := make(map[string]string)
|
||
if jdParams != nil && len(jdParams) > 0 {
|
||
for k, v := range jdParams {
|
||
localJdParams[k] = v
|
||
}
|
||
}
|
||
|
||
totalCount := 0
|
||
pageNo := AllPage
|
||
pageSize := DefaultPageSize
|
||
if tempPageNo, ok := localJdParams["pageNo"]; ok {
|
||
pageNo, _ = strconv.Atoi(tempPageNo)
|
||
}
|
||
if tempPageSize, ok := localJdParams["pageSize"]; ok {
|
||
pageSize, _ = strconv.Atoi(tempPageSize)
|
||
}
|
||
|
||
curPage := pageNo
|
||
if curPage == AllPage {
|
||
curPage = 1
|
||
}
|
||
|
||
retVal := make([]interface{}, 0)
|
||
for {
|
||
localJdParams["pageNo"] = strconv.FormatInt(int64(curPage), 10)
|
||
localJdParams["pageSize"] = strconv.FormatInt(int64(pageSize), 10)
|
||
jsonResult, err := j.AccessJDQuery(apiStr, localJdParams)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var data map[string]interface{}
|
||
err = utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data)
|
||
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
innerCode := forceInnerCode2Str(data["code"])
|
||
if innerCode != "0" && innerCode != "200" {
|
||
return nil, utils.NewErrorCode(ErrStrInnerCodeIsNotOk, innerCode)
|
||
}
|
||
|
||
inResult, totalCount2 := pageResultParser(data, totalCount)
|
||
totalCount = totalCount2
|
||
|
||
inResult = utils.DictKeysMan(inResult, keyToRemove, keyToKeep).([]interface{})
|
||
retVal = append(retVal, inResult...)
|
||
|
||
if curPage == pageNo || curPage*pageSize >= totalCount {
|
||
break
|
||
}
|
||
curPage++
|
||
}
|
||
|
||
return retVal, nil
|
||
}
|
||
|
||
func forceInnerCode2Str(innerCode interface{}) string {
|
||
if innerCodeStr, ok := innerCode.(string); ok {
|
||
return innerCodeStr
|
||
}
|
||
return utils.Int64ToStr(utils.MustInterface2Int64(innerCode))
|
||
}
|