- big big refactor.

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

View File

@@ -0,0 +1,81 @@
package dadaapi
import (
"crypto/md5"
"fmt"
"sort"
"strings"
"github.com/fatih/structs"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
ResponseHttpCodeSuccess = 200
ResponseHttpCodeGeneralErr = 555
)
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"`
DmName string `json:"dm_name"`
DmMobile string `json:"dm_mobile"`
}
type CallbackResponse struct {
Code int
Dummy string `json:"dummy"`
}
var (
SuccessResponse = &CallbackResponse{Code: ResponseHttpCodeSuccess}
FailedResponse = &CallbackResponse{Code: ResponseHttpCodeGeneralErr}
)
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]))
}
sort.Strings(values)
finalStr := strings.Join(values, "")
// baseapi.SugarLogger.Debugf("sign str:%v", finalStr)
return fmt.Sprintf("%x", md5.Sum([]byte(finalStr)))
}
func (a *API) unmarshalData(data []byte, msg interface{}) (callbackResponse *CallbackResponse) {
err := utils.UnmarshalUseNumber(data, msg)
if err != nil {
return FailedResponse
}
return nil
}
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
}
return 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(msg)
callbackResponse = a.CheckCallbackValidation(mapData)
return msg, callbackResponse
}

View File

@@ -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
}

View File

@@ -0,0 +1,139 @@
package dadaapi
import (
"testing"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
var (
dadaapi *API
sugarLogger *zap.SugaredLogger
testOrder *OperateOrderRequiredParams
)
const (
testShopNo = "11047059"
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
baseapi.Init(sugarLogger)
// sandbox
dadaapi = New("dada9623324449cd250", "30c2abbfe8a8780ad5aace46300c64b9", "73753", "http://callback.jxc4.com/dada/order", false)
// prod
testOrder = &OperateOrderRequiredParams{
ShopNo: testShopNo,
OriginID: "234242342",
CityCode: "028",
CargoPrice: 12.34,
IsPrepay: 1,
ReceiverName: "我是谁",
ReceiverAddress: "九里堤",
ReceiverLat: 30.74631,
ReceiverLng: 103.99112,
ReceiverPhone: "12812345678",
}
}
func TestTest(t *testing.T) {
sugarLogger.Debug(utils.GetCurTimeStr())
}
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.signCallbackParams(mapData)
if sign != mapData["signature"] {
t.Fatal("sign is not correct")
}
}
func TestAccessAPI(t *testing.T) {
body := make(map[string]interface{})
body["order_id"] = "fakeorderid"
result, err := dadaapi.AccessAPI("api/order/status/query", body)
failed := true
if err != nil {
if err2, ok := err.(*utils.ErrorWithCode); ok {
if err2.IntCode() != ResponseCodeSignErr {
failed = false
}
}
} else {
failed = false
}
if failed {
t.Fatalf("Error when accessing api result:%v, error:%v", result, err)
}
}
func TestGetCites(t *testing.T) {
result, err := dadaapi.GetCities()
if err != nil {
t.Fatal(err.Error())
}
failed := true
for _, city := range result {
if city.CityCode == "028" {
failed = false
}
}
if failed {
t.Fatal("failed")
}
}
func TestGetReasons(t *testing.T) {
result, err := dadaapi.GetCancelReasons()
if err != nil {
t.Fatal(err.Error())
}
// baseapi.SugarLogger.Debug(result)
failed := true
for _, reason := range result {
if reason.ID == 1 {
failed = false
}
}
if failed {
t.Fatal("failed")
}
}
func TestAddOrder(t *testing.T) {
result, err := dadaapi.AddOrder(testOrder, nil)
if err != nil {
t.Fatal(err.Error())
}
baseapi.SugarLogger.Debug(result)
}
func TestReaddOrder(t *testing.T) {
result, err := dadaapi.ReaddOrder(testOrder, nil)
if err != nil {
t.Fatal(err.Error())
}
baseapi.SugarLogger.Debug(result)
}
func TestCancelOrder(t *testing.T) {
result, err := dadaapi.CancelOrder("234242342", ReasonIDClientDontWantItAnymore, "")
if err != nil {
t.Fatal(err.Error())
}
baseapi.SugarLogger.Debug(result)
}

View File

@@ -0,0 +1,131 @@
package dadaapi
import (
"git.rosy.net.cn/baseapi/utils"
"github.com/fatih/structs"
)
const (
ReasonIDNobodyAccept = 1
ReasonIDNobodyPickup = 2
ReasonIDCourierIsPig = 3
ReasonIDClientCanceled = 4
ReasonIDOrderIsWrong = 5
ReasonIDCourierWantMeCancel = 34
ReasonIDCourierDontWantToPickup = 35
ReasonIDClientDontWantItAnymore = 36
ReasonIDCourierShirk = 37
ReasonIDOther = 10000
)
const (
OrderStatusWaitingForAccept = 1
OrderStatusPickingup = 2
OrderStatusDeliverying = 3
OrderStatusFinished = 4
OrderStatusCanceled = 5
OrderStatusExpired = 7
OrderStatusAssignment = 8
OrderStatusReturning = 9
OrderStatusReturningFinished = 10
OrderStatusAddOrderFailed = 1000
)
type OperateOrderRequiredParams struct {
ShopNo string `json:"shop_no"`
OriginID string `json:"origin_id"`
CityCode string `json:"city_code"`
CargoPrice float64 `json:"cargo_price"`
IsPrepay int `json:"is_prepay"`
ReceiverName string `json:"receiver_name"`
ReceiverAddress string `json:"receiver_address"`
ReceiverLat float64 `json:"receiver_lat"`
ReceiverLng float64 `json:"receiver_lng"`
ReceiverPhone string `json:"receiver_phone"`
ReceiverTel string `json:"receiver_tel"`
}
type CreateOrderResponse struct {
Distance float64
Fee float64
DeliverFee float64
CouponFee float64
Tips float64
InsuranceFee float64
}
type CancelOrderResponse struct {
DeductFee float64 `json:"deduct_fee"`
}
func (a *API) QueryOrderInfo(orderId string) (retVal map[string]interface{}, err error) {
params := make(map[string]interface{})
params["order_id"] = orderId
result, err := a.AccessAPI("api/order/status/query", params)
if err != nil {
return nil, err
}
return result.Result.(map[string]interface{}), nil
}
func map2CreateOrderResponse(mapData map[string]interface{}) *CreateOrderResponse {
retVal := new(CreateOrderResponse)
retVal.Distance = utils.MustInterface2Float64(mapData["distance"])
retVal.Fee = utils.MustInterface2Float64(mapData["fee"])
retVal.DeliverFee = utils.MustInterface2Float64(mapData["deliverFee"])
if value, ok := mapData["couponFee"]; ok {
retVal.CouponFee = utils.MustInterface2Float64(value)
}
if value, ok := mapData["tips"]; ok {
retVal.CouponFee = utils.MustInterface2Float64(value)
}
if value, ok := mapData["insuranceFee"]; ok {
retVal.CouponFee = utils.MustInterface2Float64(value)
}
return retVal
}
func (a *API) operateOrder(action string, orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) {
params := structs.Map(orderInfo)
params["callback"] = a.callbackURL
allParams := utils.MergeMaps(params, addParams)
result, err := a.AccessAPI(action, allParams)
if err != nil {
return nil, err
}
return map2CreateOrderResponse(result.Result.(map[string]interface{})), nil
}
func (a *API) AddOrder(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) {
return a.operateOrder("api/order/addOrder", 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 (a *API) QueryDeliverFee(orderInfo *OperateOrderRequiredParams, addParams map[string]interface{}) (retVal *CreateOrderResponse, err error) {
return a.operateOrder("api/order/queryDeliverFee", 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 (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 := a.AccessAPI("api/order/formalCancel", mapData)
if err != nil {
return nil, err
}
mapResult := result.Result.(map[string]interface{})
retVal = new(CancelOrderResponse)
retVal.DeductFee = utils.MustInterface2Float64(mapResult["deduct_fee"])
return retVal, nil
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1,64 @@
package elmapi
import (
"testing"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
var (
elmapi *API
sugarLogger *zap.SugaredLogger
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
baseapi.Init(sugarLogger)
// sandbox
// elmapi = New("b4f7e424475c3758c111dc60ceec3e2a", "RwT214gAsS", "56afff4b9ebd8a7eb532d18fa33f17be57f9b9db", false)
// prod
elmapi = New("bab2a27f99562f394b411dbb9a6214da", "KLRDcOZGrk", "1fc221f8265506531da36fb613d5f5ad673f2e9a", true)
}
func TestTest(t *testing.T) {
sugarLogger.Debug(utils.GetCurTimeStr())
}
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 {
mapResult := result.Result.(map[string]interface{})
userId := utils.MustInterface2Int64(mapResult["userId"])
if userId != 336072266326420104 || err != nil {
t.Fatalf("userId is not correct:%v", mapResult["userId"])
}
}
}
func TestGetOrder(t *testing.T) {
result, err := elmapi.GetOrder("3023582487561731159")
if err != nil {
t.Fatalf("Error when accessing AccessJDQuery result:%v, error:%v", result, err)
} else {
shopId := int(utils.MustInterface2Int64(result["shopId"]))
if shopId != 157451615 {
t.Fatalf("userId is not correct:%v", shopId)
}
}
}
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(response, msg)
}
}

View File

@@ -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
}

View File

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

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

@@ -0,0 +1,342 @@
package jdapi
import (
"crypto/md5"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
paramJson = "jd_param_json"
)
const (
// ResponseCodeSuccess 操作成功
ResponseCodeSuccess = "0"
// ResponseCodeAccessFailed 操作失败请重试如还不能正常提供服务可以提交工单https://opengd.jd.com详细说明一下开放平台跟进回复。
ResponseCodeAccessFailed = "-1"
// ResponseCodeFailedCanAutoRetry 操作失败系统会自动重试如还不能正常提供服务可以提交工单https://opengd.jd.com详细说明一下开放平台跟进回复。
ResponseCodeFailedCanAutoRetry = "-10000"
ResponseCodeFailedAccessDB = "10000"
// ResponseCodeMissingMandatoryParam 请求参数中缺失必填项信息请检查如不清楚请访问到家开放平台https://opendj.jd.comAPI分组有接口详细说明。
ResponseCodeMissingMandatoryParam = "10005"
ResponseCodeInvalidToken = "10013"
ResponseCodeInvalidSign = "10014"
ResponseCodeAbnormalParam = "10015"
ResponseCodeMethodNotExist = "10018"
ResponseCodeExceedLimit = "10032"
ResponseCodeTimeout = "100022"
ResponseCodeTomcateFailed = "100023"
ResponseCodeLoadUnexpected = "100024"
)
const (
prodURL = "https://openo2o.jd.com/djapi/%s"
signKey = "sign"
AllPage = 0
DefaultPageSize = 50
)
type API struct {
token string
appKey string
appSecret string
client *http.Client
config *platformapi.APIConfig
}
var (
InnerCodeIsNotOk = "JD result inner code is not ok"
exceedLimitCodes = map[string]int{
ResponseCodeExceedLimit: 1,
}
// todo replace all magic number
canRetryCodes = map[string]int{
ResponseCodeFailedCanAutoRetry: 1,
ResponseCodeAccessFailed: 1,
ResponseCodeFailedAccessDB: 1,
ResponseCodeTimeout: 1,
ResponseCodeTomcateFailed: 1,
ResponseCodeLoadUnexpected: 1,
}
innerCodeKeys = []string{"code", "retCode", "errorCode"}
innerCodeOKCodes = map[string]int{
"None": 1,
"0": 1,
"1": 1,
"200": 1,
"190005": 1, // 部分失败
}
noPageInnerDataKeys = []string{"result", "data"}
havePageInner2DataKeys = []string{"result", "resultList"}
havePageTotalCountKeys = []string{"totalCount", "count"}
)
type PageResultParser func(map[string]interface{}, int) ([]interface{}, int, error)
func (a *API) signParams(jdParams map[string]interface{}) string {
var keys []string
for k := range jdParams {
if k != "app_secret" && k != signKey {
keys = append(keys, k)
}
}
sort.Strings(keys)
secretStr := fmt.Sprint(jdParams["app_secret"])
allStr := secretStr
for _, k := range keys {
allStr += k + fmt.Sprint(jdParams[k])
}
allStr = allStr + secretStr
return fmt.Sprintf("%X", md5.Sum([]byte(allStr)))
}
func generateURL(baseURL, apiStr string, params map[string]interface{}) string {
fullURL := ""
if params != nil {
for k, v := range params {
if fullURL == "" {
fullURL = "?"
} else {
fullURL += "&"
}
fullURL += k + "=" + url.QueryEscape(fmt.Sprint(v))
}
}
return fmt.Sprintf(baseURL, apiStr) + fullURL
}
func New(token, appKey, appSecret string, config ...*platformapi.APIConfig) *API {
curConfig := platformapi.DefAPIConfig
if len(config) > 0 {
curConfig = *config[0]
}
return &API{
token: token,
appKey: appKey,
appSecret: appSecret,
client: &http.Client{Timeout: curConfig.ClientTimeout},
config: &curConfig,
}
}
func (a *API) AccessAPI(apiStr string, jdParams map[string]interface{}) (retVal map[string]interface{}, err error) {
params := make(map[string]interface{})
params["v"] = "1.0"
params["format"] = "json"
params["app_key"] = a.appKey
params["app_secret"] = a.appSecret
params["token"] = a.token
if jdParams == nil {
jdParams = make(map[string]interface{}, 0)
}
jdParamStr, err := json.Marshal(jdParams)
if err != nil {
baseapi.SugarLogger.Errorf("Error when marshal %v, error:%v", jdParams, err)
return nil, err
}
params["jd_param_json"] = string(jdParamStr)
params["timestamp"] = utils.GetCurTimeStr()
sign := a.signParams(params)
params[signKey] = sign
url, _ := url.Parse(generateURL(prodURL, apiStr, params))
request := &http.Request{
Method: "GET",
URL: url,
}
err = platformapi.AccessPlatformAPIWithRetry(a.client, request, a.config, func(response *http.Response) (errLevel string, err error) {
jsonResult1, err := utils.HTTPResponse2Json(response)
if err != nil {
return platformapi.ErrLevelGeneralFail, platformapi.ErrResponseDataFormatWrong
}
code := jsonResult1["code"].(string)
if code == ResponseCodeSuccess {
retVal = jsonResult1
return platformapi.ErrLevelSuccess, nil
}
baseapi.SugarLogger.Warnf("response business code is not ok, data:%v, code:%v", jsonResult1, code)
newErr := utils.NewErrorCode(jsonResult1["msg"].(string), code)
if _, ok := exceedLimitCodes[code]; ok {
return platformapi.ErrLevelExceedLimit, newErr
} else if _, ok := canRetryCodes[code]; ok {
return platformapi.ErrLevelRecoverableErr, newErr
} else {
return platformapi.ErrLevelCodeIsNotOK, newErr
}
})
return retVal, err
}
func (a *API) AccessAPINoPage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string) (interface{}, error) {
jsonResult, err := a.AccessAPI(apiStr, jdParams)
if err != nil {
return nil, err
}
var data map[string]interface{}
if err := utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data); err != nil {
return nil, platformapi.ErrResponseDataFormatWrong
}
innerCode := ""
for _, innerCodeKey := range innerCodeKeys {
if innerCode2, ok := data[innerCodeKey]; ok {
innerCode = forceInnerCode2Str(innerCode2)
break
}
}
if _, ok := innerCodeOKCodes[innerCode]; ok {
for _, innerDataKey := range noPageInnerDataKeys {
if innerData, ok := data[innerDataKey]; ok {
return utils.DictKeysMan(innerData, keyToRemove, keyToKeep), nil
}
}
baseapi.SugarLogger.Errorf("can not find inner data, data:%v", jsonResult)
return nil, platformapi.ErrResponseDataFormatWrong
} else {
// todo 可以把具体错误消息放进来
return nil, utils.NewErrorCode(InnerCodeIsNotOk, innerCode, 1)
}
}
func NormalHavePageResultParser(data map[string]interface{}, totalCount int) ([]interface{}, int, error) {
var result map[string]interface{}
var retVal []interface{}
tempResult := data["result"]
if resultStr, ok := tempResult.(string); ok {
if err := utils.UnmarshalUseNumber([]byte(resultStr), &tempResult); err != nil {
return nil, 0, platformapi.ErrResponseDataFormatWrong
}
}
result = tempResult.(map[string]interface{})
if totalCount == 0 {
for _, totalCountKey := range havePageTotalCountKeys {
if totalCount2, ok := result[totalCountKey]; ok {
totalCountInt64, _ := totalCount2.(json.Number).Int64()
totalCount = int(totalCountInt64)
if totalCount == 0 {
return make([]interface{}, 0), 0, nil
}
break
}
}
if totalCount == 0 {
baseapi.SugarLogger.Errorf("can not find totalCount key, data:%v", result)
return nil, 0, platformapi.ErrResponseDataFormatWrong
}
}
for _, inner2ResultKey := range havePageInner2DataKeys {
if inner2Result, ok := result[inner2ResultKey]; ok {
if inner2ResultStr, ok := inner2Result.(string); ok {
err := utils.UnmarshalUseNumber([]byte(inner2ResultStr), &retVal)
if err != nil {
return nil, 0, platformapi.ErrResponseDataFormatWrong
}
} else {
retVal = inner2Result.([]interface{})
}
return retVal, totalCount, nil
}
}
baseapi.SugarLogger.Errorf("can not find result key, data:%v", result)
return nil, 0, platformapi.ErrResponseDataFormatWrong
}
func (a *API) AccessAPIHavePage(apiStr string, jdParams map[string]interface{}, keyToRemove, keyToKeep []string, pageResultParser PageResultParser) ([]interface{}, error) {
if pageResultParser == nil {
pageResultParser = NormalHavePageResultParser
}
localJdParams := make(map[string]interface{})
if jdParams != nil && len(jdParams) > 0 {
for k, v := range jdParams {
localJdParams[k] = v
}
}
totalCount := 0
pageNo := AllPage
pageSize := DefaultPageSize
if tempPageNo, ok := localJdParams["pageNo"]; ok {
pageNo = tempPageNo.(int)
}
if tempPageSize, ok := localJdParams["pageSize"]; ok {
pageSize = tempPageSize.(int)
}
curPage := pageNo
if curPage == AllPage {
curPage = 1
}
retVal := make([]interface{}, 0)
for {
localJdParams["pageNo"] = curPage
localJdParams["pageSize"] = pageSize
jsonResult, err := a.AccessAPI(apiStr, localJdParams)
if err != nil {
return nil, err
}
var data map[string]interface{}
if err := utils.UnmarshalUseNumber([]byte(jsonResult["data"].(string)), &data); err != nil {
return nil, platformapi.ErrResponseDataFormatWrong
}
innerCode := forceInnerCode2Str(data["code"])
if innerCode != "0" && innerCode != "200" {
// todo 可以把具体错误消息放进来
return nil, utils.NewErrorCode(InnerCodeIsNotOk, innerCode, 1)
}
inResult, totalCount2, err := pageResultParser(data, totalCount)
if err != nil {
return nil, err
}
totalCount = totalCount2
inResult = utils.DictKeysMan(inResult, keyToRemove, keyToKeep).([]interface{})
retVal = append(retVal, inResult...)
if curPage == pageNo || curPage*pageSize >= totalCount {
break
}
curPage++
}
return retVal, nil
}
func forceInnerCode2Str(innerCode interface{}) string {
if innerCodeStr, ok := innerCode.(string); ok {
return innerCodeStr
}
return utils.Int64ToStr(utils.MustInterface2Int64(innerCode))
}

View File

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

View File

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

View File

@@ -0,0 +1,109 @@
package mtpsapi
import (
"net/http"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
)
type CallbackResponse struct {
Code int `json:"code"`
}
type CallbackCommonMsg struct {
AppKey string
Timestamp int64
Sign string
}
type CallbackOrderMsg struct {
OrderInfoCommon
CallbackCommonMsg
Status int
CancelReasonId int
CancelReason string
}
type CallbackOrderExceptionMsg struct {
OrderInfoCommon
CallbackCommonMsg
ExceptionID int64
ExceptionCode int
ExceptionDescr string
ExceptionTime int64
}
var (
SuccessResponse = &CallbackResponse{Code: 0}
SignatureIsNotOk = &CallbackResponse{Code: -1}
)
func (a *API) CheckCallbackValidation(request *http.Request) (callbackResponse *CallbackResponse) {
request.ParseForm()
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
}
for _, valueKey := range []string{"delivery_id", "mt_peisong_id", "order_id"} {
baseapi.SugarLogger.Errorf("Missing mandatory param:%v", valueKey)
if request.FormValue(valueKey) == "" {
return &CallbackResponse{
Code: -1,
}
}
}
return nil
}
func (a *API) GetOrderCallbackMsg(request *http.Request) (orderMsg *CallbackOrderMsg, callbackResponse *CallbackResponse) {
if callbackResponse = a.CheckCallbackValidation(request); callbackResponse != nil {
return nil, callbackResponse
}
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"),
},
CallbackCommonMsg: CallbackCommonMsg{
AppKey: request.FormValue("appkey"),
Timestamp: utils.Str2Int64(request.FormValue("timestamp")),
Sign: request.FormValue("sign"),
},
Status: int(utils.Str2Int64(request.FormValue("status"))),
CancelReasonId: int(utils.Str2Int64(request.FormValue("cancel_reason_id"))),
CancelReason: request.FormValue("cancel_reason"),
}
return orderMsg, nil
}
func (a *API) GetOrderExceptionCallbackMsg(request *http.Request) (orderMsg *CallbackOrderExceptionMsg, callbackResponse *CallbackResponse) {
if callbackResponse = a.CheckCallbackValidation(request); callbackResponse != nil {
return nil, callbackResponse
}
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"),
},
CallbackCommonMsg: CallbackCommonMsg{
AppKey: request.FormValue("appkey"),
Timestamp: utils.Str2Int64(request.FormValue("timestamp")),
Sign: request.FormValue("sign"),
},
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")),
}
return orderMsg, nil
}

View File

@@ -0,0 +1,279 @@
package mtpsapi
import (
"crypto/sha1"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"github.com/fatih/structs"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/platformapi"
"git.rosy.net.cn/baseapi/utils"
)
const (
mtpsAPIURL = "https://peisongopen.meituan.com/api"
signKey = "sign"
)
const (
OrderStatusWaitingForSchedule = 0
OrderStatusAccepted = 20
OrderStatusPickedUp = 30
OrderStatusDeliverred = 50
OrderStatusCanceled = 99
)
const (
DeliveryServiceCodeRapid = 4011
DeliveryServiceCodeIntime = 4012
DeliveryServiceCodeTogether = 4013
)
const (
PickupTypeClientSendToStation = 1
PickupTypeMtPick = 2
)
const (
OrderTypeASAP = 0
OrderTypeBook = 1
)
const (
CoordinateTypeMars = 0
CoordinateTypeBaidu = 1
)
// 错误码
const (
ResponseCodeSuccess = 0
)
// 取消原因
const (
CancelReasonClientActive = 101
CancelReasonClientChangeTimeOrAddress = 102
CancelReasonGoodRelated = 103
CancelReasonMerchantOther = 199
CancelReasonMtpsAttitude = 201
CancelReasonRidderSendNotIntime = 202
CancelReasonRideerGetGoodNotIntime = 203
CancelReasonRideerMtpsOther = 299
CancelReasonRideerOther = 399
)
type OrderInfoCommon struct {
DeliveryId int64
MtPeisongId string
OrderId string
CourierName string
CourierPhone string
}
type OrderInfo struct {
OrderInfoCommon
Status int
OperateTime int
CancelReasonID int
CancelReason string
}
type OrderResponse struct {
MtPeisongID string `json:"mt_peisong_id"`
DeliveryID int64 `json:"delivery_id"`
OrderID string `json:"order_id"`
}
type ResponseResult struct {
Code int `json:"code"`
Message string `json:"message"`
Data map[string]interface{} `json:"data"`
}
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"`
ReceiverPhone string `json:"receiver_phone"`
ReceiverLng int `json:"receiver_lng"`
ReceiverLat int `json:"receiver_lat"`
CoordinateType int `json:"coordinate_type"`
GoodsValue float64 `json:"goods_value"`
GoodsWeight float64 `json:"goods_weight"`
ExpectedDeliveryTime int64 `json:"expected_delivery_time"`
OrderType int `json:"order_type"`
}
type API struct {
appKey string
secret string
client *http.Client
config *platformapi.APIConfig
}
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: curConfig.ClientTimeout},
config: &curConfig,
}
}
func (a *API) signParams(params url.Values) string {
keys := make([]string, 0)
for k := range params {
if k != signKey {
keys = append(keys, k)
}
}
sort.Strings(keys)
finalStr := a.secret
for _, key := range keys {
valStr := strings.Join(params[key], "")
if valStr != "" {
finalStr += key + valStr
}
}
// baseapi.SugarLogger.Debug(finalStr)
return fmt.Sprintf("%x", sha1.Sum([]byte(finalStr)))
}
func (a *API) AccessAPI(action string, params map[string]interface{}) (retVal *ResponseResult, err error) {
if params == nil {
panic("params is nil!")
}
params2 := make(url.Values)
for k, v := range params {
params2[k] = []string{fmt.Sprint(v)}
}
params2["appkey"] = []string{a.appKey}
params2["timestamp"] = []string{utils.Int64ToStr(utils.GetCurTimestamp())}
params2["version"] = []string{"1.0"}
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")
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, platformapi.ErrResponseDataFormatWrong
}
code := int(utils.MustInterface2Int64(jsonResult1["code"]))
retVal = &ResponseResult{
Code: code,
}
if code == ResponseCodeSuccess {
if innerData, ok := jsonResult1["data"]; ok {
retVal.Data, _ = innerData.(map[string]interface{})
}
return platformapi.ErrLevelSuccess, nil
}
baseapi.SugarLogger.Warnf("response business code is not ok, data:%v, code:%v", jsonResult1, code)
retVal.Message = jsonResult1["message"].(string)
newErr := utils.NewErrorIntCode(retVal.Message, code)
return platformapi.ErrLevelCodeIsNotOK, newErr
})
return retVal, err
}
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 (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)
allParams := utils.MergeMaps(params, addParams)
if params["order_type"] != utils.Int2Str(OrderTypeBook) {
delete(params, "expected_delivery_time")
}
if result, err := a.AccessAPI("order/createByShop", allParams); err != nil {
return nil, err
} else {
return a.result2OrderResponse(result), nil
}
}
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 := a.AccessAPI("order/status/query", params); err != nil {
return nil, err
} else {
return result.Data, nil
}
}
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 := a.AccessAPI("order/delete", params); err != nil {
baseapi.SugarLogger.Debugf("result:%v", result)
return nil, err
} else {
return a.result2OrderResponse(result), nil
}
}
func (a *API) simulateOrderBehavior(action string, deliveryId int64, mtPeiSongId string) (err error) {
params := map[string]interface{}{
"delivery_id": deliveryId,
"mt_peisong_id": mtPeiSongId,
}
_, err = a.AccessAPI("test/order/"+action, params)
return err
}
func (a *API) SimulateArrange(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("arrange", deliveryId, mtPeiSongId)
}
func (a *API) SimulatePickup(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("pickup", deliveryId, mtPeiSongId)
}
func (a *API) SimulateDeliver(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("deliver", deliveryId, mtPeiSongId)
}
func (a *API) SimulateRearrange(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("rearrange", deliveryId, mtPeiSongId)
}
func (a *API) SimulateReportException(deliveryId int64, mtPeiSongId string) (err error) {
return a.simulateOrderBehavior("reportException", deliveryId, mtPeiSongId)
}

View File

@@ -0,0 +1,106 @@
package mtpsapi
import (
"testing"
"git.rosy.net.cn/baseapi"
"git.rosy.net.cn/baseapi/utils"
"go.uber.org/zap"
)
var (
mtpsapi *API
sugarLogger *zap.SugaredLogger
)
func init() {
logger, _ := zap.NewDevelopment()
sugarLogger = logger.Sugar()
baseapi.Init(sugarLogger)
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) {
if err != nil {
sugarLogger.Debug(err)
t.Fatal(err.Error())
}
}
func TestTest(t *testing.T) {
sugarLogger.Debug(utils.GetCurTimeStr())
}
func TestAccessAPI(t *testing.T) {
mtPeiSongId := "1529387562097059"
params := map[string]interface{}{
"delivery_id": 123456789,
"mt_peisong_id": mtPeiSongId,
}
result, err := mtpsapi.AccessAPI("order/status/query", params)
if err != nil {
t.Fatalf("Error when accessing AccessAPI 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)
}
}
}
func TestCreateOrderByShop(t *testing.T) {
basicParams := &CreateOrderByShopParam{
DeliveryID: 123456789,
OrderID: "order_123456789",
// 设置测试门店 id测试门店的坐标地址为 97235456,31065079高德坐标配送范围3km
ShopID: "test_0001",
DeliveryServiceCode: DeliveryServiceCodeIntime,
ReceiverName: "xjh",
ReceiverAddress: "九里堤",
ReceiverPhone: "18112345678",
ReceiverLng: 97235456,
ReceiverLat: 31065079,
CoordinateType: CoordinateTypeMars,
GoodsValue: 12.34,
GoodsWeight: 3.4,
OrderType: OrderTypeASAP,
}
order, err := mtpsapi.CreateOrderByShop(basicParams, nil)
handleError(t, err)
if order != nil {
sugarLogger.Debugf("order:%v", order)
}
}
func TestSimulateArrange(t *testing.T) {
err := mtpsapi.SimulateArrange(123456789, "1529387562097059")
handleError(t, err)
}
func TestSimulatePickup(t *testing.T) {
err := mtpsapi.SimulatePickup(123456789, "1529387562097059")
handleError(t, err)
}
func TestSimulateRearrange(t *testing.T) {
err := mtpsapi.SimulateRearrange(123456789, "1529387562097059")
handleError(t, err)
}
func TestSimulateDeliver(t *testing.T) {
err := mtpsapi.SimulateDeliver(123456789, "1529387562097059")
handleError(t, err)
}
func TestSimulateReportException(t *testing.T) {
err := mtpsapi.SimulateReportException(123456789, "1529387562097059")
handleError(t, err)
}
func TestCancelOrder(t *testing.T) {
result, err := mtpsapi.CancelOrder(123456789, "1529387562097059", CancelReasonMerchantOther, "just a test")
handleError(t, err)
sugarLogger.Debug(result)
}

119
platformapi/platformapi.go Normal file
View File

@@ -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
}
}