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)) }