- basic mtps api added.

This commit is contained in:
gazebo
2018-06-14 14:45:19 +08:00
parent 9696e142ee
commit 4f1a2bc950
7 changed files with 355 additions and 137 deletions

83
platform/common/common.go Normal file
View File

@@ -0,0 +1,83 @@
package common
import (
"errors"
"net"
"net/http"
"time"
"go.uber.org/zap"
)
type AccessPlatformAPIWithRetryParams struct {
MaxExceedLimitRetryCount int
MaxRecoverableRetryCount int
SleepSecondWhenExceedLimit time.Duration
Client *http.Client
Request *http.Request
SugarLogger *zap.SugaredLogger
}
const (
PAErrorLevelSuccess = 0
PAErrorLevelExceedLimit = 1
PAErrorLevelRecoverable = 2
PAErrorLevelFailed = 3
)
var (
ErrRecoverableErrMaxRetry = errors.New("recoverable error reach max retry count!")
ErrLimitReachMaxRetry = errors.New("Reach max retry count!")
ErrHttpCode = errors.New("HTTP Code is not 200")
ErrBusinessCode = errors.New("Business code is not ok")
)
func AccessPlatformAPIWithRetry(params *AccessPlatformAPIWithRetryParams, handleResponse func(response *http.Response) (int, error)) error {
exceedLimitRetryCount := 0
recoverableErrorRetryCount := 0
for {
response, err := params.Client.Do(params.Request)
if err != nil {
params.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 {
params.SugarLogger.Debugf("http code is:%d", response.StatusCode)
recoverableErrorRetryCount++
if recoverableErrorRetryCount <= params.MaxRecoverableRetryCount {
continue
}
return ErrHttpCode
}
errLevel, err := handleResponse(response)
if err != nil {
return err
}
if errLevel == PAErrorLevelSuccess {
return nil
} else if errLevel == PAErrorLevelExceedLimit {
exceedLimitRetryCount++
if exceedLimitRetryCount <= params.MaxExceedLimitRetryCount {
time.Sleep(params.SleepSecondWhenExceedLimit)
} else {
return ErrLimitReachMaxRetry
}
} else if errLevel == PAErrorLevelRecoverable {
if recoverableErrorRetryCount <= params.MaxRecoverableRetryCount {
continue
}
return ErrRecoverableErrMaxRetry
} else {
return ErrBusinessCode
}
}
}

View File

@@ -2,36 +2,29 @@ package elmapi
import (
"crypto/md5"
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"sort"
"strings"
"time"
"git.rosy.net.cn/baseapi/platform/common"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
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/"
)
const (
clientTimeout = time.Second * 10
sleepSecondWhenLimited = 6 * time.Second
maxRetryCountWhenNetworkException = 3
maxRetryCountWhenReachLimited = 10
)
var (
ErrSystemErrMaxRetry = errors.New("ELM System error reach max retry count!")
ErrLimitReachMaxRetry = errors.New("ELM Reach max retry count!")
ErrHttpCode = errors.New("ELM HTTP Code is not 200")
ErrELMCode = errors.New("ELM code is not 0")
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/"
)
type ELMResult struct {
@@ -46,7 +39,7 @@ type ELMAPI struct {
secret string
sugarLogger *zap.SugaredLogger
url *url.URL
client http.Client
client *http.Client
}
type ELMPayload struct {
@@ -65,7 +58,7 @@ func NewELMAPI(token, appKey, secret string, sugarLogger *zap.SugaredLogger, isP
appKey: appKey,
secret: secret,
sugarLogger: sugarLogger,
client: http.Client{Timeout: time.Second * 10},
client: &http.Client{Timeout: clientTimeout},
}
if isProd {
@@ -95,7 +88,7 @@ func (e *ELMAPI) signParams(action string, payload *ELMPayload) string {
return fmt.Sprintf("%X", md5.Sum([]byte(finalStr)))
}
func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (*ELMResult, error) {
func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (retVal *ELMResult, err error) {
if params == nil {
params = make(map[string]interface{}, 0)
}
@@ -114,14 +107,13 @@ func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (*ELMRe
}
payload.Signature = e.signParams(action, payload)
dataBytes := utils.MustMarshal(payload)
dataStr := string(dataBytes)
exceedLimitRetryCount := 0
systemErrorRetryCount := 0
for {
request := &http.Request{
apiAccess := &common.AccessPlatformAPIWithRetryParams{
MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited,
MaxRecoverableRetryCount: maxRetryCountWhenNetworkException,
SleepSecondWhenExceedLimit: sleepSecondWhenLimited,
Client: e.client,
Request: &http.Request{
Method: "POST",
URL: e.url,
Header: http.Header{
@@ -131,64 +123,37 @@ func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (*ELMRe
// "x-eleme-requestid": []string{payload.Id},
},
Body: ioutil.NopCloser(strings.NewReader(dataStr)),
}
response, err := e.client.Do(request)
if err != nil {
e.sugarLogger.Debugf("client.Get return err:%v", err)
err, ok := err.(net.Error)
systemErrorRetryCount++
if ok && err.Timeout() && systemErrorRetryCount <= maxRetryCountWhenNetworkException {
continue
} else {
return nil, err
}
}
defer response.Body.Close()
if response.StatusCode != 200 {
e.sugarLogger.Debugf("http code is:%d", response.StatusCode)
systemErrorRetryCount++
if systemErrorRetryCount <= maxRetryCountWhenNetworkException {
continue
}
return nil, ErrHttpCode
}
Body: ioutil.NopCloser(strings.NewReader(string(utils.MustMarshal(payload)))),
},
SugarLogger: e.sugarLogger,
}
err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (result int, err error) {
jsonResult1, err := utils.HttpResponse2Json(response)
if err != nil {
e.sugarLogger.Warnf("HttpResponse2Json return:%v", err)
return 0, err
}
resultError, _ := jsonResult1["error"].(map[string]interface{})
jsonResult := &ELMResult{
retVal = &ELMResult{
Id: jsonResult1["id"].(string),
Error: resultError,
Result: jsonResult1["result"],
}
if err != nil {
e.sugarLogger.Warnf("HttpResponse2Json return:%v", err)
return nil, err
}
errinfoMap := jsonResult.Error
errinfoMap := retVal.Error
if errinfoMap == nil {
return jsonResult, nil
return common.PAErrorLevelSuccess, nil
}
errCode := errinfoMap["code"].(string)
if errCode == "EXCEED_LIMIT" {
exceedLimitRetryCount++
if exceedLimitRetryCount <= maxRetryCountWhenReachLimited {
time.Sleep(sleepSecondWhenLimited)
} else {
return jsonResult, ErrLimitReachMaxRetry
}
return common.PAErrorLevelExceedLimit, nil
} else if errCode == "SERVER_ERROR" || errCode == "BIZ_SYSTEM_ERROR" || errCode == "BIZ_1006" || errCode == "BUSINESS_ERROR" {
if systemErrorRetryCount <= maxRetryCountWhenNetworkException {
continue
}
return jsonResult, ErrSystemErrMaxRetry
return common.PAErrorLevelRecoverable, nil
} else {
e.sugarLogger.Debug(jsonResult)
return jsonResult, ErrELMCode
return common.PAErrorLevelFailed, nil
}
}
})
return retVal, err
}

View File

@@ -1,7 +1,6 @@
package elmapi
import (
"encoding/json"
"testing"
"git.rosy.net.cn/baseapi/utils"
@@ -29,10 +28,9 @@ func TestAccessELM(t *testing.T) {
t.Fatalf("Error when accessing AccessJDQuery result:%v, error:%v", result, err)
} else {
mapResult := result.Result.(map[string]interface{})
userIdNumber := mapResult["userId"].(json.Number)
userId, err := userIdNumber.Int64()
userId := utils.MustInterface2Int64(mapResult["userId"])
if userId != 336072266326420104 || err != nil {
t.Fatalf("userId is not correct:%v", userIdNumber)
t.Fatalf("userId is not correct:%v", mapResult["userId"])
}
}
}

View File

@@ -1,14 +1,12 @@
package elmapi
type ELMOrderInfo map[string]interface{}
func (e *ELMAPI) GetOrder(orderId string) (ELMOrderInfo, error) {
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 := ELMOrderInfo(result.Result.(map[string]interface{}))
innerResult := map[string]interface{}(result.Result.(map[string]interface{}))
return innerResult, nil
}
return nil, err

View File

@@ -5,17 +5,25 @@ import (
"encoding/json"
"errors"
"fmt"
"net"
"net/http"
"net/url"
"sort"
"strconv"
"time"
"git.rosy.net.cn/baseapi/platform/common"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
const (
clientTimeout = time.Second * 10
sleepSecondWhenLimited = 6 * time.Second
maxRetryCountWhenNetworkException = 3
maxRetryCountWhenReachLimited = 10
)
const (
// JDErrorCodeSuccess 操作成功
JDErrorCodeSuccess = "0"
@@ -37,30 +45,23 @@ const (
JDErrorCodeLoadUnexpected = "100024"
)
const (
jdAPIURL = "https://openo2o.jd.com/djapi/%s"
AllPage = 0
DefaultPageSize = 50
)
type JDAPI struct {
token string
appKey string
appSecret string
logger *zap.Logger
sugarLogger *zap.SugaredLogger
client http.Client
client *http.Client
}
const (
sleepSecondWhenLimited = 6 * time.Second
maxRetryCountWhenNetworkException = 3
maxRetryCountWhenReachLimited = 10
jdAPIURL = "https://openo2o.jd.com/djapi/%s"
AllPage = 0
DefaultPageSize = 50
)
var (
ErrSystemErrMaxRetry = errors.New("JD System error reach max retry count!")
ErrLimitReachMaxRetry = errors.New("JD Reach max retry count!")
ErrHttpCode = errors.New("JD HTTP Code is not 200")
ErrJDCode = errors.New("JD code is not 0")
ErrInnerCodeIsNotOk = errors.New("JD result inner code is not ok")
ErrInnerCodeIsNotOk = errors.New("JD result inner code is not ok")
exceedLimitCodes = map[string]int{
JDErrorCodeExceedLimit: 1,
@@ -82,6 +83,7 @@ var (
"None": 1,
"0": 1,
"1": 1,
"200": 1,
"190005": 1, // 部分失败
}
@@ -128,10 +130,10 @@ func genGetURL(baseURL, apiStr string, params map[string]string) string {
}
func NewJDAPI(token, appKey, appSecret string, logger *zap.Logger) *JDAPI {
return &JDAPI{token, appKey, appSecret, logger, logger.Sugar(), http.Client{Timeout: time.Second * 10}}
return &JDAPI{token, appKey, appSecret, logger, logger.Sugar(), &http.Client{Timeout: clientTimeout}}
}
func (j *JDAPI) AccessJDQuery(apiStr string, jdParams map[string]string) (map[string]interface{}, error) {
func (j *JDAPI) AccessJDQuery(apiStr string, jdParams map[string]string) (retVal map[string]interface{}, err error) {
params := make(map[string]string)
params["v"] = "1.0"
params["format"] = "json"
@@ -152,62 +154,45 @@ func (j *JDAPI) AccessJDQuery(apiStr string, jdParams map[string]string) (map[st
sign := signParams(params)
params["sign"] = sign
exceedLimitRetryCount := 0
systemErrorRetryCount := 0
for {
fullURL := genGetURL(jdAPIURL, apiStr, params)
j.sugarLogger.Debugf("fullURL:%v", fullURL)
response, err := j.client.Get(fullURL)
if err != nil {
j.sugarLogger.Debugf("client.Get return err:%v", err)
err, ok := err.(net.Error)
systemErrorRetryCount++
if ok && err.Timeout() && systemErrorRetryCount <= maxRetryCountWhenNetworkException {
continue
} else {
return nil, err
}
}
defer response.Body.Close()
url, _ := url.Parse(genGetURL(jdAPIURL, apiStr, params))
if response.StatusCode != 200 {
j.sugarLogger.Debugf("http code is:%d", response.StatusCode)
systemErrorRetryCount++
if systemErrorRetryCount <= maxRetryCountWhenNetworkException {
continue
}
apiAccess := &common.AccessPlatformAPIWithRetryParams{
MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited,
MaxRecoverableRetryCount: maxRetryCountWhenNetworkException,
SleepSecondWhenExceedLimit: sleepSecondWhenLimited,
Client: j.client,
Request: &http.Request{
Method: "GET",
URL: url,
},
SugarLogger: j.sugarLogger,
}
return nil, ErrHttpCode
}
jsonResult, err := utils.HttpResponse2Json(response)
err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (errLevel int, err error) {
jsonResult1, err := utils.HttpResponse2Json(response)
if err != nil {
j.sugarLogger.Warnf("HttpResponse2Json return:%v", err)
return nil, err
return 0, err
}
code := jsonResult["code"].(string)
code := jsonResult1["code"].(string)
if code == "0" {
return jsonResult, nil
retVal = jsonResult1
return common.PAErrorLevelSuccess, nil
}
j.sugarLogger.Debugf("jd code is:%s", code)
if _, ok := exceedLimitCodes[code]; ok {
exceedLimitRetryCount++
if exceedLimitRetryCount <= maxRetryCountWhenReachLimited {
time.Sleep(sleepSecondWhenLimited)
} else {
return jsonResult, ErrLimitReachMaxRetry
}
return common.PAErrorLevelExceedLimit, nil
} else if _, ok := canRetryCodes[code]; ok {
systemErrorRetryCount++
if systemErrorRetryCount > maxRetryCountWhenNetworkException {
return jsonResult, ErrSystemErrMaxRetry
}
return common.PAErrorLevelRecoverable, nil
} else {
return jsonResult, ErrJDCode
return common.PAErrorLevelFailed, nil
}
}
})
return retVal, err
}
func (j *JDAPI) AccessJDQueryNoPage(apiStr string, jdParams map[string]string, keyToRemove, keyToKeep []string) (interface{}, error) {
@@ -226,7 +211,7 @@ func (j *JDAPI) AccessJDQueryNoPage(apiStr string, jdParams map[string]string, k
innerCode := ""
for _, innerCodeKey := range jdResultInnerCodeKeys {
if innerCode2, ok := data[innerCodeKey]; ok {
innerCode = innerCode2.(string)
innerCode = forceInnerCode2Str(innerCode2)
break
}
}
@@ -333,7 +318,7 @@ func (j *JDAPI) AccessJDQueryHavePage(apiStr string, jdParams map[string]string,
return nil, err
}
innerCode := data["code"].(string)
innerCode := forceInnerCode2Str(data["code"])
if innerCode != "0" && innerCode != "200" {
return nil, ErrInnerCodeIsNotOk
}
@@ -352,3 +337,10 @@ func (j *JDAPI) AccessJDQueryHavePage(apiStr string, jdParams map[string]string,
return retVal, nil
}
func forceInnerCode2Str(innerCode interface{}) string {
if innerCodeStr, ok := innerCode.(string); ok {
return innerCodeStr
}
return string(utils.MustInterface2Int64(innerCode))
}

142
platform/mtpsapi/mtpsapi.go Normal file
View File

@@ -0,0 +1,142 @@
package mtpsapi
import (
"crypto/sha1"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
"git.rosy.net.cn/baseapi/platform/common"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
const (
clientTimeout = time.Second * 10
sleepSecondWhenLimited = 6 * time.Second
maxRetryCountWhenNetworkException = 3
maxRetryCountWhenReachLimited = 10
)
const (
mtpsAPIURL = "https://peisongopen.meituan.com/api"
)
const (
mtpsStatusSuccess = 0
mtpsStatusSystemError = 1
mtpsStatusMissingSystemParams = 2
mtpsStatusMissingBusinessParams = 3
)
type MTPSResult struct {
Code int `json:"code"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
type MTPSAPI struct {
appKey string
secret string
sugarLogger *zap.SugaredLogger
client *http.Client
}
// type MTPSOderStatus struct {
// DeliveryId int64 `json:"delivery_id"`
// MtPeiSongId string `json:"mt_peisong_id"`
// OrderId string `json:"order_id"`
// Status int `json:"status"`
// OperateTime int `json:"operate_time"`
// CourierName string `json:"courier_name"`
// CourierPhone string `json:"courier_phone"`
// CancelReasonId int `json:"cancel_reason_id"`
// CancelReason string `json:"cancel_reason"`
// }
func NewMTPSAPI(appKey, secret string, sugarLogger *zap.SugaredLogger) *MTPSAPI {
api := &MTPSAPI{
appKey: appKey,
secret: secret,
sugarLogger: sugarLogger,
client: &http.Client{Timeout: clientTimeout},
}
return api
}
func (m *MTPSAPI) signParams(params url.Values) string {
keyValues := make([]string, 0)
for k, v := range params {
valStr := strings.Join(v, "")
if valStr != "" {
keyValues = append(keyValues, k+valStr)
}
}
sort.Strings(keyValues)
finalStr := m.secret + strings.Join(keyValues, "")
// e.sugarLogger.Debugf("sign str:%v", finalStr)
return fmt.Sprintf("%x", sha1.Sum([]byte(finalStr)))
}
func (m *MTPSAPI) AccessMTPS(action string, params map[string]interface{}) (retVal *MTPSResult, err error) {
if params == nil {
panic("params is nil!")
}
params["appkey"] = m.appKey
params["timestamp"] = utils.GetCurTimestamp()
params["version"] = "1.0"
params2 := make(url.Values)
for k, v := range params {
params2[k] = []string{fmt.Sprint(v)}
}
params2["sign"] = []string{m.signParams(params2)}
request, _ := http.NewRequest("POST", mtpsAPIURL+"/"+action, strings.NewReader(string(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,
SugarLogger: m.sugarLogger,
}
err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (result int, err error) {
jsonResult1, err := utils.HttpResponse2Json(response)
if err != nil {
m.sugarLogger.Warnf("HttpResponse2Json return:%v", err)
return 0, err
}
code := int(utils.MustInterface2Int64(jsonResult1["code"]))
retVal = &MTPSResult{
Code: code,
}
if code == mtpsStatusSuccess {
retVal.Data = jsonResult1["data"].(map[string]interface{})
return common.PAErrorLevelSuccess, nil
}
retVal.Message = jsonResult1["message"].(string)
return common.PAErrorLevelFailed, nil
})
return retVal, err
}
func (m *MTPSAPI) 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 {
return nil, err
} else {
return result.Data, nil
}
}

View File

@@ -0,0 +1,40 @@
package mtpsapi
import (
"testing"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
var (
mtpsapi *MTPSAPI
sugarLogger *zap.SugaredLogger
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
mtpsapi = NewMTPSAPI("3c0a05d464c247c19d7ec13accc78605", "b1M}9?:sTbsB[OF2gNORnN(|(iy9rB8(`7]|[wGLnbmt`evfM>E:A90DjHAW:UPE", sugarLogger)
}
func TestTest(t *testing.T) {
sugarLogger.Debug(utils.GetCurTimeStr())
}
func TestAccessMTPS(t *testing.T) {
mtPeiSongId := "1528955769086020"
params := map[string]interface{}{
"delivery_id": 378194,
"mt_peisong_id": mtPeiSongId,
}
result, err := mtpsapi.AccessMTPS("order/status/query", params)
if err != nil {
t.Fatalf("Error when accessing AccessMTPS result:%v, error:%v", result, err)
} else {
getMtPsId := result.Data["mt_peisong_id"].(string)
if getMtPsId != mtPeiSongId {
t.Fatalf("mt_peisong_id is not same, %v vs %v", mtPeiSongId, getMtPsId)
}
}
}