From 7657966da5d53415b1426779bf261e11e58aaaf2 Mon Sep 17 00:00:00 2001 From: gazebo Date: Fri, 22 Jun 2018 22:26:12 +0800 Subject: [PATCH] - big big refactor. --- platform/common/common.go | 97 ----- platform/dadaapi/dadaapi.go | 187 ---------- platform/elmapi/callback.go | 68 ---- platform/elmapi/elmapi.go | 164 --------- platform/elmapi/order.go | 23 -- platform/jdapi/callback.go | 121 ------ platform/jdapi/jdapi.go | 347 ------------------ platform/jdapi/order.go | 53 --- {platform => platformapi}/dadaapi/callback.go | 38 +- platformapi/dadaapi/dadaapi.go | 176 +++++++++ .../dadaapi/dadaapi_test.go | 18 +- {platform => platformapi}/dadaapi/order.go | 52 +-- platformapi/elmapi/callback.go | 69 ++++ platformapi/elmapi/elmapi.go | 155 ++++++++ .../elmapi/elmapi_test.go | 12 +- platformapi/elmapi/order.go | 23 ++ platformapi/jdapi/callback.go | 121 ++++++ platformapi/jdapi/jdapi.go | 342 +++++++++++++++++ {platform => platformapi}/jdapi/jdapi_test.go | 28 +- platformapi/jdapi/order.go | 53 +++ {platform => platformapi}/mtpsapi/callback.go | 50 +-- {platform => platformapi}/mtpsapi/mtpsapi.go | 144 ++++---- .../mtpsapi/mtpsapi_test.go | 22 +- platformapi/platformapi.go | 119 ++++++ utils/errorwithcode.go | 36 +- utils/utils.go | 22 +- utils/utils_test.go | 3 + 27 files changed, 1277 insertions(+), 1266 deletions(-) delete mode 100644 platform/common/common.go delete mode 100644 platform/dadaapi/dadaapi.go delete mode 100644 platform/elmapi/callback.go delete mode 100644 platform/elmapi/elmapi.go delete mode 100644 platform/elmapi/order.go delete mode 100644 platform/jdapi/callback.go delete mode 100644 platform/jdapi/jdapi.go delete mode 100644 platform/jdapi/order.go rename {platform => platformapi}/dadaapi/callback.go (51%) create mode 100644 platformapi/dadaapi/dadaapi.go rename {platform => platformapi}/dadaapi/dadaapi_test.go (84%) rename {platform => platformapi}/dadaapi/order.go (58%) create mode 100644 platformapi/elmapi/callback.go create mode 100644 platformapi/elmapi/elmapi.go rename {platform => platformapi}/elmapi/elmapi_test.go (79%) create mode 100644 platformapi/elmapi/order.go create mode 100644 platformapi/jdapi/callback.go create mode 100644 platformapi/jdapi/jdapi.go rename {platform => platformapi}/jdapi/jdapi_test.go (67%) create mode 100644 platformapi/jdapi/order.go rename {platform => platformapi}/mtpsapi/callback.go (63%) rename {platform => platformapi}/mtpsapi/mtpsapi.go (51%) rename {platform => platformapi}/mtpsapi/mtpsapi_test.go (78%) create mode 100644 platformapi/platformapi.go diff --git a/platform/common/common.go b/platform/common/common.go deleted file mode 100644 index 5c282fc2..00000000 --- a/platform/common/common.go +++ /dev/null @@ -1,97 +0,0 @@ -package common - -import ( - "errors" - "net" - "net/http" - "time" - - "github.com/fatih/structs" - - "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/utils" -) - -type AccessPlatformAPIWithRetryParams struct { - MaxExceedLimitRetryCount int - MaxRecoverableRetryCount int - SleepSecondWhenExceedLimit time.Duration - Client *http.Client - Request *http.Request -} - -const ( - PAErrorLevelSuccess = "JXC4_SUCCESS" - PAErrorLevelExceedLimit = "JXC4_EXCEED_LIMIT" - PAErrorLevelRecoverable = "JXC4_RECOVERABLE" - PAErrorLevelGeneralFail = "JXC4_GENERAL_FAIL" -) - -var ( - ErrRecoverableErrMaxRetry = errors.New("recoverable error reach max retry count!") - ErrLimitReachMaxRetry = errors.New("Reach max retry count!") -) - -var ( - ErrStrHttpCode = "HTTP Code is not 200" - ErrStrBusinessCode = "Business code is not ok" -) - -var ( - CBErrMsgUnescape = "can not unescape data:%v, data:%v" - CBErrMsgUnmarshal = "can not unmarshal data:%v, data:%v" -) - -func init() { - structs.DefaultTagName = "json" -} - -func AccessPlatformAPIWithRetry(params *AccessPlatformAPIWithRetryParams, handleResponse func(response *http.Response) (string, error)) error { - exceedLimitRetryCount := 0 - recoverableErrorRetryCount := 0 - for { - response, err := params.Client.Do(params.Request) - if err != nil { - baseapi.SugarLogger.Debugf("client.Get return err:%v", err) - err, ok := err.(net.Error) - recoverableErrorRetryCount++ - if ok && err.Timeout() && recoverableErrorRetryCount <= params.MaxRecoverableRetryCount { - continue - } else { - return err - } - } - defer response.Body.Close() - if response.StatusCode != 200 { - baseapi.SugarLogger.Debugf("http code is:%d", response.StatusCode) - recoverableErrorRetryCount++ - if recoverableErrorRetryCount <= params.MaxRecoverableRetryCount { - continue - } - - return utils.NewErrorIntCode(ErrStrHttpCode, response.StatusCode) - } - - errCode, err := handleResponse(response) - if err != nil { - return err - } - if errCode == PAErrorLevelSuccess { - return nil - } else if errCode == PAErrorLevelExceedLimit { - exceedLimitRetryCount++ - if exceedLimitRetryCount <= params.MaxExceedLimitRetryCount { - time.Sleep(params.SleepSecondWhenExceedLimit) - } else { - return ErrLimitReachMaxRetry - } - } else if errCode == PAErrorLevelRecoverable { - if recoverableErrorRetryCount <= params.MaxRecoverableRetryCount { - continue - } - return ErrRecoverableErrMaxRetry - } else { - return utils.NewErrorCode(ErrStrBusinessCode, errCode) - } - } -} diff --git a/platform/dadaapi/dadaapi.go b/platform/dadaapi/dadaapi.go deleted file mode 100644 index 8fe0ec46..00000000 --- a/platform/dadaapi/dadaapi.go +++ /dev/null @@ -1,187 +0,0 @@ -package dadaapi - -import ( - "bytes" - "crypto/md5" - "fmt" - "net/http" - "sort" - "time" - - "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/platform/common" - "git.rosy.net.cn/baseapi/utils" -) - -const ( - dadaSandboxURL = "http://newopen.qa.imdada.cn/" - dadaProdURL = "http://newopen.imdada.cn/" - signKey = "signature" -) - -const ( - clientTimeout = time.Second * 10 - sleepSecondWhenLimited = 6 * time.Second - maxRetryCountWhenNetworkException = 3 - maxRetryCountWhenReachLimited = 10 -) - -const ( - DadaCodeException = -1 - DadaCodeSuccess = 0 - DadaCodeSignErr = 2003 - DadaCodeRetryLater = 2012 - DadaCodeNetworkErr = 2455 -) - -type DadaAPI struct { - appKey string - appSecret string - sourceId string - url string - callbackURL string - client *http.Client -} - -type DadaResult struct { - Status string `json:"status"` - Code int `json:"code"` - Msg string `json:"msg"` - Result interface{} `json:"result"` - ErrorCode int `json:"errorCode"` -} - -type DadaCity struct { - CityName string `json:"cityName"` - CityCode string `json:"cityCode"` -} - -type CancelReason struct { - Id int `json:"id"` - Reason string `json:"reason"` -} - -func NewDadaAPI(appKey, appSecret, sourceId, callbackURL string, isProd bool) *DadaAPI { - api := &DadaAPI{ - appKey: appKey, - appSecret: appSecret, - sourceId: sourceId, - callbackURL: callbackURL, - client: &http.Client{Timeout: clientTimeout}, - } - if isProd { - api.url = dadaProdURL - } else { - api.url = dadaSandboxURL - } - return api -} - -func (d *DadaAPI) signParams(mapData map[string]interface{}) string { - keys := make([]string, 0) - for k := range mapData { - if k != signKey { - keys = append(keys, k) - } - } - sort.Strings(keys) - - finalStr := d.appSecret - for _, k := range keys { - finalStr += k + fmt.Sprint(mapData[k]) - } - - finalStr += d.appSecret - // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) - return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) -} - -func (d *DadaAPI) AccessDada(action string, params map[string]interface{}) (retVal *DadaResult, err error) { - params2 := make(map[string]interface{}) - - params2["app_key"] = d.appKey - params2["timestamp"] = utils.Int64ToStr(utils.GetCurTimestamp()) - params2["format"] = "json" - params2["v"] = "1.0" - params2["source_id"] = d.sourceId - if params == nil { - params2["body"] = "" - } else { - params2["body"] = string(utils.MustMarshal(params)) - } - params2[signKey] = d.signParams(params2) - params2Bytes := utils.MustMarshal(params2) - request, _ := http.NewRequest("POST", d.url+action, bytes.NewReader(params2Bytes)) - request.Header.Set("Content-Type", "application/json") - apiAccess := &common.AccessPlatformAPIWithRetryParams{ - MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited, - MaxRecoverableRetryCount: maxRetryCountWhenNetworkException, - SleepSecondWhenExceedLimit: sleepSecondWhenLimited, - Client: d.client, - Request: request, - } - - err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (result string, err error) { - jsonResult1, err := utils.HttpResponse2Json(response) - if err != nil { - baseapi.SugarLogger.Warnf("HttpResponse2Json return:%v", err) - return common.PAErrorLevelGeneralFail, err - } - code := int(utils.MustInterface2Int64(jsonResult1["code"])) - retVal = &DadaResult{ - Code: code, - ErrorCode: code, - Msg: jsonResult1["msg"].(string), - Status: jsonResult1["status"].(string), - } - - if code == DadaCodeSuccess { - retVal.Result = jsonResult1["result"] - return common.PAErrorLevelSuccess, nil - } - baseapi.SugarLogger.Debug(jsonResult1) - if code == DadaCodeRetryLater || code == DadaCodeNetworkErr { - return common.PAErrorLevelRecoverable, nil - } - - return common.PAErrorLevelGeneralFail, utils.NewErrorIntCode(retVal.Msg, code) - }) - - return retVal, err -} - -func (d *DadaAPI) GetCities() (retVal []DadaCity, err error) { - result, err := d.AccessDada("api/cityCode/list", nil) - if err != nil { - return nil, err - } - - cites := result.Result.([]interface{}) - for _, v := range cites { - v2 := v.(map[string]interface{}) - city := DadaCity{ - CityName: v2["cityName"].(string), - CityCode: v2["cityCode"].(string), - } - retVal = append(retVal, city) - } - return retVal, nil -} - -func (d *DadaAPI) GetCancelReasons() (retVal []CancelReason, err error) { - result, err := d.AccessDada("api/order/cancel/reasons", nil) - if err != nil { - return nil, err - } - - cites := result.Result.([]interface{}) - for _, v := range cites { - v2 := v.(map[string]interface{}) - reason := CancelReason{ - Id: int(utils.MustInterface2Int64(v2["id"])), - Reason: v2["reason"].(string), - } - retVal = append(retVal, reason) - } - return retVal, nil -} diff --git a/platform/elmapi/callback.go b/platform/elmapi/callback.go deleted file mode 100644 index f2d212ab..00000000 --- a/platform/elmapi/callback.go +++ /dev/null @@ -1,68 +0,0 @@ -package elmapi - -import ( - "fmt" - - "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/platform/common" - "git.rosy.net.cn/baseapi/utils" - "github.com/fatih/structs" -) - -const ( - OrderValid = 10 - MerchantValid = 12 - OrderCanceled = 14 - MerchantInvalid = 15 - OrderForceInvalid = 17 - OrderFinished = 18 -) - -type ELMCallbackResponse struct { - Message string `json:"message"` -} - -type ELMCallbackMsg struct { - AppId int `json:"appId"` - RequestId string `json:"requestId"` - Type int `json:"type"` - Message string `json:"message"` - ShopId int `json:"shopId"` - Timestamp int64 `json:"timestamp"` - UserId int64 `json:"userId"` - Signature string `json:"signature"` -} - -var ( - SuccessResponse = &ELMCallbackResponse{"ok"} -) - -func (e *ELMAPI) unmarshalData(data []byte, msg interface{}) (callbackResponse *ELMCallbackResponse) { - err := utils.UnmarshalUseNumber(data, msg) - if err != nil { - return &ELMCallbackResponse{ - Message: fmt.Sprintf(common.CBErrMsgUnmarshal, data, err), - } - } - return nil -} - -func (e *ELMAPI) CheckCallbackValidation(mapData map[string]interface{}) (callbackResponse *ELMCallbackResponse) { - sign := e.signParamsMap(mapData, "") - if remoteSign, _ := mapData[signKey].(string); sign != remoteSign { - baseapi.SugarLogger.Infof("Signature is not ok, mine:%v, get:%v", sign, remoteSign) - return &ELMCallbackResponse{Message: "signature is invalid"} - } - return nil -} - -func (e *ELMAPI) GetCallbackMsg(data []byte) (msg *ELMCallbackMsg, callbackResponse *ELMCallbackResponse) { - msg = new(ELMCallbackMsg) - if callbackResponse = e.unmarshalData(data, msg); callbackResponse != nil { - return nil, callbackResponse - } - - mapData := structs.Map(msg) - callbackResponse = e.CheckCallbackValidation(mapData) - return msg, callbackResponse -} diff --git a/platform/elmapi/elmapi.go b/platform/elmapi/elmapi.go deleted file mode 100644 index 97e2c26f..00000000 --- a/platform/elmapi/elmapi.go +++ /dev/null @@ -1,164 +0,0 @@ -package elmapi - -import ( - "crypto/md5" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "sort" - "strings" - "time" - - "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/platform/common" - "git.rosy.net.cn/baseapi/utils" -) - -const ( - clientTimeout = time.Second * 10 - sleepSecondWhenLimited = 6 * time.Second - maxRetryCountWhenNetworkException = 3 - maxRetryCountWhenReachLimited = 10 -) - -const ( - ELM_API_URL_SANDBOX = "https://open-api-sandbox.shop.ele.me/api/v1/" - ELM_API_URL_PROD = "https://open-api.shop.ele.me/api/v1/" - signKey = "signature" -) - -type ELMResult struct { - Id string - Result interface{} - Error map[string]interface{} -} - -type ELMAPI struct { - token string - appKey string - secret string - url *url.URL - client *http.Client -} - -type ELMPayload struct { - Token string `json:"token"` - Nop string `json:"nop"` - Metas map[string]interface{} `json:"metas"` - Params map[string]interface{} `json:"params"` - Action string `json:"action"` - Id string `json:"id"` - Signature string `json:"signature"` -} - -func NewELMAPI(token, appKey, secret string, isProd bool) *ELMAPI { - api := &ELMAPI{ - token: token, - appKey: appKey, - secret: secret, - client: &http.Client{Timeout: clientTimeout}, - } - - if isProd { - api.url, _ = url.Parse(ELM_API_URL_PROD) - } else { - api.url, _ = url.Parse(ELM_API_URL_SANDBOX) - } - return api -} - -func (e *ELMAPI) signParamsMap(mapData map[string]interface{}, prefix string) string { - keyValues := make([]string, 0) - for k, v := range mapData { - if k != signKey { - vStr := "" - if prefix == "" { // callback sign - vStr = fmt.Sprint(v) - } else { // call sign - vBytes := utils.MustMarshal(v) - vStr = string(vBytes) - } - keyValues = append(keyValues, k+"="+vStr) - } - } - - sort.Strings(keyValues) - finalStr := prefix + strings.Join(keyValues, "") + e.secret - // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) - return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) -} - -func (e *ELMAPI) signParams(action string, payload *ELMPayload) string { - mapData := utils.MergeMaps(payload.Metas, payload.Params) - return e.signParamsMap(mapData, action+e.token) -} - -func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (retVal *ELMResult, err error) { - if params == nil { - params = make(map[string]interface{}, 0) - } - metas := map[string]interface{}{ - "app_key": e.appKey, - "timestamp": utils.GetCurTimestamp(), - } - - payload := &ELMPayload{ - Token: e.token, - Nop: "1.0.0", - Metas: metas, - Params: params, - Action: action, - Id: utils.GetUUID(), - } - - payload.Signature = e.signParams(action, payload) - - apiAccess := &common.AccessPlatformAPIWithRetryParams{ - MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited, - MaxRecoverableRetryCount: maxRetryCountWhenNetworkException, - SleepSecondWhenExceedLimit: sleepSecondWhenLimited, - Client: e.client, - Request: &http.Request{ - Method: "POST", - URL: e.url, - Header: http.Header{ - "Content-Type": []string{"application/json; charset=utf-8"}, - "Content-Encoding": []string{"gzip, deflate"}, - "User-Agent": []string{"eleme-golang-api"}, - // "x-eleme-requestid": []string{payload.Id}, - }, - - Body: ioutil.NopCloser(strings.NewReader(string(utils.MustMarshal(payload)))), - }, - } - - err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (result string, err error) { - jsonResult1, err := utils.HttpResponse2Json(response) - if err != nil { - baseapi.SugarLogger.Warnf("HttpResponse2Json return:%v", err) - return common.PAErrorLevelGeneralFail, err - } - resultError, _ := jsonResult1["error"].(map[string]interface{}) - retVal = &ELMResult{ - Id: jsonResult1["id"].(string), - Error: resultError, - Result: jsonResult1["result"], - } - errinfoMap := retVal.Error - if errinfoMap == nil { - return common.PAErrorLevelSuccess, nil - } - baseapi.SugarLogger.Debug(jsonResult1) - errCode := errinfoMap["code"].(string) - if errCode == "EXCEED_LIMIT" { - return common.PAErrorLevelExceedLimit, nil - } else if errCode == "SERVER_ERROR" || errCode == "BIZ_SYSTEM_ERROR" || errCode == "BIZ_1006" || errCode == "BUSINESS_ERROR" { - return common.PAErrorLevelRecoverable, nil - } else { - return common.PAErrorLevelGeneralFail, utils.NewErrorCode(errinfoMap["message"].(string), errCode) - } - }) - - return retVal, err -} diff --git a/platform/elmapi/order.go b/platform/elmapi/order.go deleted file mode 100644 index ed3729e6..00000000 --- a/platform/elmapi/order.go +++ /dev/null @@ -1,23 +0,0 @@ -package elmapi - -const ( - ELMOrderStatusPending = "pending" - ELMOrderStatusUnprocessed = "unprocessed" - ELMOrderStatusRefunding = "refunding" - ELMOrderStatusValid = "valid" - ELMOrderStatusInvalid = "invalid" - ELMOrderStatusSettled = "settled" -) - -func (e *ELMAPI) GetOrder(orderId string) (map[string]interface{}, error) { - result, err := e.AccessELM("eleme.order.getOrder", map[string]interface{}{ - "orderId": orderId, - }) - - if err == nil { - innerResult := result.Result.(map[string]interface{}) - return innerResult, nil - } - - return nil, err -} diff --git a/platform/jdapi/callback.go b/platform/jdapi/callback.go deleted file mode 100644 index 7c98ddd4..00000000 --- a/platform/jdapi/callback.go +++ /dev/null @@ -1,121 +0,0 @@ -package jdapi - -import ( - "fmt" - "net/http" - "net/url" - - "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/platform/common" - "git.rosy.net.cn/baseapi/utils" -) - -type JDCallbackResponse struct { - Code string `json:"code"` - Msg string `json:"msg"` - Data string `json:"data"` -} - -type JDOrderMsg struct { - Id int `json:"-"` // 用于传递Jdorder的主键值,减少一次读库操作 - BillId string `json:"billId"` - StatusId string `json:"statusId"` - Timestamp string `json:"timestamp"` - Remark string `json:"remark"` -} - -type JDDeliveryStatusMsg struct { - OrderId string - DeliveryStatusTime string - DeliveryManNo string - DeliveryManName string - DeliveryManPhone string - DeliveryCarrierNo string - DeliveryCarrierName string - DeliveryStatus int - Remark string - FailType string - CreatePin string - OpTime int64 - InputTime string -} - -var ( - SuccessResponse = &JDCallbackResponse{Code: "0", Msg: "success", Data: ""} -) - -func (j *JDAPI) unmarshalData(strData string, msg interface{}) (callbackResponse *JDCallbackResponse) { - err := utils.UnmarshalUseNumber([]byte(strData), msg) - if err != nil { - return &JDCallbackResponse{ - Code: JDerrorCodeAbnormalParam, - Msg: fmt.Sprintf(common.CBErrMsgUnmarshal, strData, err), - Data: strData, - } - } - return nil -} - -func (j *JDAPI) CheckCallbackValidation(request *http.Request) (callbackResponse *JDCallbackResponse) { - 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"] = j.appSecret - mapData["v"] = request.FormValue("v") - mapData[JD_PARAM_JSON] = request.FormValue(JD_PARAM_JSON) - - sign := j.signParams(mapData) - if sign != request.FormValue(signKey) { - baseapi.SugarLogger.Infof("Signature is not ok, mine:%v, get:%v", sign, request.FormValue(signKey)) - return &JDCallbackResponse{ - Code: JDerrorCodeInvalidSign, - Msg: "signature is invalid", - Data: string(utils.MustMarshal(mapData)), - } - } - return nil -} - -func (j *JDAPI) getCommonOrderCallbackMsg(request *http.Request, msg interface{}, needDecode bool) (callbackResponse *JDCallbackResponse) { - if callbackResponse = j.CheckCallbackValidation(request); callbackResponse != nil { - return callbackResponse - } - - jdParamJSON := request.FormValue(JD_PARAM_JSON) - if needDecode { - if jdParamJSON2, err := url.QueryUnescape(jdParamJSON); err == nil { - jdParamJSON = jdParamJSON2 - } else { - return &JDCallbackResponse{ - Code: JDerrorCodeAbnormalParam, - Msg: fmt.Sprintf(common.CBErrMsgUnescape, jdParamJSON, err), - Data: jdParamJSON, - } - } - } - - if callbackResponse = j.unmarshalData(jdParamJSON, msg); callbackResponse != nil { - return callbackResponse - } - return nil -} - -func (j *JDAPI) GetOrderCallbackMsg(request *http.Request) (msg *JDOrderMsg, callbackResponse *JDCallbackResponse) { - msg = new(JDOrderMsg) - callbackResponse = j.getCommonOrderCallbackMsg(request, msg, false) - return msg, callbackResponse -} - -func (j *JDAPI) GetOrderApplyCancelCallbackMsg(request *http.Request) (msg *JDOrderMsg, callbackResponse *JDCallbackResponse) { - msg = new(JDOrderMsg) - callbackResponse = j.getCommonOrderCallbackMsg(request, msg, true) - return msg, callbackResponse -} - -func (j *JDAPI) GetOrderDeliveryCallbackMsg(request *http.Request) (msg *JDDeliveryStatusMsg, callbackResponse *JDCallbackResponse) { - msg = new(JDDeliveryStatusMsg) - callbackResponse = j.getCommonOrderCallbackMsg(request, msg, true) - return msg, callbackResponse -} diff --git a/platform/jdapi/jdapi.go b/platform/jdapi/jdapi.go deleted file mode 100644 index 4a9796cf..00000000 --- a/platform/jdapi/jdapi.go +++ /dev/null @@ -1,347 +0,0 @@ -package jdapi - -import ( - "crypto/md5" - "encoding/json" - "fmt" - "net/http" - "net/url" - "sort" - "time" - - "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/platform/common" - - "git.rosy.net.cn/baseapi/utils" -) - -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 - 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]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 genGetURL(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 NewJDAPI(token, appKey, appSecret string) *JDAPI { - return &JDAPI{token, appKey, appSecret, &http.Client{Timeout: clientTimeout}} -} - -func (j *JDAPI) AccessJDQuery(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"] = j.appKey - params["app_secret"] = j.appSecret - params["token"] = j.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 := 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, - }, - } - - err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (errLevel string, err error) { - jsonResult1, err := utils.HttpResponse2Json(response) - - if err != nil { - baseapi.SugarLogger.Warnf("HttpResponse2Json return:%v", err) - return common.PAErrorLevelGeneralFail, err - } - - code := jsonResult1["code"].(string) - if code == JDErrorCodeSuccess { - retVal = jsonResult1 - return common.PAErrorLevelSuccess, nil - } - baseapi.SugarLogger.Debug(jsonResult1) - 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]interface{}, 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]interface{}, keyToRemove, keyToKeep []string, pageResultParser PageResultParser) ([]interface{}, error) { - if pageResultParser == nil { - pageResultParser = NormalJDQueryHavePageResultParser - } - - 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 := 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)) -} diff --git a/platform/jdapi/order.go b/platform/jdapi/order.go deleted file mode 100644 index c0ee3157..00000000 --- a/platform/jdapi/order.go +++ /dev/null @@ -1,53 +0,0 @@ -package jdapi - -import ( - "git.rosy.net.cn/baseapi/utils" -) - -const ( - JdOrderStatusNew = "32000" - JdOrderStatusAdjust = "33080" - JdOrderStatusUserCancel = "20030" - JdOrderStatusWaitOutStore = "32001" -) - -func (j JDAPI) OrderQuery(jdParams map[string]interface{}) (retVal []interface{}, err error) { - retVal, err = j.AccessJDQueryHavePage("order/es/query", jdParams, nil, nil, nil) - return -} - -func (j JDAPI) QuerySingleOrder(orderId string) ([]interface{}, error) { - jdParams := make(map[string]interface{}) - jdParams["orderId"] = orderId - return j.AccessJDQueryHavePage("order/es/query", jdParams, nil, nil, nil) -} - -func (j JDAPI) LegacyQuerySingleOrder(orderId string) (map[string]interface{}, error) { - jdParams := make(map[string]interface{}) - jdParams["orderId"] = orderId - - result, err := j.AccessJDQuery("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 (j JDAPI) OrderAcceptOperate(orderId string, isAgreed bool) (interface{}, error) { - jdParams := map[string]interface{}{ - "orderId": orderId, - "isAgreed": utils.Bool2String(isAgreed), - "operator": utils.GetAPIOperator(), - } - return j.AccessJDQueryNoPage("ocs/orderAcceptOperate", jdParams, nil, nil) -} diff --git a/platform/dadaapi/callback.go b/platformapi/dadaapi/callback.go similarity index 51% rename from platform/dadaapi/callback.go rename to platformapi/dadaapi/callback.go index cd555f4e..cc3a2853 100644 --- a/platform/dadaapi/callback.go +++ b/platformapi/dadaapi/callback.go @@ -13,34 +13,34 @@ import ( ) const ( - DadaResponseHttpCodeSuccess = 200 - DadaResponseHttpCodeGeneralErr = 555 + ResponseHttpCodeSuccess = 200 + ResponseHttpCodeGeneralErr = 555 ) -type DadaCallbackMsg struct { - ClientId string `json:"client_id"` - OrderId string `json:"order_id"` +type CallbackMsg struct { + ClientID string `json:"client_id"` + OrderID string `json:"order_id"` OrderStatus int `json:"order_status"` CancelReason string `json:"cancel_reason"` CancelFrom int `json:"cancel_from"` UpdateTime int `json:"update_time"` Signature string `json:"signature"` - DmId int `json:"dm_id"` + DmID int `json:"dm_id"` DmName string `json:"dm_name"` DmMobile string `json:"dm_mobile"` } -type DadaCallbackResponse struct { +type CallbackResponse struct { Code int Dummy string `json:"dummy"` } var ( - SuccessResponse = &DadaCallbackResponse{Code: DadaResponseHttpCodeSuccess} - FailedResponse = &DadaCallbackResponse{Code: DadaResponseHttpCodeGeneralErr} + SuccessResponse = &CallbackResponse{Code: ResponseHttpCodeSuccess} + FailedResponse = &CallbackResponse{Code: ResponseHttpCodeGeneralErr} ) -func (d *DadaAPI) signParamsCallback(mapData map[string]interface{}) string { +func (a *API) signCallbackParams(mapData map[string]interface{}) string { values := make([]string, 0) for _, k := range []string{"client_id", "order_id", "update_time"} { values = append(values, fmt.Sprint(mapData[k])) @@ -52,7 +52,7 @@ func (d *DadaAPI) signParamsCallback(mapData map[string]interface{}) string { return fmt.Sprintf("%x", md5.Sum([]byte(finalStr))) } -func (d *DadaAPI) unmarshalData(data []byte, msg interface{}) (callbackResponse *DadaCallbackResponse) { +func (a *API) unmarshalData(data []byte, msg interface{}) (callbackResponse *CallbackResponse) { err := utils.UnmarshalUseNumber(data, msg) if err != nil { return FailedResponse @@ -60,8 +60,8 @@ func (d *DadaAPI) unmarshalData(data []byte, msg interface{}) (callbackResponse return nil } -func (d *DadaAPI) CheckCallbackValidation(mapData map[string]interface{}) (callbackResponse *DadaCallbackResponse) { - sign := d.signParamsCallback(mapData) +func (a *API) CheckCallbackValidation(mapData map[string]interface{}) (callbackResponse *CallbackResponse) { + sign := a.signCallbackParams(mapData) if remoteSign, _ := mapData[signKey].(string); sign != remoteSign { baseapi.SugarLogger.Infof("Signature is not ok, mine:%v, get:%v", sign, remoteSign) return FailedResponse @@ -69,13 +69,13 @@ func (d *DadaAPI) CheckCallbackValidation(mapData map[string]interface{}) (callb return nil } -func (d *DadaAPI) GetOrderCallbackMsg(data []byte) (orderMsg *DadaCallbackMsg, callbackResponse *DadaCallbackResponse) { - orderMsg = new(DadaCallbackMsg) - if callbackResponse = d.unmarshalData(data, orderMsg); callbackResponse != nil { +func (a *API) GetOrderCallbackMsg(data []byte) (msg *CallbackMsg, callbackResponse *CallbackResponse) { + msg = new(CallbackMsg) + if callbackResponse = a.unmarshalData(data, msg); callbackResponse != nil { return nil, FailedResponse } - mapData := structs.Map(orderMsg) - callbackResponse = d.CheckCallbackValidation(mapData) - return orderMsg, callbackResponse + mapData := structs.Map(msg) + callbackResponse = a.CheckCallbackValidation(mapData) + return msg, callbackResponse } diff --git a/platformapi/dadaapi/dadaapi.go b/platformapi/dadaapi/dadaapi.go new file mode 100644 index 00000000..efa304f7 --- /dev/null +++ b/platformapi/dadaapi/dadaapi.go @@ -0,0 +1,176 @@ +package dadaapi + +import ( + "bytes" + "crypto/md5" + "fmt" + "net/http" + "sort" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + sandboxURL = "http://newopen.qa.imdada.cn/" + prodURL = "http://newopen.imdada.cn/" + signKey = "signature" +) + +const ( + ResponseCodeException = -1 + ResponseCodeSuccess = 0 + ResponseCodeSignErr = 2003 + ResponseCodeRetryLater = 2012 + ResponseCodeNetworkErr = 2455 +) + +type API struct { + appKey string + appSecret string + sourceID string + url string + callbackURL string + client *http.Client + config *platformapi.APIConfig +} + +type ResponseResult struct { + Status string `json:"status"` + Code int `json:"code"` + Msg string `json:"msg"` + Result interface{} `json:"result"` + ErrorCode int `json:"errorCode"` +} + +type City struct { + CityName string `json:"cityName"` + CityCode string `json:"cityCode"` +} + +type CancelReason struct { + ID int `json:"id"` + Reason string `json:"reason"` +} + +func New(appKey, appSecret, sourceId, callbackURL string, isProd bool, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + api := &API{ + appKey: appKey, + appSecret: appSecret, + sourceID: sourceId, + callbackURL: callbackURL, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } + if isProd { + api.url = prodURL + } else { + api.url = sandboxURL + } + return api +} + +func (a *API) signParams(mapData map[string]interface{}) string { + keys := make([]string, 0) + for k := range mapData { + if k != signKey { + keys = append(keys, k) + } + } + sort.Strings(keys) + + finalStr := a.appSecret + for _, k := range keys { + finalStr += k + fmt.Sprint(mapData[k]) + } + + finalStr += a.appSecret + // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) + return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) +} + +func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *ResponseResult, err error) { + params2 := make(map[string]interface{}) + + params2["app_key"] = a.appKey + params2["timestamp"] = utils.Int64ToStr(utils.GetCurTimestamp()) + params2["format"] = "json" + params2["v"] = "1.0" + params2["source_id"] = a.sourceID + if params == nil { + params2["body"] = "" + } else { + params2["body"] = string(utils.MustMarshal(params)) + } + params2[signKey] = a.signParams(params2) + params2Bytes := utils.MustMarshal(params2) + request, _ := http.NewRequest("POST", a.url+action, bytes.NewReader(params2Bytes)) + request.Header.Set("Content-Type", "application/json") + + err = platformapi.AccessPlatformAPIWithRetry(a.client, request, a.config, func(response *http.Response) (result string, err error) { + jsonResult1, err := utils.HTTPResponse2Json(response) + if err != nil { + return platformapi.ErrLevelGeneralFail, err + } + code := int(utils.MustInterface2Int64(jsonResult1["code"])) + retVal = &ResponseResult{ + Code: code, + ErrorCode: code, + Msg: jsonResult1["msg"].(string), + Status: jsonResult1["status"].(string), + } + if code == ResponseCodeSuccess { + retVal.Result = jsonResult1["result"] + return platformapi.ErrLevelSuccess, nil + } + baseapi.SugarLogger.Warnf("response business code is not ok, data:%v, code:%v", jsonResult1, code) + newErr := utils.NewErrorIntCode(retVal.Msg, code) + if code == ResponseCodeRetryLater || code == ResponseCodeNetworkErr { + return platformapi.ErrLevelRecoverableErr, newErr + } + return platformapi.ErrLevelCodeIsNotOK, newErr + }) + + return retVal, err +} + +func (a *API) GetCities() (retVal []City, err error) { + result, err := a.AccessAPI("api/cityCode/list", nil) + if err != nil { + return nil, err + } + + cites := result.Result.([]interface{}) + for _, v := range cites { + v2 := v.(map[string]interface{}) + city := City{ + CityName: v2["cityName"].(string), + CityCode: v2["cityCode"].(string), + } + retVal = append(retVal, city) + } + return retVal, nil +} + +func (a *API) GetCancelReasons() (retVal []CancelReason, err error) { + result, err := a.AccessAPI("api/order/cancel/reasons", nil) + if err != nil { + return nil, err + } + + cites := result.Result.([]interface{}) + for _, v := range cites { + v2 := v.(map[string]interface{}) + reason := CancelReason{ + ID: int(utils.MustInterface2Int64(v2["id"])), + Reason: v2["reason"].(string), + } + retVal = append(retVal, reason) + } + return retVal, nil +} diff --git a/platform/dadaapi/dadaapi_test.go b/platformapi/dadaapi/dadaapi_test.go similarity index 84% rename from platform/dadaapi/dadaapi_test.go rename to platformapi/dadaapi/dadaapi_test.go index f9d9375c..729518c5 100644 --- a/platform/dadaapi/dadaapi_test.go +++ b/platformapi/dadaapi/dadaapi_test.go @@ -10,7 +10,7 @@ import ( ) var ( - dadaapi *DadaAPI + dadaapi *API sugarLogger *zap.SugaredLogger testOrder *OperateOrderRequiredParams ) @@ -25,13 +25,13 @@ func init() { baseapi.Init(sugarLogger) // sandbox - dadaapi = NewDadaAPI("dada9623324449cd250", "30c2abbfe8a8780ad5aace46300c64b9", "73753", "http://callback.jxc4.com/dada/order", false) + dadaapi = New("dada9623324449cd250", "30c2abbfe8a8780ad5aace46300c64b9", "73753", "http://callback.jxc4.com/dada/order", false) // prod testOrder = &OperateOrderRequiredParams{ ShopNo: testShopNo, - OriginId: "234242342", + OriginID: "234242342", CityCode: "028", CargoPrice: 12.34, IsPrepay: 1, @@ -52,21 +52,21 @@ func TestSignCallback(t *testing.T) { sampleData := `{"signature":"5a277f2519b6011028ff541fb09b8553","client_id":"275000419162381","order_id":"234242342","order_status":1,"cancel_reason":"","cancel_from":0,"dm_id":0,"update_time":1529564947}` mapData := make(map[string]interface{}) utils.UnmarshalUseNumber([]byte(sampleData), &mapData) - sign := dadaapi.signParamsCallback(mapData) + sign := dadaapi.signCallbackParams(mapData) if sign != mapData["signature"] { t.Fatal("sign is not correct") } } -func TestAccessDada(t *testing.T) { +func TestAccessAPI(t *testing.T) { body := make(map[string]interface{}) body["order_id"] = "fakeorderid" - result, err := dadaapi.AccessDada("api/order/status/query", body) + result, err := dadaapi.AccessAPI("api/order/status/query", body) failed := true if err != nil { if err2, ok := err.(*utils.ErrorWithCode); ok { - if err2.IntCode() != DadaCodeSignErr { + if err2.IntCode() != ResponseCodeSignErr { failed = false } } @@ -104,7 +104,7 @@ func TestGetReasons(t *testing.T) { // baseapi.SugarLogger.Debug(result) failed := true for _, reason := range result { - if reason.Id == 1 { + if reason.ID == 1 { failed = false } } @@ -131,7 +131,7 @@ func TestReaddOrder(t *testing.T) { } func TestCancelOrder(t *testing.T) { - result, err := dadaapi.CancelOrder("234242342", ReasonIdClientDontWantItAnymore, "") + result, err := dadaapi.CancelOrder("234242342", ReasonIDClientDontWantItAnymore, "") if err != nil { t.Fatal(err.Error()) } diff --git a/platform/dadaapi/order.go b/platformapi/dadaapi/order.go similarity index 58% rename from platform/dadaapi/order.go rename to platformapi/dadaapi/order.go index 6b18d9ef..3b13eff7 100644 --- a/platform/dadaapi/order.go +++ b/platformapi/dadaapi/order.go @@ -6,16 +6,16 @@ import ( ) const ( - ReasonIdNobodyAccept = 1 - ReasonIdNobodyPickup = 2 - ReasonIdCourierIsPig = 3 - ReasonIdClientCanceled = 4 - ReasonIdOrderIsWrong = 5 - ReasonIdCourierWantMeCancel = 34 - ReasonIdCourierDontWantToPickup = 35 - ReasonIdClientDontWantItAnymore = 36 - ReasonIdCourierShirk = 37 - ReasonIdOther = 10000 + ReasonIDNobodyAccept = 1 + ReasonIDNobodyPickup = 2 + ReasonIDCourierIsPig = 3 + ReasonIDClientCanceled = 4 + ReasonIDOrderIsWrong = 5 + ReasonIDCourierWantMeCancel = 34 + ReasonIDCourierDontWantToPickup = 35 + ReasonIDClientDontWantItAnymore = 36 + ReasonIDCourierShirk = 37 + ReasonIDOther = 10000 ) const ( @@ -33,7 +33,7 @@ const ( type OperateOrderRequiredParams struct { ShopNo string `json:"shop_no"` - OriginId string `json:"origin_id"` + OriginID string `json:"origin_id"` CityCode string `json:"city_code"` CargoPrice float64 `json:"cargo_price"` IsPrepay int `json:"is_prepay"` @@ -58,10 +58,10 @@ type CancelOrderResponse struct { DeductFee float64 `json:"deduct_fee"` } -func (d *DadaAPI) QueryOrderInfo(orderId string) (retVal map[string]interface{}, err error) { +func (a *API) QueryOrderInfo(orderId string) (retVal map[string]interface{}, err error) { params := make(map[string]interface{}) params["order_id"] = orderId - result, err := d.AccessDada("api/order/status/query", params) + result, err := a.AccessAPI("api/order/status/query", params) if err != nil { return nil, err } @@ -87,12 +87,12 @@ func map2CreateOrderResponse(mapData map[string]interface{}) *CreateOrderRespons return retVal } -func (d *DadaAPI) operateOrder(action string, orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { +func (a *API) operateOrder(action string, orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { params := structs.Map(orderInfo) - params["callback"] = d.callbackURL + params["callback"] = a.callbackURL allParams := utils.MergeMaps(params, addParams) - result, err := d.AccessDada(action, allParams) + result, err := a.AccessAPI(action, allParams) if err != nil { return nil, err } @@ -100,25 +100,25 @@ func (d *DadaAPI) operateOrder(action string, orderInfo *OperateOrderRequiredPar return map2CreateOrderResponse(result.Result.(map[string]interface{})), nil } -func (d *DadaAPI) AddOrder(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { - return d.operateOrder("api/order/addOrder", orderInfo, addParams) +func (a *API) AddOrder(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { + return a.operateOrder("api/order/addOrder", orderInfo, addParams) } -func (d *DadaAPI) ReaddOrder(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { - return d.operateOrder("api/order/reAddOrder", orderInfo, addParams) +func (a *API) ReaddOrder(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { + return a.operateOrder("api/order/reAddOrder", orderInfo, addParams) } -func (d *DadaAPI) QueryDeliverFee(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { - return d.operateOrder("api/order/queryDeliverFee", orderInfo, addParams) +func (a *API) QueryDeliverFee(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { + return a.operateOrder("api/order/queryDeliverFee", orderInfo, addParams) } -func (d *DadaAPI) AddOrderAfterQuery(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { - return d.operateOrder("api/order/addAfterQuery", orderInfo, addParams) +func (a *API) AddOrderAfterQuery(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) { + return a.operateOrder("api/order/addAfterQuery", orderInfo, addParams) } -func (d *DadaAPI) CancelOrder(orderId string, cancelOrderReasonId int, cancelOrderReason string) (retVal *CancelOrderResponse, err error) { +func (a *API) CancelOrder(orderId string, cancelOrderReasonId int, cancelOrderReason string) (retVal *CancelOrderResponse, err error) { mapData := utils.Params2Map("order_id", orderId, "cancel_reason_id", cancelOrderReasonId, "cancel_reason", cancelOrderReason) - result, err := d.AccessDada("api/order/formalCancel", mapData) + result, err := a.AccessAPI("api/order/formalCancel", mapData) if err != nil { return nil, err } diff --git a/platformapi/elmapi/callback.go b/platformapi/elmapi/callback.go new file mode 100644 index 00000000..34e55aa4 --- /dev/null +++ b/platformapi/elmapi/callback.go @@ -0,0 +1,69 @@ +package elmapi + +import ( + "fmt" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" + "github.com/fatih/structs" +) + +// https://open.shop.ele.me/openapi/documents/callback +const ( + MsgTypeOrderValid = 10 + MsgTypeMerchantValid = 12 + MsgTypeOrderCanceled = 14 + MsgTypeMerchantInvalid = 15 + MsgTypeOrderForceInvalid = 17 + MsgTypeOrderFinished = 18 +) + +type CallbackResponse struct { + Message string `json:"message"` +} + +type CallbackMsg struct { + AppID int `json:"appId"` + RequestID string `json:"requestId"` + Type int `json:"type"` + Message string `json:"message"` + ShopID int `json:"shopId"` + Timestamp int64 `json:"timestamp"` + UserID int64 `json:"userId"` + Signature string `json:"signature"` +} + +var ( + SuccessResponse = &CallbackResponse{"ok"} +) + +func (e *API) unmarshalData(data []byte, msg interface{}) (callbackResponse *CallbackResponse) { + err := utils.UnmarshalUseNumber(data, msg) + if err != nil { + return &CallbackResponse{ + Message: fmt.Sprintf(platformapi.ErrStrUnmarshalError, data, err), + } + } + return nil +} + +func (e *API) CheckCallbackValidation(mapData map[string]interface{}) (callbackResponse *CallbackResponse) { + sign := e.signParamsMap(mapData, "") + if remoteSign, _ := mapData[signKey].(string); sign != remoteSign { + baseapi.SugarLogger.Infof("Signature is not ok, mine:%v, get:%v", sign, remoteSign) + return &CallbackResponse{Message: platformapi.ErrStrCallbackSignatureIsWrong} + } + return nil +} + +func (e *API) GetCallbackMsg(data []byte) (msg *CallbackMsg, callbackResponse *CallbackResponse) { + msg = new(CallbackMsg) + if callbackResponse = e.unmarshalData(data, msg); callbackResponse != nil { + return nil, callbackResponse + } + + mapData := structs.Map(msg) + callbackResponse = e.CheckCallbackValidation(mapData) + return msg, callbackResponse +} diff --git a/platformapi/elmapi/elmapi.go b/platformapi/elmapi/elmapi.go new file mode 100644 index 00000000..988d215f --- /dev/null +++ b/platformapi/elmapi/elmapi.go @@ -0,0 +1,155 @@ +package elmapi + +import ( + "crypto/md5" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strings" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + sandboxURL = "https://open-api-sandbox.shop.ele.me/api/v1/" + prodURL = "https://open-api.shop.ele.me/api/v1/" + signKey = "signature" +) + +type ResponseResult struct { + ID string + Result interface{} + Error map[string]interface{} +} + +type API struct { + token string + appKey string + secret string + url *url.URL + client *http.Client + config *platformapi.APIConfig +} + +type payload struct { + Token string `json:"token"` + Nop string `json:"nop"` + Metas map[string]interface{} `json:"metas"` + Params map[string]interface{} `json:"params"` + Action string `json:"action"` + ID string `json:"id"` + Signature string `json:"signature"` +} + +func New(token, appKey, secret string, isProd bool, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + api := &API{ + token: token, + appKey: appKey, + secret: secret, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } + + if isProd { + api.url, _ = url.Parse(prodURL) + } else { + api.url, _ = url.Parse(sandboxURL) + } + return api +} + +func (a *API) signParamsMap(mapData map[string]interface{}, prefix string) string { + keyValues := make([]string, 0) + for k, v := range mapData { + if k != signKey { + vStr := "" + if prefix == "" { // callback sign + vStr = fmt.Sprint(v) + } else { // call sign + vBytes := utils.MustMarshal(v) + vStr = string(vBytes) + } + keyValues = append(keyValues, k+"="+vStr) + } + } + + sort.Strings(keyValues) + finalStr := prefix + strings.Join(keyValues, "") + a.secret + // baseapi.SugarLogger.Debugf("sign str:%v", finalStr) + return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) +} + +func (a *API) signParams(action string, pl *payload) string { + mapData := utils.MergeMaps(pl.Metas, pl.Params) + return a.signParamsMap(mapData, action+a.token) +} + +func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *ResponseResult, err error) { + if params == nil { + params = make(map[string]interface{}, 0) + } + metas := map[string]interface{}{ + "app_key": a.appKey, + "timestamp": utils.GetCurTimestamp(), + } + + pl := &payload{ + Token: a.token, + Nop: "1.0.0", + Metas: metas, + Params: params, + Action: action, + ID: utils.GetUUID(), + } + + pl.Signature = a.signParams(action, pl) + + request := &http.Request{ + Method: "POST", + URL: a.url, + Header: http.Header{ + "Content-Type": []string{"application/json; charset=utf-8"}, + "Content-Encoding": []string{"gzip, deflate"}, + "User-Agent": []string{"eleme-golang-api"}, + // "x-eleme-requestid": []string{payload.Id}, + }, + + Body: ioutil.NopCloser(strings.NewReader(string(utils.MustMarshal(pl)))), + } + err = platformapi.AccessPlatformAPIWithRetry(a.client, request, a.config, func(response *http.Response) (result string, err error) { + jsonResult1, err := utils.HTTPResponse2Json(response) + if err != nil { + return platformapi.ErrLevelGeneralFail, err + } + resultError, _ := jsonResult1["error"].(map[string]interface{}) + retVal = &ResponseResult{ + ID: jsonResult1["id"].(string), + Error: resultError, + Result: jsonResult1["result"], + } + errinfoMap := retVal.Error + if errinfoMap == nil { + return platformapi.ErrLevelSuccess, nil + } + code := errinfoMap["code"].(string) + baseapi.SugarLogger.Warnf("response business code is not ok, data:%v, code:%v", jsonResult1, code) + newErr := utils.NewErrorCode(errinfoMap["message"].(string), code) + if code == "EXCEED_LIMIT" { + return platformapi.ErrLevelExceedLimit, newErr + } else if code == "SERVER_ERROR" || code == "BIZ_SYSTEM_ERROR" || code == "BIZ_1006" || code == "BUSINESS_ERROR" { + return platformapi.ErrLevelRecoverableErr, newErr + } else { + return platformapi.ErrLevelCodeIsNotOK, newErr + } + }) + + return retVal, err +} diff --git a/platform/elmapi/elmapi_test.go b/platformapi/elmapi/elmapi_test.go similarity index 79% rename from platform/elmapi/elmapi_test.go rename to platformapi/elmapi/elmapi_test.go index 19a56b57..55f06f30 100644 --- a/platform/elmapi/elmapi_test.go +++ b/platformapi/elmapi/elmapi_test.go @@ -10,7 +10,7 @@ import ( ) var ( - elmapi *ELMAPI + elmapi *API sugarLogger *zap.SugaredLogger ) @@ -20,18 +20,18 @@ func init() { baseapi.Init(sugarLogger) // sandbox - // elmapi = NewELMAPI("b4f7e424475c3758c111dc60ceec3e2a", "RwT214gAsS", "56afff4b9ebd8a7eb532d18fa33f17be57f9b9db", false) + // elmapi = New("b4f7e424475c3758c111dc60ceec3e2a", "RwT214gAsS", "56afff4b9ebd8a7eb532d18fa33f17be57f9b9db", false) // prod - elmapi = NewELMAPI("bab2a27f99562f394b411dbb9a6214da", "KLRDcOZGrk", "1fc221f8265506531da36fb613d5f5ad673f2e9a", true) + elmapi = New("bab2a27f99562f394b411dbb9a6214da", "KLRDcOZGrk", "1fc221f8265506531da36fb613d5f5ad673f2e9a", true) } func TestTest(t *testing.T) { sugarLogger.Debug(utils.GetCurTimeStr()) } -func TestAccessELM(t *testing.T) { - result, err := elmapi.AccessELM("eleme.user.getUser", nil) +func TestAccessAPI(t *testing.T) { + result, err := elmapi.AccessAPI("eleme.user.getUser", nil) if err != nil { t.Fatalf("Error when accessing AccessJDQuery result:%v, error:%v", result, err) } else { @@ -59,6 +59,6 @@ func TestCallbackSign(t *testing.T) { jsonStr := `{"requestId":"200016348669063447","type":18,"appId":78247922,"message":"{\"orderId\":\"3024923917769149510\",\"state\":\"settled\",\"shopId\":157492364,\"updateTime\":1529465510,\"role\":1}","shopId":157492364,"timestamp":1529465510255,"signature":"D65F917D93B4F599B85486C799599141","userId":336072266322770688}` msg, response := elmapi.GetCallbackMsg([]byte(jsonStr)) if response != nil || msg == nil { - t.Fatal("Something wrong") + t.Fatal(response, msg) } } diff --git a/platformapi/elmapi/order.go b/platformapi/elmapi/order.go new file mode 100644 index 00000000..eb3bcf25 --- /dev/null +++ b/platformapi/elmapi/order.go @@ -0,0 +1,23 @@ +package elmapi + +const ( + OrderStatusPending = "pending" + OrderStatusUnprocessed = "unprocessed" + OrderStatusRefunding = "refunding" + OrderStatusValid = "valid" + OrderStatusInvalid = "invalid" + OrderStatusSettled = "settled" +) + +func (a *API) GetOrder(orderID string) (map[string]interface{}, error) { + result, err := a.AccessAPI("eleme.order.getOrder", map[string]interface{}{ + "orderId": orderID, + }) + + if err == nil { + innerResult := result.Result.(map[string]interface{}) + return innerResult, nil + } + + return nil, err +} diff --git a/platformapi/jdapi/callback.go b/platformapi/jdapi/callback.go new file mode 100644 index 00000000..6e70125b --- /dev/null +++ b/platformapi/jdapi/callback.go @@ -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 +} diff --git a/platformapi/jdapi/jdapi.go b/platformapi/jdapi/jdapi.go new file mode 100644 index 00000000..85e25dc0 --- /dev/null +++ b/platformapi/jdapi/jdapi.go @@ -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.com)API分组有接口详细说明。 + 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)) +} diff --git a/platform/jdapi/jdapi_test.go b/platformapi/jdapi/jdapi_test.go similarity index 67% rename from platform/jdapi/jdapi_test.go rename to platformapi/jdapi/jdapi_test.go index 5e1b3f52..e8f520c3 100644 --- a/platform/jdapi/jdapi_test.go +++ b/platformapi/jdapi/jdapi_test.go @@ -11,7 +11,7 @@ import ( ) var ( - jdapi *JDAPI + jdapi *API sugarLogger *zap.SugaredLogger ) @@ -19,18 +19,18 @@ func init() { logger, _ := zap.NewDevelopment() sugarLogger = logger.Sugar() baseapi.Init(sugarLogger) - jdapi = NewJDAPI("91633f2a-c5f5-4982-a925-a220d19095c3", "1dba76d40cac446ca500c0391a0b6c9d", "a88d031a1e7b462cb1579f12e97fe7f4") - // jdapi = NewJDAPI("c8854ef2-f80a-45ee-aceb-dc8014d646f8", "06692746f7224695ad4788ce340bc854", "d6b42a35a7414a5490d811654d745c84") + 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 TestAccessJDQuery(t *testing.T) { - result, err := jdapi.AccessJDQuery("address/allcities", nil) +func TestAccessAPI(t *testing.T) { + result, err := jdapi.AccessAPI("address/allcities", nil) if err != nil { - t.Fatalf("Error when accessing AccessJDQuery: %v", err) + t.Fatalf("Error when accessing AccessAPI: %v", err) } else { code := result["code"].(string) if code != "0" { @@ -39,10 +39,10 @@ func TestAccessJDQuery(t *testing.T) { } } -func TestAccessJDQueryNoPage(t *testing.T) { - result, err := jdapi.AccessJDQueryNoPage("address/allcities", nil, []string{"yn"}, nil) +func TestAccessAPINoPage(t *testing.T) { + result, err := jdapi.AccessAPINoPage("address/allcities", nil, []string{"yn"}, nil) if err != nil { - t.Fatalf("AccessJDQueryNoPage return error:%v", err) + t.Fatalf("TestAccessAPINoPage return error:%v", err) } cityInfo := result.([]interface{}) if len(cityInfo) == 0 { @@ -57,14 +57,14 @@ func TestAccessJDQueryNoPage(t *testing.T) { } } -func TestAccessJDQueryHavePage(t *testing.T) { +func TestAccessAPIHavePage(t *testing.T) { jdParams := map[string]interface{}{ "pageNo": 1, "pageSize": 20, } - skuInfo, err := jdapi.AccessJDQueryHavePage("pms/querySkuInfos", jdParams, nil, []string{"skuName", "skuId"}, nil) + skuInfo, err := jdapi.AccessAPIHavePage("pms/querySkuInfos", jdParams, nil, []string{"skuName", "skuId"}, nil) if err != nil { - t.Fatalf("AccessJDQueryHavePage return error:%v", err) + t.Fatalf("AccessAPIHavePage return error:%v", err) } if len(skuInfo) == 0 { @@ -79,12 +79,12 @@ func TestAccessJDQueryHavePage(t *testing.T) { } } -func TestGenGetURL(t *testing.T) { +func TestGenerateURL(t *testing.T) { params := make(map[string]interface{}) params["key"] = "v" params["key2"] = "v2" - fullURL := genGetURL(jdAPIURL, "address/allcities", params) + fullURL := generateURL(prodURL, "address/allcities", params) response, err := http.Get(fullURL) if err != nil { diff --git a/platformapi/jdapi/order.go b/platformapi/jdapi/order.go new file mode 100644 index 00000000..bb29b2c7 --- /dev/null +++ b/platformapi/jdapi/order.go @@ -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) +} diff --git a/platform/mtpsapi/callback.go b/platformapi/mtpsapi/callback.go similarity index 63% rename from platform/mtpsapi/callback.go rename to platformapi/mtpsapi/callback.go index 02e7695e..a9c753cb 100644 --- a/platform/mtpsapi/callback.go +++ b/platformapi/mtpsapi/callback.go @@ -7,41 +7,41 @@ import ( "git.rosy.net.cn/baseapi/utils" ) -type MtpsCallbackResponse struct { +type CallbackResponse struct { Code int `json:"code"` } -type MtpsCallbackCommon struct { +type CallbackCommonMsg struct { AppKey string Timestamp int64 Sign string } -type MtpsCallbackOrderMsg struct { - MtpsOrderInfoCommon - MtpsCallbackCommon +type CallbackOrderMsg struct { + OrderInfoCommon + CallbackCommonMsg Status int CancelReasonId int CancelReason string } -type MtpsCallbackOrderExceptionMsg struct { - MtpsOrderInfoCommon - MtpsCallbackCommon - ExceptionId int64 +type CallbackOrderExceptionMsg struct { + OrderInfoCommon + CallbackCommonMsg + ExceptionID int64 ExceptionCode int ExceptionDescr string ExceptionTime int64 } var ( - SuccessResponse = &MtpsCallbackResponse{Code: 0} - SignatureIsNotOk = &MtpsCallbackResponse{Code: -1} + SuccessResponse = &CallbackResponse{Code: 0} + SignatureIsNotOk = &CallbackResponse{Code: -1} ) -func (m *MTPSAPI) CheckCallbackValidation(request *http.Request) (callbackResponse *MtpsCallbackResponse) { +func (a *API) CheckCallbackValidation(request *http.Request) (callbackResponse *CallbackResponse) { request.ParseForm() - sign := m.signParams(request.PostForm) + sign := a.signParams(request.PostForm) if sign != request.FormValue(signKey) { baseapi.SugarLogger.Infof("Signature is not ok, mine:%v, get:%v", sign, request.FormValue(signKey)) return SignatureIsNotOk @@ -50,7 +50,7 @@ func (m *MTPSAPI) CheckCallbackValidation(request *http.Request) (callbackRespon for _, valueKey := range []string{"delivery_id", "mt_peisong_id", "order_id"} { baseapi.SugarLogger.Errorf("Missing mandatory param:%v", valueKey) if request.FormValue(valueKey) == "" { - return &MtpsCallbackResponse{ + return &CallbackResponse{ Code: -1, } } @@ -58,19 +58,19 @@ func (m *MTPSAPI) CheckCallbackValidation(request *http.Request) (callbackRespon return nil } -func (m *MTPSAPI) GetOrderCallbackMsg(request *http.Request) (orderMsg *MtpsCallbackOrderMsg, callbackResponse *MtpsCallbackResponse) { - if callbackResponse = m.CheckCallbackValidation(request); callbackResponse != nil { +func (a *API) GetOrderCallbackMsg(request *http.Request) (orderMsg *CallbackOrderMsg, callbackResponse *CallbackResponse) { + if callbackResponse = a.CheckCallbackValidation(request); callbackResponse != nil { return nil, callbackResponse } - orderMsg = &MtpsCallbackOrderMsg{ - MtpsOrderInfoCommon: MtpsOrderInfoCommon{ + orderMsg = &CallbackOrderMsg{ + OrderInfoCommon: OrderInfoCommon{ DeliveryId: utils.Str2Int64(request.FormValue("delivery_id")), MtPeisongId: request.FormValue("mt_peisong_id"), OrderId: request.FormValue("order_id"), CourierName: request.FormValue("courier_name"), CourierPhone: request.FormValue("courier_phone"), }, - MtpsCallbackCommon: MtpsCallbackCommon{ + CallbackCommonMsg: CallbackCommonMsg{ AppKey: request.FormValue("appkey"), Timestamp: utils.Str2Int64(request.FormValue("timestamp")), Sign: request.FormValue("sign"), @@ -82,24 +82,24 @@ func (m *MTPSAPI) GetOrderCallbackMsg(request *http.Request) (orderMsg *MtpsCall return orderMsg, nil } -func (m *MTPSAPI) GetOrderExceptionCallbackMsg(request *http.Request) (orderMsg *MtpsCallbackOrderExceptionMsg, callbackResponse *MtpsCallbackResponse) { - if callbackResponse = m.CheckCallbackValidation(request); callbackResponse != nil { +func (a *API) GetOrderExceptionCallbackMsg(request *http.Request) (orderMsg *CallbackOrderExceptionMsg, callbackResponse *CallbackResponse) { + if callbackResponse = a.CheckCallbackValidation(request); callbackResponse != nil { return nil, callbackResponse } - orderMsg = &MtpsCallbackOrderExceptionMsg{ - MtpsOrderInfoCommon: MtpsOrderInfoCommon{ + orderMsg = &CallbackOrderExceptionMsg{ + OrderInfoCommon: OrderInfoCommon{ DeliveryId: utils.Str2Int64(request.FormValue("delivery_id")), MtPeisongId: request.FormValue("mt_peisong_id"), OrderId: request.FormValue("order_id"), CourierName: request.FormValue("courier_name"), CourierPhone: request.FormValue("courier_phone"), }, - MtpsCallbackCommon: MtpsCallbackCommon{ + CallbackCommonMsg: CallbackCommonMsg{ AppKey: request.FormValue("appkey"), Timestamp: utils.Str2Int64(request.FormValue("timestamp")), Sign: request.FormValue("sign"), }, - ExceptionId: utils.Str2Int64(request.FormValue("exception_id")), + ExceptionID: utils.Str2Int64(request.FormValue("exception_id")), ExceptionCode: int(utils.Str2Int64(request.FormValue("exception_code"))), ExceptionDescr: request.FormValue("exception_descr"), ExceptionTime: utils.Str2Int64(request.FormValue("exception_time")), diff --git a/platform/mtpsapi/mtpsapi.go b/platformapi/mtpsapi/mtpsapi.go similarity index 51% rename from platform/mtpsapi/mtpsapi.go rename to platformapi/mtpsapi/mtpsapi.go index 8ff5b9ba..9a392910 100644 --- a/platform/mtpsapi/mtpsapi.go +++ b/platformapi/mtpsapi/mtpsapi.go @@ -8,22 +8,14 @@ import ( "sort" "strconv" "strings" - "time" "github.com/fatih/structs" "git.rosy.net.cn/baseapi" - "git.rosy.net.cn/baseapi/platform/common" + "git.rosy.net.cn/baseapi/platformapi" "git.rosy.net.cn/baseapi/utils" ) -const ( - clientTimeout = time.Second * 10 - sleepSecondWhenLimited = 6 * time.Second - maxRetryCountWhenNetworkException = 3 - maxRetryCountWhenReachLimited = 10 -) - const ( mtpsAPIURL = "https://peisongopen.meituan.com/api" signKey = "sign" @@ -38,9 +30,9 @@ const ( ) const ( - DSCRapid = 4011 - DSCIntime = 4012 - DSCTogether = 4013 + DeliveryServiceCodeRapid = 4011 + DeliveryServiceCodeIntime = 4012 + DeliveryServiceCodeTogether = 4013 ) const ( @@ -78,7 +70,7 @@ const ( CancelReasonRideerOther = 399 ) -type MtpsOrderInfoCommon struct { +type OrderInfoCommon struct { DeliveryId int64 MtPeisongId string OrderId string @@ -86,30 +78,30 @@ type MtpsOrderInfoCommon struct { CourierPhone string } -type MtpsOrderInfo struct { - MtpsOrderInfoCommon +type OrderInfo struct { + OrderInfoCommon Status int OperateTime int - CancelReasonId int + CancelReasonID int CancelReason string } -type MtpsOrderResponse struct { - MtPeisongId string `json:"mt_peisong_id` - DeliveryId int64 `json:"delivery_id` - OrderId string `json:"order_id` +type OrderResponse struct { + MtPeisongID string `json:"mt_peisong_id"` + DeliveryID int64 `json:"delivery_id"` + OrderID string `json:"order_id"` } -type MTPSResult struct { +type ResponseResult struct { Code int `json:"code"` Message string `json:"message"` Data map[string]interface{} `json:"data"` } -type MtpsCreateOrderByShopInfo struct { - DeliveryId int64 `json:"delivery_id"` - OrderId string `json:"order_id"` - ShopId string `json:"shop_id"` +type CreateOrderByShopParam struct { + DeliveryID int64 `json:"delivery_id"` + OrderID string `json:"order_id"` + ShopID string `json:"shop_id"` DeliveryServiceCode int `json:"delivery_service_code"` ReceiverName string `json:"receiver_name"` ReceiverAddress string `json:"receiver_address"` @@ -123,23 +115,27 @@ type MtpsCreateOrderByShopInfo struct { OrderType int `json:"order_type"` } -type MTPSAPI struct { +type API struct { appKey string secret string client *http.Client + config *platformapi.APIConfig } -func NewMTPSAPI(appKey, secret string) *MTPSAPI { - api := &MTPSAPI{ +func New(appKey, secret string, config ...*platformapi.APIConfig) *API { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + return &API{ appKey: appKey, secret: secret, - client: &http.Client{Timeout: clientTimeout}, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, } - - return api } -func (m *MTPSAPI) signParams(params url.Values) string { +func (a *API) signParams(params url.Values) string { keys := make([]string, 0) for k := range params { if k != signKey { @@ -148,7 +144,7 @@ func (m *MTPSAPI) signParams(params url.Values) string { } sort.Strings(keys) - finalStr := m.secret + finalStr := a.secret for _, key := range keys { valStr := strings.Join(params[key], "") if valStr != "" { @@ -160,7 +156,7 @@ func (m *MTPSAPI) signParams(params url.Values) string { return fmt.Sprintf("%x", sha1.Sum([]byte(finalStr))) } -func (m *MTPSAPI) AccessMTPS(action string, params map[string]interface{}) (retVal *MTPSResult, err error) { +func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *ResponseResult, err error) { if params == nil { panic("params is nil!") } @@ -169,54 +165,47 @@ func (m *MTPSAPI) AccessMTPS(action string, params map[string]interface{}) (retV for k, v := range params { params2[k] = []string{fmt.Sprint(v)} } - params2["appkey"] = []string{m.appKey} + params2["appkey"] = []string{a.appKey} params2["timestamp"] = []string{utils.Int64ToStr(utils.GetCurTimestamp())} params2["version"] = []string{"1.0"} - params2[signKey] = []string{m.signParams(params2)} + params2[signKey] = []string{a.signParams(params2)} // baseapi.SugarLogger.Debug(params2.Encode()) request, _ := http.NewRequest("POST", mtpsAPIURL+"/"+action, strings.NewReader(params2.Encode())) request.Header.Set("Content-Type", "application/x-www-form-urlencoded") - apiAccess := &common.AccessPlatformAPIWithRetryParams{ - MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited, - MaxRecoverableRetryCount: maxRetryCountWhenNetworkException, - SleepSecondWhenExceedLimit: sleepSecondWhenLimited, - Client: m.client, - Request: request, - } - err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (result string, err error) { - jsonResult1, err := utils.HttpResponse2Json(response) + err = platformapi.AccessPlatformAPIWithRetry(a.client, request, a.config, func(response *http.Response) (result string, err error) { + jsonResult1, err := utils.HTTPResponse2Json(response) if err != nil { - baseapi.SugarLogger.Warnf("HttpResponse2Json return:%v", err) - return common.PAErrorLevelGeneralFail, err + return platformapi.ErrLevelGeneralFail, platformapi.ErrResponseDataFormatWrong } code := int(utils.MustInterface2Int64(jsonResult1["code"])) - retVal = &MTPSResult{ + retVal = &ResponseResult{ Code: code, } if code == ResponseCodeSuccess { if innerData, ok := jsonResult1["data"]; ok { retVal.Data, _ = innerData.(map[string]interface{}) } - return common.PAErrorLevelSuccess, nil + return platformapi.ErrLevelSuccess, nil } - baseapi.SugarLogger.Debug(jsonResult1) + baseapi.SugarLogger.Warnf("response business code is not ok, data:%v, code:%v", jsonResult1, code) retVal.Message = jsonResult1["message"].(string) - return common.PAErrorLevelGeneralFail, utils.NewErrorIntCode(retVal.Message, code) + newErr := utils.NewErrorIntCode(retVal.Message, code) + return platformapi.ErrLevelCodeIsNotOK, newErr }) return retVal, err } -func (m *MTPSAPI) result2OrderResponse(result *MTPSResult) (order *MtpsOrderResponse) { - order = new(MtpsOrderResponse) - order.MtPeisongId = result.Data["mt_peisong_id"].(string) - order.DeliveryId = utils.MustInterface2Int64(result.Data["delivery_id"]) - order.OrderId = result.Data["order_id"].(string) +func (a *API) result2OrderResponse(result *ResponseResult) (order *OrderResponse) { + order = new(OrderResponse) + order.MtPeisongID = result.Data["mt_peisong_id"].(string) + order.DeliveryID = utils.MustInterface2Int64(result.Data["delivery_id"]) + order.OrderID = result.Data["order_id"].(string) return order } -func (m *MTPSAPI) CreateOrderByShop(basicParams *MtpsCreateOrderByShopInfo, addParams map[string]interface{}) (order *MtpsOrderResponse, err error) { +func (a *API) CreateOrderByShop(basicParams *CreateOrderByShopParam, addParams map[string]interface{}) (order *OrderResponse, err error) { params := structs.Map(basicParams) params["goods_value"] = strconv.FormatFloat(basicParams.GoodsValue, 'f', 2, 64) params["goods_weight"] = strconv.FormatFloat(basicParams.GoodsWeight, 'f', 2, 64) @@ -225,67 +214,66 @@ func (m *MTPSAPI) CreateOrderByShop(basicParams *MtpsCreateOrderByShopInfo, addP if params["order_type"] != utils.Int2Str(OrderTypeBook) { delete(params, "expected_delivery_time") } - if result, err := m.AccessMTPS("order/createByShop", allParams); err != nil { - baseapi.SugarLogger.Debugf("result:%v", result) - return nil, utils.NewErrorIntCode(err.Error(), result.Code) + if result, err := a.AccessAPI("order/createByShop", allParams); err != nil { + return nil, err } else { - return m.result2OrderResponse(result), nil + return a.result2OrderResponse(result), nil } } -func (m *MTPSAPI) QueryOrderStatus(deliveryId int64, mtPeiSongId string) (retVal map[string]interface{}, err error) { +func (a *API) QueryOrderStatus(deliveryId int64, mtPeiSongId string) (retVal map[string]interface{}, err error) { params := map[string]interface{}{ "delivery_id": deliveryId, "mt_peisong_id": mtPeiSongId, } - if result, err := m.AccessMTPS("order/status/query", params); err != nil { + if result, err := a.AccessAPI("order/status/query", params); err != nil { return nil, err } else { return result.Data, nil } } -func (m *MTPSAPI) CancelOrder(deliveryId int64, mtPeiSongId string, cancelReasonId int, cancelReason string) (result *MtpsOrderResponse, err error) { +func (a *API) CancelOrder(deliveryId int64, mtPeiSongId string, cancelReasonId int, cancelReason string) (result *OrderResponse, err error) { params := map[string]interface{}{ "delivery_id": deliveryId, "mt_peisong_id": mtPeiSongId, "cancel_reason_id": cancelReasonId, "cancel_reason": cancelReason, } - if result, err := m.AccessMTPS("order/delete", params); err != nil { + if result, err := a.AccessAPI("order/delete", params); err != nil { baseapi.SugarLogger.Debugf("result:%v", result) return nil, err } else { - return m.result2OrderResponse(result), nil + return a.result2OrderResponse(result), nil } } -func (m *MTPSAPI) simulateOrderBehavior(action string, deliveryId int64, mtPeiSongId string) (err error) { +func (a *API) simulateOrderBehavior(action string, deliveryId int64, mtPeiSongId string) (err error) { params := map[string]interface{}{ "delivery_id": deliveryId, "mt_peisong_id": mtPeiSongId, } - _, err = m.AccessMTPS("test/order/"+action, params) + _, err = a.AccessAPI("test/order/"+action, params) return err } -func (m *MTPSAPI) SimulateArrange(deliveryId int64, mtPeiSongId string) (err error) { - return m.simulateOrderBehavior("arrange", deliveryId, mtPeiSongId) +func (a *API) SimulateArrange(deliveryId int64, mtPeiSongId string) (err error) { + return a.simulateOrderBehavior("arrange", deliveryId, mtPeiSongId) } -func (m *MTPSAPI) SimulatePickup(deliveryId int64, mtPeiSongId string) (err error) { - return m.simulateOrderBehavior("pickup", deliveryId, mtPeiSongId) +func (a *API) SimulatePickup(deliveryId int64, mtPeiSongId string) (err error) { + return a.simulateOrderBehavior("pickup", deliveryId, mtPeiSongId) } -func (m *MTPSAPI) SimulateDeliver(deliveryId int64, mtPeiSongId string) (err error) { - return m.simulateOrderBehavior("deliver", deliveryId, mtPeiSongId) +func (a *API) SimulateDeliver(deliveryId int64, mtPeiSongId string) (err error) { + return a.simulateOrderBehavior("deliver", deliveryId, mtPeiSongId) } -func (m *MTPSAPI) SimulateRearrange(deliveryId int64, mtPeiSongId string) (err error) { - return m.simulateOrderBehavior("rearrange", deliveryId, mtPeiSongId) +func (a *API) SimulateRearrange(deliveryId int64, mtPeiSongId string) (err error) { + return a.simulateOrderBehavior("rearrange", deliveryId, mtPeiSongId) } -func (m *MTPSAPI) SimulateReportException(deliveryId int64, mtPeiSongId string) (err error) { - return m.simulateOrderBehavior("reportException", deliveryId, mtPeiSongId) +func (a *API) SimulateReportException(deliveryId int64, mtPeiSongId string) (err error) { + return a.simulateOrderBehavior("reportException", deliveryId, mtPeiSongId) } diff --git a/platform/mtpsapi/mtpsapi_test.go b/platformapi/mtpsapi/mtpsapi_test.go similarity index 78% rename from platform/mtpsapi/mtpsapi_test.go rename to platformapi/mtpsapi/mtpsapi_test.go index 6189f1cd..e006aa46 100644 --- a/platform/mtpsapi/mtpsapi_test.go +++ b/platformapi/mtpsapi/mtpsapi_test.go @@ -10,7 +10,7 @@ import ( ) var ( - mtpsapi *MTPSAPI + mtpsapi *API sugarLogger *zap.SugaredLogger ) @@ -18,8 +18,8 @@ func init() { logger, _ := zap.NewDevelopment() sugarLogger = logger.Sugar() baseapi.Init(sugarLogger) - mtpsapi = NewMTPSAPI("25e816550bc9484480642f19a95f13fd", "r4$HqrKx9~=7?2Jfo,$Z~a7%~k!Au&pEdI2)oPJvSbH2ao@2N0[8wSIvtuumh_J^") - // mtpsapi = NewMTPSAPI("3c0a05d464c247c19d7ec13accc78605", "b1M}9?:sTbsB[OF2gNORnN(|(iy9rB8(`7]|[wGLnbmt`evfM>E:A90DjHAW:UPE") + mtpsapi = New("25e816550bc9484480642f19a95f13fd", "r4$HqrKx9~=7?2Jfo,$Z~a7%~k!Au&pEdI2)oPJvSbH2ao@2N0[8wSIvtuumh_J^") + // mtpsapi = New("3c0a05d464c247c19d7ec13accc78605", "b1M}9?:sTbsB[OF2gNORnN(|(iy9rB8(`7]|[wGLnbmt`evfM>E:A90DjHAW:UPE") } func handleError(t *testing.T, err error) { @@ -32,15 +32,15 @@ func TestTest(t *testing.T) { sugarLogger.Debug(utils.GetCurTimeStr()) } -func TestAccessMTPS(t *testing.T) { +func TestAccessAPI(t *testing.T) { mtPeiSongId := "1529387562097059" params := map[string]interface{}{ "delivery_id": 123456789, "mt_peisong_id": mtPeiSongId, } - result, err := mtpsapi.AccessMTPS("order/status/query", params) + result, err := mtpsapi.AccessAPI("order/status/query", params) if err != nil { - t.Fatalf("Error when accessing AccessMTPS result:%v, error:%v", result, err) + t.Fatalf("Error when accessing AccessAPI result:%v, error:%v", result, err) } else { getMtPsId := result.Data["mt_peisong_id"].(string) if getMtPsId != mtPeiSongId { @@ -50,12 +50,12 @@ func TestAccessMTPS(t *testing.T) { } func TestCreateOrderByShop(t *testing.T) { - basicParams := &MtpsCreateOrderByShopInfo{ - DeliveryId: 123456789, - OrderId: "order_123456789", + basicParams := &CreateOrderByShopParam{ + DeliveryID: 123456789, + OrderID: "order_123456789", // 设置测试门店 id,测试门店的坐标地址为 97235456,31065079(高德坐标),配送范围3km - ShopId: "test_0001", - DeliveryServiceCode: DSCIntime, + ShopID: "test_0001", + DeliveryServiceCode: DeliveryServiceCodeIntime, ReceiverName: "xjh", ReceiverAddress: "九里堤", ReceiverPhone: "18112345678", diff --git a/platformapi/platformapi.go b/platformapi/platformapi.go new file mode 100644 index 00000000..2a2d9967 --- /dev/null +++ b/platformapi/platformapi.go @@ -0,0 +1,119 @@ +package platformapi + +import ( + "errors" + "net" + "net/http" + "time" + + "github.com/fatih/structs" + + "git.rosy.net.cn/baseapi" +) + +const ( + DefClientTimeout = 10 * time.Second + DefSleepSecondWhenExceedLimit = 6 * time.Second + DefMaxRecoverableRetryCount = 3 + DefMaxExceedLimitRetryCount = 10 +) + +type APIRetryConfig struct { + MaxExceedLimitRetryCount int + MaxRecoverableRetryCount int + SleepSecondWhenExceedLimit time.Duration +} + +type APIConfig struct { + APIRetryConfig + ClientTimeout time.Duration +} + +type AccessPlatformAPIWithRetryParam struct { + APIRetryConfig + Client *http.Client + Request *http.Request +} + +var ( + DefAPIConfig = APIConfig{ + APIRetryConfig: APIRetryConfig{ + MaxExceedLimitRetryCount: DefMaxExceedLimitRetryCount, + MaxRecoverableRetryCount: DefMaxRecoverableRetryCount, + SleepSecondWhenExceedLimit: DefSleepSecondWhenExceedLimit, + }, + ClientTimeout: DefClientTimeout, + } +) + +const ( + ErrLevelSuccess = "JXC4_SUCCESS" + ErrLevelExceedLimit = "JXC4_EXCEED_LIMIT" + ErrLevelRecoverableErr = "JXC4_RECOVERABLE" + ErrLevelGeneralFail = "JXC4_GENERAL_FAIL" + ErrLevelCodeIsNotOK = "JXC4_CODE_IS_NOT_OK" +) + +// common api access error +var ( + ErrAPIAccessFailed = errors.New("access API failed") + ErrHTTPCodeIsNot200 = errors.New("HTTP code is not 200") + ErrRecoverableErrReachMaxRetry = errors.New("recoverable error reach max retry count") + ErrLimitExceedReachMaxRetry = errors.New("limit exceed reach max retry count") + ErrResponseDataFormatWrong = errors.New("the data of response has wrong format") +) + +// common callback response +var ( + ErrStrUnescapeError = "can not unescape data:%v, error:%v" + ErrStrUnmarshalError = "can not unmarshal data:%v, error:%v" + ErrStrCallbackSignatureIsWrong = "wrong signature" +) + +func init() { + structs.DefaultTagName = "json" +} + +func AccessPlatformAPIWithRetry(client *http.Client, request *http.Request, config *APIConfig, handleResponse func(response *http.Response) (string, error)) error { + exceedLimitRetryCount := 0 + recoverableErrorRetryCount := 0 + for { + response, err := client.Do(request) + if err != nil { + baseapi.SugarLogger.Debugf("client.Get return err:%v", err) + err, ok := err.(net.Error) + recoverableErrorRetryCount++ + if ok && err.Timeout() && recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { + continue + } else { + baseapi.SugarLogger.Errorf("access api error:%v", err) + return ErrAPIAccessFailed + } + } + defer response.Body.Close() + if response.StatusCode != 200 { + baseapi.SugarLogger.Errorf("HTTP code not 200, it's:%v", response.StatusCode) + return ErrHTTPCodeIsNot200 + } + + errLevel, err := handleResponse(response) + if err == nil { + return nil + } else if errLevel == ErrLevelExceedLimit { + exceedLimitRetryCount++ + if exceedLimitRetryCount <= config.MaxExceedLimitRetryCount { + time.Sleep(config.SleepSecondWhenExceedLimit) + continue + } else { + return ErrLimitExceedReachMaxRetry + } + } else if errLevel == ErrLevelRecoverableErr { + recoverableErrorRetryCount++ + if recoverableErrorRetryCount <= config.MaxRecoverableRetryCount { + continue + } + return err + } + return err + } +} diff --git a/utils/errorwithcode.go b/utils/errorwithcode.go index 3251908d..7019e5f7 100644 --- a/utils/errorwithcode.go +++ b/utils/errorwithcode.go @@ -2,31 +2,43 @@ package utils import ( "fmt" + "strconv" ) type ErrorWithCode struct { + level int str string code string intCode int } -func NewErrorCode(err, code string) *ErrorWithCode { - return &ErrorWithCode{ - str: err, - code: code, - intCode: Str2Int(code), +func NewErrorCode(err, code string, level ...int) *ErrorWithCode { + retVal := &ErrorWithCode{ + str: err, + code: code, } + retVal.intCode, _ = strconv.Atoi(code) + if len(level) > 0 { + retVal.level = level[0] + } + + return retVal } -func NewErrorIntCode(err string, code int) *ErrorWithCode { - return &ErrorWithCode{ - str: err, - code: Int2Str(code), - intCode: code, - } +func NewErrorIntCode(err string, code int, level ...int) *ErrorWithCode { + return NewErrorCode(err, Int2Str(code), level...) } + func (e *ErrorWithCode) Error() string { - return fmt.Sprintf("%s, code:%s", e.str, e.code) + return fmt.Sprintf("level:%d, str:%s, code:%s", e.level, e.str, e.code) +} + +func (e *ErrorWithCode) String() string { + return e.Error() +} + +func (e *ErrorWithCode) Level() int { + return e.level } func (e *ErrorWithCode) Code() string { diff --git a/utils/utils.go b/utils/utils.go index af443a76..8d671fb7 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -92,7 +92,11 @@ func DictKeysMan(data interface{}, keysToRemove []string, keysToKeep []string) i func UnmarshalUseNumber(data []byte, result interface{}) error { d := json.NewDecoder(bytes.NewReader(data)) d.UseNumber() - return d.Decode(result) + err := d.Decode(result) + if err != nil { + baseapi.SugarLogger.Errorf("decode data:%v, error:%v", string(data), err) + } + return err } func MustMarshal(obj interface{}) []byte { @@ -112,12 +116,18 @@ func Bool2String(value bool) string { } func Str2Int(str string) int { - retVal, _ := strconv.Atoi(str) + retVal, err := strconv.Atoi(str) + if err != nil { + baseapi.SugarLogger.Errorf("error when convert %s to int", str) + } return retVal } func Str2Int64(str string) int64 { - retVal, _ := strconv.ParseInt(str, 10, 64) + retVal, err := strconv.ParseInt(str, 10, 64) + if err != nil { + baseapi.SugarLogger.Errorf("error when convert %s to int64", str) + } return retVal } @@ -150,15 +160,15 @@ func Timestamp2Str(timestamp int64) string { return time.Unix(timestamp/1000, 0).Format("2006-01-02 15:04:05") } -func HttpResponse2Json(response *http.Response) (map[string]interface{}, error) { +func HTTPResponse2Json(response *http.Response) (map[string]interface{}, error) { var jsonResult map[string]interface{} bodyData, err := ioutil.ReadAll(response.Body) if err != nil { + baseapi.SugarLogger.Errorf("ioutil.ReadAll error:%v", err) return nil, err } - err = UnmarshalUseNumber(bodyData, &jsonResult) - if err != nil { + if err = UnmarshalUseNumber(bodyData, &jsonResult); err != nil { return nil, err } return jsonResult, nil diff --git a/utils/utils_test.go b/utils/utils_test.go index f48ba419..481e17c4 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -73,4 +73,7 @@ func TestJson(t *testing.T) { unmarshStr(t, jsonStr, &obj2) unmarshStr(t, jsonStr, &obj3) unmarshStr(t, jsonStr, &obj4) + t.Skip("skip the flollowing") + t.Fatal(1) + t.Fail() }