483 lines
15 KiB
Go
483 lines
15 KiB
Go
package jdapi
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/md5"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"git.rosy.net.cn/jx-callback/globals"
|
||
"net/http"
|
||
"reflect"
|
||
"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
|
||
|
||
baseURL string
|
||
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{
|
||
baseURL: prodURL,
|
||
token: token,
|
||
appKey: appKey,
|
||
appSecret: appSecret,
|
||
client: &http.Client{Timeout: curConfig.ClientTimeout},
|
||
config: &curConfig,
|
||
}
|
||
}
|
||
|
||
func NewFakeJD(token, baseURL string, config ...*platformapi.APIConfig) *API {
|
||
a := New(token, "", "", config...)
|
||
a.baseURL = baseURL
|
||
return a
|
||
}
|
||
|
||
func NewPageOnly(cookie string, config ...*platformapi.APIConfig) *API {
|
||
api := New("", "", "", config...)
|
||
api.SetCookie(AccessStorePageCookieName, cookie)
|
||
api.SetCookie(AccessStorePageCookieName2, cookie)
|
||
return api
|
||
}
|
||
|
||
func (a *API) GetToken() (token string) {
|
||
return a.token
|
||
}
|
||
|
||
func (a *API) GetAppKey() (appKey string) {
|
||
return a.appKey
|
||
}
|
||
|
||
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(a.baseURL, apiStr, params)
|
||
// baseapi.SugarLogger.Debug(fullURL)
|
||
request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
|
||
} else {
|
||
fullURL := utils.GenerateGetURL(a.baseURL, apiStr, nil)
|
||
// 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 {
|
||
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
|
||
}
|
||
if jsonResult["code"].(string) != "0" {
|
||
return nil, errors.New(jsonResult["msg"].(string))
|
||
}
|
||
if (jsonResult["data"] == nil || utils.IsNil(jsonResult["data"])) && (jsonResult["encryptData"] != nil || utils.IsNil(jsonResult["encryptData"])) {
|
||
decodeData, err := JDDecodeToData(a.appSecret, jsonResult["encryptData"].(string))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
jsonResult["data"] = string(decodeData)
|
||
}
|
||
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]
|
||
var result0 = make(map[string]interface{}, 0)
|
||
if resultStr, ok := tempResult.(string); ok {
|
||
if err := utils.UnmarshalUseNumber([]byte(resultStr), &result0); err != nil {
|
||
return nil, 0, platformapi.ErrResponseDataFormatWrong
|
||
}
|
||
}
|
||
|
||
result = result0
|
||
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{}
|
||
encryptData, err := JDDecodeToData(a.appSecret, jsonResult["encryptData"].(string))
|
||
|
||
if err := utils.UnmarshalUseNumber(encryptData, &data); err != nil {
|
||
globals.SugarLogger.Errorf("err 1 := %s", err.Error())
|
||
globals.SugarLogger.Errorf("jsonResult=========:= %s", utils.Format4Output(jsonResult, false))
|
||
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
|
||
}
|
||
|
||
func JavaDateHook(fromType reflect.Type, toType reflect.Type, data interface{}) (interface{}, error) {
|
||
if (fromType.Kind() >= reflect.Int && fromType.Kind() <= reflect.Uint64) ||
|
||
fromType.Kind() == reflect.String {
|
||
if toType.Kind() == reflect.Ptr {
|
||
toType = toType.Elem()
|
||
}
|
||
if toType.Kind() == reflect.Struct &&
|
||
toType.Name() == "JavaDate" {
|
||
if milliSec := utils.Interface2Int64WithDefault(data, 0); milliSec > 0 {
|
||
data = utils.NewJavaDateFromTime(utils.Timestamp2Time(milliSec))
|
||
} else if fromType.Kind() == reflect.String {
|
||
data = utils.NewJavaDateFromTime(utils.Str2Time(data.(string)))
|
||
}
|
||
}
|
||
}
|
||
return data, nil
|
||
}
|
||
|
||
func JdMap2StructByJson(inObj interface{}, outObjAddr interface{}, weaklyTypedInput bool) (err error) {
|
||
return utils.Map2Struct(inObj, outObjAddr, true, "", JavaDateHook)
|
||
}
|
||
|
||
func JDDecodeToData(appSecret, data string) ([]byte, error) {
|
||
key := appSecret[:16]
|
||
iv := appSecret[16:32]
|
||
sDec, err := base64.StdEncoding.DecodeString(data)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
resp, err := utils.AESCBCDecpryt(sDec, []byte(key), []byte(iv))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return resp, nil
|
||
}
|