311 lines
9.7 KiB
Go
311 lines
9.7 KiB
Go
package mtwmapi
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/md5"
|
||
"fmt"
|
||
"mime/multipart"
|
||
"net/http"
|
||
"net/url"
|
||
"sort"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.rosy.net.cn/baseapi/platformapi"
|
||
"git.rosy.net.cn/baseapi/utils"
|
||
)
|
||
|
||
const (
|
||
apiURL = "https://waimaiopen.meituan.com/api/v1"
|
||
sandboxAPIURL = "http://openapi.b.waimai.test.sankuai.com/api/v1"
|
||
signKey = "sig"
|
||
|
||
appIDc4 = "589"
|
||
appIDsc = "5873"
|
||
)
|
||
|
||
const (
|
||
KeyAppPoiCode = "app_poi_code"
|
||
KeyAppPoiCodes = "app_poi_codes"
|
||
KeyAppFoodCode = "app_food_code"
|
||
KeyImgName = "img_name"
|
||
KeyImgData = "img_data"
|
||
KeyOrderID = "order_id"
|
||
)
|
||
|
||
const (
|
||
resultKeyData = "data"
|
||
resultKeyMsg = "msg"
|
||
resultKeySuccessMsg = "success_msg"
|
||
)
|
||
const (
|
||
GeneralMaxLimit = 200 // 大多数的API的批处理最大条数
|
||
)
|
||
|
||
const (
|
||
ErrCodeSuccess = 0
|
||
ErrCodeSysErr = 700 // 系统错误,按美团外卖技术支持的说法,可当成需重试的错误
|
||
ErrCodeParameterFormatWrong = 705 // 参数格式错误
|
||
ErrCodeAccessLimited = 711 // 接口调用过于频繁,触发流控,请降低调用频率
|
||
|
||
ErrCodeNoAppFood = 805 // 不存在此菜品
|
||
ErrCodeNoSuchOrder = 806 // 不存在此订单
|
||
ErrCodeOpFailed = 808 // 操作失败(如订单在操作时,状态已变更等情况)
|
||
ErrCodeSkuCategoryNotExist = 1021 // 菜品分类不存在
|
||
ErrCodeSkuCategoryExist = 1037 // 菜品分类已存在
|
||
ErrCodeCanNotModifyStoreDeliveryInfo = 3018 // 商家已接入美团配送,不可修改门店配送相关信息
|
||
)
|
||
|
||
type API struct {
|
||
platformapi.APICookie
|
||
|
||
appID string
|
||
secret string
|
||
token string
|
||
callbackURL string
|
||
client *http.Client
|
||
config *platformapi.APIConfig
|
||
}
|
||
|
||
var (
|
||
canRetryCodes = map[int]int{
|
||
ErrCodeAccessLimited: 1,
|
||
}
|
||
canRecoverCodes = map[int]int{
|
||
ErrCodeSysErr: 1,
|
||
}
|
||
)
|
||
|
||
func New(appID, secret, callbackURL, token string, config ...*platformapi.APIConfig) *API {
|
||
curConfig := platformapi.DefAPIConfig
|
||
if len(config) > 0 {
|
||
curConfig = *config[0]
|
||
}
|
||
return &API{
|
||
appID: appID,
|
||
secret: secret,
|
||
token: token,
|
||
callbackURL: callbackURL,
|
||
client: &http.Client{Timeout: curConfig.ClientTimeout},
|
||
config: &curConfig,
|
||
}
|
||
}
|
||
|
||
func (a *API) genURL(cmd string) string {
|
||
return apiURL + "/" + cmd
|
||
}
|
||
|
||
func (a *API) signParams(signURL string, params map[string]interface{}) string {
|
||
keys := make([]string, 0)
|
||
for k := range params {
|
||
if k != signKey {
|
||
keys = append(keys, k)
|
||
}
|
||
}
|
||
|
||
sort.Strings(keys)
|
||
finalStr := signURL
|
||
kvPaires := make([]string, len(keys))
|
||
for k, key := range keys {
|
||
if params[key] != nil {
|
||
kvPaires[k] = key + "=" + fmt.Sprintf("%v", params[key])
|
||
}
|
||
}
|
||
finalStr += strings.Join(kvPaires, "&") + a.secret
|
||
// baseapi.SugarLogger.Debug(finalStr)
|
||
return fmt.Sprintf("%x", md5.Sum([]byte(finalStr)))
|
||
}
|
||
|
||
func (a *API) GetAppID() string {
|
||
return a.appID
|
||
}
|
||
|
||
func (a *API) SetToken(token string) {
|
||
a.token = token
|
||
}
|
||
|
||
func (a *API) AccessAPI2(cmd string, isGet bool, bizParams map[string]interface{}, resultKey, trackInfo string) (retVal interface{}, err error) {
|
||
params := make(map[string]interface{})
|
||
params["timestamp"] = time.Now().Unix()
|
||
params["app_id"] = a.appID
|
||
if a.appID == appIDsc {
|
||
params["access_token"] = a.token
|
||
}
|
||
//params["access_token"] = "token_s14CNZErtLaXijy0JtBAXg"
|
||
params = utils.MergeMaps(params, bizParams)
|
||
imgData := params[KeyImgData]
|
||
if imgData != nil {
|
||
delete(params, KeyImgData)
|
||
}
|
||
signURL := a.genURL(cmd) + "?"
|
||
params[signKey] = a.signParams(signURL, params)
|
||
err = platformapi.AccessPlatformAPIWithRetry(a.client,
|
||
func() *http.Request {
|
||
var request *http.Request
|
||
if isGet {
|
||
fullURL := utils.GenerateGetURL(apiURL, cmd, params)
|
||
// baseapi.SugarLogger.Debug(fullURL)
|
||
request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
|
||
} else {
|
||
fullURL := a.genURL(cmd)
|
||
// baseapi.SugarLogger.Debug(utils.Map2URLValues(params).Encode())
|
||
if imgData == nil {
|
||
request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode()))
|
||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
} else {
|
||
var b bytes.Buffer
|
||
w := multipart.NewWriter(&b)
|
||
if fw, err := w.CreateFormFile("file", params[KeyImgName].(string)); err != nil {
|
||
panic(err.Error())
|
||
} else {
|
||
fw.Write(imgData.([]byte))
|
||
}
|
||
for k, v := range params {
|
||
// baseapi.SugarLogger.Debug(k, " ", v)
|
||
w.WriteField(k, url.QueryEscape(fmt.Sprint(v)))
|
||
}
|
||
w.Close()
|
||
// b.WriteString(utils.Map2URLValues(params).Encode())
|
||
request, _ = http.NewRequest(http.MethodPost, fullURL, &b)
|
||
request.Header.Set("Content-Type", w.FormDataContentType())
|
||
}
|
||
request.Header.Set("charset", "UTF-8")
|
||
}
|
||
if trackInfo != "" {
|
||
request.Header.Set(platformapi.KeyTrackInfo, trackInfo)
|
||
}
|
||
// request.Close = true //todo 为了性能考虑还是不要关闭
|
||
return request
|
||
},
|
||
a.config,
|
||
func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
|
||
if jsonResult1 == nil {
|
||
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
|
||
}
|
||
// 不管有无错误,都尝试取得数据(因为有出错,但有有效数据返回的情况),比如ecommerce/order/getOrderIdByDaySeq
|
||
if resultKey != "" {
|
||
retVal = jsonResult1[resultKey]
|
||
} else {
|
||
retVal = jsonResult1
|
||
}
|
||
if errObj, ok := jsonResult1["error"]; ok {
|
||
errorInfo := errObj.(map[string]interface{})
|
||
errCode := int(utils.MustInterface2Int64(errorInfo["code"]))
|
||
if errCode != ErrCodeSuccess {
|
||
newErr := utils.NewErrorIntCode(errorInfo["msg"].(string), errCode)
|
||
if canRetryCodes[errCode] == 1 {
|
||
return platformapi.ErrLevelExceedLimit, newErr
|
||
} else if canRecoverCodes[errCode] == 1 {
|
||
return platformapi.ErrLevelRecoverableErr, newErr
|
||
} else {
|
||
return platformapi.ErrLevelCodeIsNotOK, newErr
|
||
}
|
||
}
|
||
}
|
||
return platformapi.ErrLevelSuccess, nil
|
||
})
|
||
err = platformapi.RebuildError(err, bizParams, []string{
|
||
KeyAppPoiCode,
|
||
KeyAppPoiCodes,
|
||
KeyAppFoodCode,
|
||
})
|
||
return retVal, err
|
||
}
|
||
|
||
func (a *API) AccessAPI3(cmd string, isGet bool, bizParams map[string]interface{}) (retVal interface{}, err error) {
|
||
params := make(map[string]interface{})
|
||
params["timestamp"] = time.Now().Unix()
|
||
params["app_id"] = a.appID
|
||
params = utils.MergeMaps(params, bizParams)
|
||
signURL := a.genURL(cmd) + "?"
|
||
params[signKey] = a.signParams(signURL, params)
|
||
err = platformapi.AccessPlatformAPIWithRetry(a.client,
|
||
func() *http.Request {
|
||
var request *http.Request
|
||
if isGet {
|
||
fullURL := utils.GenerateGetURL(apiURL, cmd, params)
|
||
// baseapi.SugarLogger.Debug(fullURL)
|
||
request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
|
||
} else {
|
||
fullURL := a.genURL(cmd)
|
||
// baseapi.SugarLogger.Debug(utils.Map2URLValues(params).Encode())
|
||
request, _ = http.NewRequest(http.MethodPost, fullURL, strings.NewReader(utils.Map2URLValues(params).Encode()))
|
||
request.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
request.Header.Set("charset", "UTF-8")
|
||
}
|
||
// request.Close = true //todo 为了性能考虑还是不要关闭
|
||
return request
|
||
},
|
||
a.config,
|
||
func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) {
|
||
if jsonResult1 == nil {
|
||
return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil")
|
||
}
|
||
// 不管有无错误,都尝试取得数据(因为有出错,但有有效数据返回的情况),比如ecommerce/order/getOrderIdByDaySeq
|
||
retVal = jsonResult1
|
||
if errObj, ok := jsonResult1["data"]; ok {
|
||
errorInfo := errObj.(string)
|
||
if errorInfo != "ok" {
|
||
newErr := utils.NewErrorIntCode(jsonResult1["error_list"].([]interface{})[0].(map[string]interface{})["msg"].(string), int(utils.MustInterface2Int64(jsonResult1["error_list"].([]interface{})[0].(map[string]interface{})["code"])))
|
||
return errLevel, newErr
|
||
}
|
||
}
|
||
return platformapi.ErrLevelSuccess, nil
|
||
})
|
||
return retVal, err
|
||
}
|
||
|
||
func (a *API) AccessAPI(cmd string, isGet bool, bizParams map[string]interface{}) (retVal interface{}, err error) {
|
||
return a.AccessAPI2(cmd, isGet, bizParams, resultKeyData, "")
|
||
}
|
||
|
||
//美团认证
|
||
//https://open-shangou.meituan.com/home/guide/38
|
||
func (a *API) GetOAuthCode(appPoiCode string) (retVal interface{}, err error) {
|
||
retVal, err = a.AccessAPI2("oauth/authorize", true, map[string]interface{}{
|
||
"app_poi_code": appPoiCode,
|
||
"response_type": "code",
|
||
}, "code", "")
|
||
return retVal, err
|
||
}
|
||
|
||
func (a *API) GetAccessToken(code string) (retVal interface{}, err error) {
|
||
retVal, err = a.AccessAPI2("oauth/token", false, map[string]interface{}{
|
||
"grant_type": "authorization_code",
|
||
"code": code,
|
||
}, "access_token", "")
|
||
return retVal, err
|
||
}
|
||
|
||
type TokenResult struct {
|
||
Status int `json:"status"`
|
||
State interface{} `json:"state"`
|
||
Message string `json:"message"`
|
||
AccessToken string `json:"access_token"`
|
||
ExpiresIn int `json:"expires_in"`
|
||
RefreshToken string `json:"refresh_token"`
|
||
ReExpiresIn int `json:"re_expires_in"`
|
||
}
|
||
|
||
func (a *API) GetAccessToken2(appPoiCode string) (result *TokenResult, err error) {
|
||
retVal, err := a.AccessAPI2("oauth/authorize", true, map[string]interface{}{
|
||
"response_type": "token",
|
||
"app_poi_code": appPoiCode,
|
||
}, "", "")
|
||
if err == nil {
|
||
utils.Map2StructByJson(retVal, &result, false)
|
||
}
|
||
return result, err
|
||
}
|
||
|
||
func (a *API) RefreshAccessToken(token string) (result *TokenResult, err error) {
|
||
retVal, err := a.AccessAPI2("oauth/token", false, map[string]interface{}{
|
||
"grant_type": "refresh_token",
|
||
"refresh_token": token,
|
||
}, "", "")
|
||
if err == nil {
|
||
utils.Map2StructByJson(retVal, &result, false)
|
||
}
|
||
return result, err
|
||
}
|