343 lines
9.5 KiB
Go
343 lines
9.5 KiB
Go
package jdapi
|
||
|
||
import (
|
||
"crypto/md5"
|
||
"encoding/json"
|
||
"fmt"
|
||
"net/http"
|
||
"net/url"
|
||
"sort"
|
||
|
||
"git.rosy.net.cn/baseapi"
|
||
|
||
"git.rosy.net.cn/baseapi/platformapi"
|
||
"git.rosy.net.cn/baseapi/utils"
|
||
)
|
||
|
||
const (
|
||
paramJson = "jd_param_json"
|
||
)
|
||
|
||
const (
|
||
// ResponseCodeSuccess 操作成功
|
||
ResponseCodeSuccess = "0"
|
||
// ResponseCodeAccessFailed 操作失败,请重试,如还不能正常提供服务,可以提交工单(https://opengd.jd.com)详细说明一下,开放平台跟进回复。
|
||
ResponseCodeAccessFailed = "-1"
|
||
// ResponseCodeFailedCanAutoRetry 操作失败,系统会自动重试,如还不能正常提供服务,可以提交工单(https://opengd.jd.com)详细说明一下,开放平台跟进回复。
|
||
ResponseCodeFailedCanAutoRetry = "-10000"
|
||
ResponseCodeFailedAccessDB = "10000"
|
||
// ResponseCodeMissingMandatoryParam 请求参数中缺失必填项信息,请检查,如不清楚,请访问到家开放平台(https://opendj.jd.com)API分组有接口详细说明。
|
||
ResponseCodeMissingMandatoryParam = "10005"
|
||
ResponseCodeInvalidToken = "10013"
|
||
ResponseCodeInvalidSign = "10014"
|
||
ResponseCodeAbnormalParam = "10015"
|
||
ResponseCodeMethodNotExist = "10018"
|
||
ResponseCodeExceedLimit = "10032"
|
||
|
||
ResponseCodeTimeout = "100022"
|
||
ResponseCodeTomcateFailed = "100023"
|
||
ResponseCodeLoadUnexpected = "100024"
|
||
)
|
||
|
||
const (
|
||
prodURL = "https://openo2o.jd.com/djapi/%s"
|
||
signKey = "sign"
|
||
AllPage = 0
|
||
DefaultPageSize = 50
|
||
)
|
||
|
||
type API struct {
|
||
token string
|
||
appKey string
|
||
appSecret string
|
||
client *http.Client
|
||
config *platformapi.APIConfig
|
||
}
|
||
|
||
var (
|
||
InnerCodeIsNotOk = "JD result inner code is not ok"
|
||
|
||
exceedLimitCodes = map[string]int{
|
||
ResponseCodeExceedLimit: 1,
|
||
}
|
||
|
||
// todo replace all magic number
|
||
canRetryCodes = map[string]int{
|
||
ResponseCodeFailedCanAutoRetry: 1,
|
||
ResponseCodeAccessFailed: 1,
|
||
ResponseCodeFailedAccessDB: 1,
|
||
ResponseCodeTimeout: 1,
|
||
ResponseCodeTomcateFailed: 1,
|
||
ResponseCodeLoadUnexpected: 1,
|
||
}
|
||
|
||
innerCodeKeys = []string{"code", "retCode", "errorCode"}
|
||
|
||
innerCodeOKCodes = map[string]int{
|
||
"None": 1,
|
||
"0": 1,
|
||
"1": 1,
|
||
"200": 1,
|
||
"190005": 1, // 部分失败
|
||
}
|
||
|
||
noPageInnerDataKeys = []string{"result", "data"}
|
||
havePageInner2DataKeys = []string{"result", "resultList"}
|
||
havePageTotalCountKeys = []string{"totalCount", "count"}
|
||
)
|
||
|
||
type PageResultParser func(map[string]interface{}, int) ([]interface{}, int, error)
|
||
|
||
func (a *API) signParams(jdParams map[string]interface{}) string {
|
||
var keys []string
|
||
for k := range jdParams {
|
||
if k != "app_secret" && k != signKey {
|
||
keys = append(keys, k)
|
||
}
|
||
}
|
||
|
||
sort.Strings(keys)
|
||
secretStr := fmt.Sprint(jdParams["app_secret"])
|
||
allStr := secretStr
|
||
for _, k := range keys {
|
||
allStr += k + fmt.Sprint(jdParams[k])
|
||
}
|
||
allStr = allStr + secretStr
|
||
|
||
return fmt.Sprintf("%X", md5.Sum([]byte(allStr)))
|
||
}
|
||
|
||
func generateURL(baseURL, apiStr string, params map[string]interface{}) string {
|
||
fullURL := ""
|
||
|
||
if params != nil {
|
||
for k, v := range params {
|
||
if fullURL == "" {
|
||
fullURL = "?"
|
||
} else {
|
||
fullURL += "&"
|
||
}
|
||
fullURL += k + "=" + url.QueryEscape(fmt.Sprint(v))
|
||
}
|
||
}
|
||
|
||
return fmt.Sprintf(baseURL, apiStr) + fullURL
|
||
}
|
||
|
||
func New(token, appKey, appSecret string, config ...*platformapi.APIConfig) *API {
|
||
curConfig := platformapi.DefAPIConfig
|
||
if len(config) > 0 {
|
||
curConfig = *config[0]
|
||
}
|
||
return &API{
|
||
token: token,
|
||
appKey: appKey,
|
||
appSecret: appSecret,
|
||
client: &http.Client{Timeout: curConfig.ClientTimeout},
|
||
config: &curConfig,
|
||
}
|
||
}
|
||
|
||
func (a *API) AccessAPI(apiStr string, jdParams map[string]interface{}) (retVal map[string]interface{}, err error) {
|
||
params := make(map[string]interface{})
|
||
params["v"] = "1.0"
|
||
params["format"] = "json"
|
||
params["app_key"] = a.appKey
|
||
params["app_secret"] = a.appSecret
|
||
params["token"] = a.token
|
||
|
||
if jdParams == nil {
|
||
jdParams = make(map[string]interface{}, 0)
|
||
}
|
||
jdParamStr, err := json.Marshal(jdParams)
|
||
if err != nil {
|
||
baseapi.SugarLogger.Errorf("Error when marshal %v, error:%v", jdParams, err)
|
||
return nil, err
|
||
}
|
||
params["jd_param_json"] = string(jdParamStr)
|
||
params["timestamp"] = utils.GetCurTimeStr()
|
||
sign := a.signParams(params)
|
||
params[signKey] = sign
|
||
|
||
url, _ := url.Parse(generateURL(prodURL, apiStr, params))
|
||
request := &http.Request{
|
||
Method: "GET",
|
||
URL: url,
|
||
}
|
||
err = platformapi.AccessPlatformAPIWithRetry(a.client, request, a.config, func(response *http.Response) (errLevel string, err error) {
|
||
jsonResult1, err := utils.HTTPResponse2Json(response)
|
||
if err != nil {
|
||
return platformapi.ErrLevelGeneralFail, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
code := jsonResult1["code"].(string)
|
||
if code == ResponseCodeSuccess {
|
||
retVal = jsonResult1
|
||
return platformapi.ErrLevelSuccess, nil
|
||
}
|
||
baseapi.SugarLogger.Warnf("response business code is not ok, data:%v, code:%v", jsonResult1, code)
|
||
newErr := utils.NewErrorCode(jsonResult1["msg"].(string), 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 (a *API) AccessAPINoPage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string) (interface{}, error) {
|
||
jsonResult, err := a.AccessAPI(apiStr, jdParams)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
var data map[string]interface{}
|
||
if err := utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data); err != nil {
|
||
return nil, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
|
||
innerCode := ""
|
||
for _, innerCodeKey := range innerCodeKeys {
|
||
if innerCode2, ok := data[innerCodeKey]; ok {
|
||
innerCode = forceInnerCode2Str(innerCode2)
|
||
break
|
||
}
|
||
}
|
||
|
||
if _, ok := innerCodeOKCodes[innerCode]; ok {
|
||
for _, innerDataKey := range noPageInnerDataKeys {
|
||
if innerData, ok := data[innerDataKey]; ok {
|
||
return utils.DictKeysMan(innerData, keyToRemove, keyToKeep), nil
|
||
}
|
||
}
|
||
baseapi.SugarLogger.Errorf("can not find inner data, data:%v", jsonResult)
|
||
return nil, platformapi.ErrResponseDataFormatWrong
|
||
} else {
|
||
// todo 可以把具体错误消息放进来
|
||
return nil, utils.NewErrorCode(InnerCodeIsNotOk, innerCode, 1)
|
||
}
|
||
}
|
||
|
||
func NormalHavePageResultParser(data map[string]interface{}, totalCount int) ([]interface{}, int, error) {
|
||
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 {
|
||
return nil, 0, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
}
|
||
|
||
result = tempResult.(map[string]interface{})
|
||
|
||
if totalCount == 0 {
|
||
for _, totalCountKey := range havePageTotalCountKeys {
|
||
if totalCount2, ok := result[totalCountKey]; ok {
|
||
totalCountInt64, _ := totalCount2.(json.Number).Int64()
|
||
totalCount = int(totalCountInt64)
|
||
if totalCount == 0 {
|
||
return make([]interface{}, 0), 0, nil
|
||
}
|
||
break
|
||
}
|
||
}
|
||
if totalCount == 0 {
|
||
baseapi.SugarLogger.Errorf("can not find totalCount key, data:%v", result)
|
||
return nil, 0, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
}
|
||
|
||
for _, inner2ResultKey := range havePageInner2DataKeys {
|
||
if inner2Result, ok := result[inner2ResultKey]; ok {
|
||
|
||
if inner2ResultStr, ok := inner2Result.(string); ok {
|
||
err := utils.UnmarshalUseNumber([]byte(inner2ResultStr), &retVal)
|
||
if err != nil {
|
||
return nil, 0, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
} else {
|
||
retVal = inner2Result.([]interface{})
|
||
}
|
||
|
||
return retVal, totalCount, nil
|
||
}
|
||
}
|
||
baseapi.SugarLogger.Errorf("can not find result key, data:%v", result)
|
||
return nil, 0, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
|
||
func (a *API) AccessAPIHavePage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string, pageResultParser PageResultParser) ([]interface{}, error) {
|
||
if pageResultParser == nil {
|
||
pageResultParser = NormalHavePageResultParser
|
||
}
|
||
|
||
localJdParams := make(map[string]interface{})
|
||
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 = tempPageNo.(int)
|
||
}
|
||
if tempPageSize, ok := localJdParams["pageSize"]; ok {
|
||
pageSize = tempPageSize.(int)
|
||
}
|
||
|
||
curPage := pageNo
|
||
if curPage == AllPage {
|
||
curPage = 1
|
||
}
|
||
|
||
retVal := make([]interface{}, 0)
|
||
for {
|
||
localJdParams["pageNo"] = curPage
|
||
localJdParams["pageSize"] = pageSize
|
||
jsonResult, err := a.AccessAPI(apiStr, localJdParams)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
var data map[string]interface{}
|
||
if err := utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data); err != nil {
|
||
return nil, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
|
||
innerCode := forceInnerCode2Str(data["code"])
|
||
if innerCode != "0" && innerCode != "200" {
|
||
// todo 可以把具体错误消息放进来
|
||
return nil, utils.NewErrorCode(InnerCodeIsNotOk, innerCode, 1)
|
||
}
|
||
|
||
inResult, totalCount2, err := pageResultParser(data, totalCount)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
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))
|
||
}
|