package jdapi import ( "bytes" "crypto/md5" "encoding/json" "fmt" "net/http" "sort" "strings" "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 ( ResponseInnerCodeOrderAlreadyPickedUp = "10132" ResponseInnerCodeOrderCanceled = "10138" ResponseInnerCodeOrderLocked = "10139" ResponseInnerCodePartialFailed = "190005" ) const ( prodURL = "https://openapi.jddj.com/djapi" signKey = "sign" appSecretKey = "app_secret" AllPage = 0 DefaultPageSize = 50 ) type API struct { platformapi.APICookie token string appKey string appSecret string client *http.Client config *platformapi.APIConfig } var ( exceedLimitCodes = map[string]int{ ResponseCodeExceedLimit: 1, } 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"} ) var ( nullResultParser = genNoPageResultParser("code", "msg", "", "0") watchKeys = []string{ KeyOutStationNo, KeyStationNo, "StoreNo", KeyOutSkuId, KeySkuId, } ) 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 != signKey { keys = append(keys, k) } } sort.Strings(keys) buf := &bytes.Buffer{} buf.WriteString(a.appSecret) for _, k := range keys { buf.WriteString(k) buf.WriteString(fmt.Sprint(jdParams[k])) } buf.WriteString(a.appSecret) return fmt.Sprintf("%X", md5.Sum(buf.Bytes())) } 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 NewPageOnly(cookie string, config ...*platformapi.APIConfig) *API { api := New("", "", "", config...) api.SetCookie(accessStorePageCookieName, cookie) api.SetCookie(accessStorePageCookieName2, cookie) return api } func (a *API) AccessAPI2(apiStr string, jdParams map[string]interface{}, traceInfo string) (retVal map[string]interface{}, err error) { params := make(map[string]interface{}) params["v"] = "1.0" params["format"] = "json" params["app_key"] = a.appKey params["token"] = a.token if jdParams == nil { jdParams = make(map[string]interface{}, 0) } jdParamBytes, err := json.Marshal(jdParams) if err != nil { baseapi.SugarLogger.Errorf("Error when marshal %v, error:%v", jdParams, err) return nil, err } jdParamStr := string(jdParamBytes) userGet := true if true { //len(jdParamStr) > 12 { userGet = false } params["jd_param_json"] = jdParamStr err = platformapi.AccessPlatformAPIWithRetry(a.client, func() *http.Request { params["timestamp"] = utils.GetCurTimeStr() sign := a.signParams(params) params[signKey] = sign var request *http.Request if userGet { fullURL := utils.GenerateGetURL(prodURL, apiStr, params) // baseapi.SugarLogger.Debug(fullURL) request, _ = http.NewRequest(http.MethodGet, fullURL, nil) } else { fullURL := prodURL + "/" + apiStr // baseapi.SugarLogger.Debug(utils.Map2URLValues(params).Encode()) request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode())) request.Header.Set("charset", "UTF-8") request.Header.Set("Content-Type", "application/x-www-form-urlencoded") } if traceInfo != "" { request.Header.Set(platformapi.KeyTrackInfo, traceInfo) } // 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") } code := utils.Interface2String(jsonResult1["code"]) if code == "" { if jsonResult1[platformapi.KeyData] == nil { baseapi.SugarLogger.Warnf("abnormal jdapi apiStr:%s, jdParams:%s, result:%s", apiStr, utils.Format4Output(jdParams, true), utils.Format4Output(jsonResult1, true)) } else { return platformapi.ErrLevelRecoverableErr, fmt.Errorf("%s return not json", apiStr) } } if code == ResponseCodeSuccess { retVal = jsonResult1 return platformapi.ErrLevelSuccess, nil } newErr := utils.NewErrorCode(utils.Interface2String(jsonResult1["msg"]), code) if _, ok := exceedLimitCodes[code]; ok { return platformapi.ErrLevelExceedLimit, newErr } else if _, ok := canRetryCodes[code]; ok { return platformapi.ErrLevelRecoverableErr, newErr } else { baseapi.SugarLogger.Debugf("jd AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) return platformapi.ErrLevelCodeIsNotOK, newErr } }) return retVal, platformapi.RebuildError(err, jdParams, watchKeys) } func (a *API) AccessAPI(apiStr string, jdParams map[string]interface{}) (retVal map[string]interface{}, err error) { return a.AccessAPI2(apiStr, jdParams, "") } func genNoPageResultParser(codeKey, msgKey, resultKey, okCode string) func(data map[string]interface{}) (interface{}, error) { return func(data map[string]interface{}) (innerData interface{}, err error) { rawInnerCode, ok := data[codeKey] if !ok { panic(fmt.Sprintf("genNoPageResultParser codeKey %s can not be found in result:%v", codeKey, data)) } innerCode := forceInnerCode2Str(rawInnerCode) if resultKey != "" { innerData, _ = data[resultKey] } if innerCode != okCode { errMsg := formatErrorMsg(data[msgKey]) if innerCode == ResponseInnerCodePartialFailed { errMsg += ", " + utils.Format4Output(innerData, true) } err = utils.NewErrorCode(errMsg, innerCode, 1) } return innerData, err } } func formatErrorMsg(msg interface{}) (strMsg string) { if msg != nil { strMsg = utils.Format4Output(msg, true) } return strMsg } func (a *API) AccessAPINoPage2(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string, resultParser func(data map[string]interface{}) (interface{}, error), traceInfo string) (interface{}, error) { if resultParser == nil { resultParser = genNoPageResultParser("code", "msg", "result", "0") } jsonResult, err := a.AccessAPI2(apiStr, jdParams, traceInfo) 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 } result, err := resultParser(data) if err == nil { return utils.DictKeysMan(result, keyToRemove, keyToKeep), nil } err = platformapi.RebuildError(err, jdParams, watchKeys) baseapi.SugarLogger.Infof("AccessAPINoPage failed, apiStr:%s, jdParams:%s, data:%s, error:%v", apiStr, utils.Format4Output(jdParams, true), utils.Format4Output(jsonResult, true), err) return result, err } func (a *API) AccessAPINoPage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string, resultParser func(data map[string]interface{}) (interface{}, error)) (interface{}, error) { return a.AccessAPINoPage2(apiStr, jdParams, keyToRemove, keyToKeep, resultParser, "") } func genNormalHavePageResultParser(dataKey string) (handler PageResultParser) { return func(data map[string]interface{}, totalCount int) ([]interface{}, int, error) { var result map[string]interface{} var retVal []interface{} tempResult := data[dataKey] 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 inner2Result == nil { retVal = nil } else 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{}, int, error) { if pageResultParser == nil { pageResultParser = genNormalHavePageResultParser("result") } 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[KeyPageNo]; ok { pageNo = tempPageNo.(int) } if tempPageSize, ok := localJdParams[KeyPageSize]; ok { pageSize = tempPageSize.(int) } curPage := pageNo if curPage == AllPage { curPage = 1 } retVal := make([]interface{}, 0) for { localJdParams[KeyPageNo] = curPage localJdParams[KeyPageSize] = pageSize jsonResult, err := a.AccessAPI(apiStr, localJdParams) if err != nil { return nil, totalCount, err } var data map[string]interface{} if err := utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data); err != nil { return nil, totalCount, platformapi.ErrResponseDataFormatWrong } innerCode := forceInnerCode2Str(data["code"]) if innerCode != "0" { err2 := utils.NewErrorCode(getErrMsgFromData(data), innerCode, 1) baseapi.SugarLogger.Infof("AccessAPIHavePage failed, apiStr:%s, jdParams:%s, data:%s, error:%v", apiStr, utils.Format4Output(jdParams, true), utils.Format4Output(jsonResult, true), err2) return nil, totalCount, platformapi.RebuildError(err2, jdParams, watchKeys) } inResult, totalCount2, err := pageResultParser(data, totalCount) if err != nil { return nil, totalCount, platformapi.RebuildError(err, jdParams, watchKeys) } totalCount = totalCount2 inResult = utils.DictKeysMan(inResult, keyToRemove, keyToKeep).([]interface{}) retVal = append(retVal, inResult...) if curPage == pageNo || curPage*pageSize >= totalCount { break } curPage++ } return retVal, totalCount, nil } func forceInnerCode2Str(innerCode interface{}) string { if innerCode == nil { return "0" } else if innerCodeStr, ok := innerCode.(string); ok { return innerCodeStr } return fmt.Sprintf("%v", innerCode) } func getErrMsgFromData(data map[string]interface{}) string { msg := utils.Interface2String(data["msg"]) if msg == "" { msg = string(utils.MustMarshal(data)) baseapi.SugarLogger.Warnf("getErrMsgFromData data:%v have no field msg!", data) } return msg }