- big big refactor.

This commit is contained in:
gazebo
2018-06-22 22:26:12 +08:00
parent 062f7a2540
commit 7657966da5
27 changed files with 1277 additions and 1266 deletions

View File

@@ -0,0 +1,121 @@
package jdapi
import (
"fmt"
"net/http"
"net/url"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
type CallbackResponse struct {
Code string `json:"code"`
Msg string `json:"msg"`
Data string `json:"data"`
}
type CallbackOrderMsg struct {
ID int `json:"-"` // 用于传递Jdorder的主键值减少一次读库操作
BillID string `json:"billId"`
StatusID string `json:"statusId"`
Timestamp string `json:"timestamp"`
Remark string `json:"remark"`
}
type CallbackDeliveryStatusMsg struct {
OrderID string `json:"orderId"`
DeliveryStatusTime string `json:"deliveryStatusTime"`
DeliveryManNo string `json:"deliveryManNo"`
DeliveryManName string `json:"deliveryManName"`
DeliveryManPhone string `json:"deliveryManPhone"`
DeliveryCarrierNo string `json:"deliveryCarrierNo"`
DeliveryCarrierName string `json:"deliveryCarrierName"`
DeliveryStatus int `json:"deliveryStatus"`
Remark string `json:"remark"`
FailType string `json:"failType"`
CreatePin string `json:"createPin"`
OpTime int64 `json:"opTime"`
InputTime string `json:"inputTime"`
}
var (
SuccessResponse = &CallbackResponse{Code: "0", Msg: "success", Data: ""}
)
func (a *API) unmarshalData(strData string, msg interface{}) (callbackResponse *CallbackResponse) {
err := utils.UnmarshalUseNumber([]byte(strData), msg)
if err != nil {
return &CallbackResponse{
Code: ResponseCodeAbnormalParam,
Msg: fmt.Sprintf(platformapi.ErrStrUnmarshalError, strData, err),
Data: strData,
}
}
return nil
}
func (a *API) CheckCallbackValidation(request *http.Request) (callbackResponse *CallbackResponse) {
mapData := make(map[string]interface{})
mapData["token"] = request.FormValue("token")
mapData["app_key"] = request.FormValue("app_key")
mapData["timestamp"] = request.FormValue("timestamp")
mapData["format"] = request.FormValue("format")
mapData["app_secret"] = a.appSecret
mapData["v"] = request.FormValue("v")
mapData[paramJson] = request.FormValue(paramJson)
sign := a.signParams(mapData)
if sign != request.FormValue(signKey) {
baseapi.SugarLogger.Infof("Signature is not ok, mine:%v, get:%v", sign, request.FormValue(signKey))
return &CallbackResponse{
Code: ResponseCodeInvalidSign,
Msg: platformapi.ErrStrCallbackSignatureIsWrong,
Data: string(utils.MustMarshal(mapData)),
}
}
return nil
}
func (a *API) getCommonOrderCallbackMsg(request *http.Request, msg interface{}, needDecode bool) (callbackResponse *CallbackResponse) {
if callbackResponse = a.CheckCallbackValidation(request); callbackResponse != nil {
return callbackResponse
}
jdParamJSON := request.FormValue(paramJson)
if needDecode {
if jdParamJSON2, err := url.QueryUnescape(jdParamJSON); err == nil {
jdParamJSON = jdParamJSON2
} else {
return &CallbackResponse{
Code: ResponseCodeAbnormalParam,
Msg: fmt.Sprintf(platformapi.ErrStrUnescapeError, jdParamJSON, err),
Data: jdParamJSON,
}
}
}
if callbackResponse = a.unmarshalData(jdParamJSON, msg); callbackResponse != nil {
return callbackResponse
}
return nil
}
func (a *API) GetOrderCallbackMsg(request *http.Request) (msg *CallbackOrderMsg, callbackResponse *CallbackResponse) {
msg = new(CallbackOrderMsg)
callbackResponse = a.getCommonOrderCallbackMsg(request, msg, false)
return msg, callbackResponse
}
func (a *API) GetOrderApplyCancelCallbackMsg(request *http.Request) (msg *CallbackOrderMsg, callbackResponse *CallbackResponse) {
msg = new(CallbackOrderMsg)
callbackResponse = a.getCommonOrderCallbackMsg(request, msg, true)
return msg, callbackResponse
}
func (a *API) GetOrderDeliveryCallbackMsg(request *http.Request) (msg *CallbackDeliveryStatusMsg, callbackResponse *CallbackResponse) {
msg = new(CallbackDeliveryStatusMsg)
callbackResponse = a.getCommonOrderCallbackMsg(request, msg, true)
return msg, callbackResponse
}

342
platformapi/jdapi/jdapi.go Normal file
View File

@@ -0,0 +1,342 @@
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.comAPI分组有接口详细说明。
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))
}

View File

@@ -0,0 +1,119 @@
package jdapi
import (
"net/http"
"testing"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
var (
jdapi *API
sugarLogger *zap.SugaredLogger
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
baseapi.Init(sugarLogger)
jdapi = New("91633f2a-c5f5-4982-a925-a220d19095c3", "1dba76d40cac446ca500c0391a0b6c9d", "a88d031a1e7b462cb1579f12e97fe7f4")
// jdapi = New("c8854ef2-f80a-45ee-aceb-dc8014d646f8", "06692746f7224695ad4788ce340bc854", "d6b42a35a7414a5490d811654d745c84")
}
func TestTest(t *testing.T) {
sugarLogger.Debug(utils.GetCurTimeStr())
}
func TestAccessAPI(t *testing.T) {
result, err := jdapi.AccessAPI("address/allcities", nil)
if err != nil {
t.Fatalf("Error when accessing AccessAPI: %v", err)
} else {
code := result["code"].(string)
if code != "0" {
t.Fatalf("code is not 0, %v", code)
}
}
}
func TestAccessAPINoPage(t *testing.T) {
result, err := jdapi.AccessAPINoPage("address/allcities", nil, []string{"yn"}, nil)
if err != nil {
t.Fatalf("TestAccessAPINoPage return error:%v", err)
}
cityInfo := result.([]interface{})
if len(cityInfo) == 0 {
t.Fatal("city info is empty")
}
oneCity := cityInfo[0].(map[string]interface{})
if _, ok := oneCity["areaName"]; !ok {
t.Fatal("no areaName key")
}
if _, ok := oneCity["yn"]; ok {
t.Fatal("yn field havn't been removed")
}
}
func TestAccessAPIHavePage(t *testing.T) {
jdParams := map[string]interface{}{
"pageNo": 1,
"pageSize": 20,
}
skuInfo, err := jdapi.AccessAPIHavePage("pms/querySkuInfos", jdParams, nil, []string{"skuName", "skuId"}, nil)
if err != nil {
t.Fatalf("AccessAPIHavePage return error:%v", err)
}
if len(skuInfo) == 0 {
t.Fatal("sku info is empty")
}
oneSku := skuInfo[0].(map[string]interface{})
if _, ok := oneSku["skuName"]; !ok {
t.Fatal("no skuName key")
}
if _, ok := oneSku["outSkuId"]; ok {
t.Fatal("outSkuId key not removed")
}
}
func TestGenerateURL(t *testing.T) {
params := make(map[string]interface{})
params["key"] = "v"
params["key2"] = "v2"
fullURL := generateURL(prodURL, "address/allcities", params)
response, err := http.Get(fullURL)
if err != nil {
t.Fatalf("Get return error:%v", err)
}
defer response.Body.Close()
}
func TestOrderQuery(t *testing.T) {
jdParams := map[string]interface{}{
"orderId": "813452298000322",
}
result, err := jdapi.OrderQuery(jdParams)
if err != nil {
t.Fatalf("OrderQuery return error:%v", err)
}
if len(result) == 0 {
t.Fatal("OrderQuery return empty data")
}
buyerCityOk := false
orderInfo, _ := result[0].(map[string]interface{})
if buyerCity1, ok := orderInfo["buyerCity"]; ok {
if buyerCity, ok := buyerCity1.(string); ok && buyerCity == "1930" {
buyerCityOk = true
}
}
if !buyerCityOk {
t.Fatal("OrderQuery get data wrong")
}
}

View File

@@ -0,0 +1,53 @@
package jdapi
import (
"git.rosy.net.cn/baseapi/utils"
)
const (
OrderStatusNew = "32000"
OrderStatusAdjust = "33080"
OrderStatusUserCancel = "20030"
OrderStatusWaitOutStore = "32001"
)
func (a API) OrderQuery(jdParams map[string]interface{}) (retVal []interface{}, err error) {
retVal, err = a.AccessAPIHavePage("order/es/query", jdParams, nil, nil, nil)
return
}
func (a API) QuerySingleOrder(orderId string) ([]interface{}, error) {
jdParams := make(map[string]interface{})
jdParams["orderId"] = orderId
return a.AccessAPIHavePage("order/es/query", jdParams, nil, nil, nil)
}
func (a API) LegacyQuerySingleOrder(orderId string) (map[string]interface{}, error) {
jdParams := make(map[string]interface{})
jdParams["orderId"] = orderId
result, err := a.AccessAPI("order/es/query", jdParams)
if err != nil {
return nil, err
}
dataStr, _ := result["data"].(string)
var data map[string]interface{}
utils.UnmarshalUseNumber([]byte(dataStr), &data)
result["data"] = data
var dataResult map[string]interface{}
utils.UnmarshalUseNumber([]byte(data["result"].(string)), &dataResult)
data["result"] = dataResult
return result, nil
}
func (a API) OrderAcceptOperate(orderId string, isAgreed bool) (interface{}, error) {
jdParams := map[string]interface{}{
"orderId": orderId,
"isAgreed": utils.Bool2String(isAgreed),
"operator": utils.GetAPIOperator(),
}
return a.AccessAPINoPage("ocs/orderAcceptOperate", jdParams, nil, nil)
}