package mtwmapi import ( "bytes" "crypto/md5" "fmt" "mime/multipart" "net/http" "net/url" "sort" "strings" "time" "git.rosy.net.cn/baseapi" "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" ) 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 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 string, config ...*platformapi.APIConfig) *API { curConfig := platformapi.DefAPIConfig if len(config) > 0 { curConfig = *config[0] } return &API{ appID: appID, secret: secret, 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) 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 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 { baseapi.SugarLogger.Debugf("mtwm AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) 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) AccessAPI(cmd string, isGet bool, bizParams map[string]interface{}) (retVal interface{}, err error) { return a.AccessAPI2(cmd, isGet, bizParams, resultKeyData, "") }