Files
baseapi/platformapi/jdapi/jdapi.go
邹宗楠 7ac9146ca5 1
2025-01-06 14:34:40 +08:00

496 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package jdapi
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"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.comAPI分组有接口详细说明。
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
)
const (
JdVendorOrgCodeVagetable = "320406" // 菜市默认京东code
JdVendorOrgCodeFruit = "339032" // 水果默认京东code
JdVendorOrgCodePet = "390558" // 百货默认京东code
)
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)
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{}
if jsonResult["encryptData"] != nil {
encryptData, err := JDDecodeToData(a.appSecret, jsonResult["encryptData"].(string))
if err != nil {
return nil, totalCount, platformapi.ErrResponseDataFormatWrong
}
if err := utils.UnmarshalUseNumber(encryptData, &data); err != nil {
return nil, totalCount, platformapi.ErrResponseDataFormatWrong
}
} else if jsonResult["encryptData"] == nil && jsonResult["data"] != nil {
if err := utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data); err != nil {
return nil, totalCount, platformapi.ErrResponseDataFormatWrong
}
} else {
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.AESCBCDecprytJD(sDec, []byte(key), []byte(iv))
if err != nil {
return nil, err
}
return resp, nil
}