Files
baseapi/platformapi/mtwmapi/mtwmapi.go
2025-11-21 09:09:09 +08:00

326 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package mtwmapi
import (
"bytes"
"crypto/md5"
"fmt"
"git.rosy.net.cn/baseapi/utils"
"mime/multipart"
"net/http"
"net/url"
"sort"
"strings"
"time"
"git.rosy.net.cn/baseapi/platformapi"
)
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"
KeyAppID = "app_id"
)
const (
resultKeyData = "data"
resultKeyMsg = "msg"
resultKeySuccessMsg = "success_msg"
)
const (
GeneralMaxLimit = 100 // 大多数的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
}
func (a *API) Error() string {
//TODO implement me
panic("implement me")
}
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) GetToken() string {
return a.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)
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 {
w.WriteField(k, url.QueryEscape(fmt.Sprint(v)))
}
w.Close()
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)
request, _ = http.NewRequest(http.MethodGet, fullURL, nil)
} else {
fullURL := a.genURL(cmd)
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 {
switch errObj.(type) {
case string:
errorInfo := errObj.(string)
if errorInfo != "ok" {
var newErr *utils.ErrorWithCode
if cmd == "bill/list" {
newErr = utils.NewErrorIntCode(jsonResult1["error"].(interface{}).(map[string]interface{})["msg"].(string), int(utils.MustInterface2Int64(jsonResult1["error"].(interface{}).(map[string]interface{})["code"])))
} else {
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
}
case interface{}:
}
}
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
}