package mtwmapi import ( "bytes" "crypto/md5" "fmt" "mime/multipart" "net/http" "net/url" "sort" "strings" "sync" "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 ( ErrCodeSysErr = 700 // 系统错误,按美团外卖技术支持的说法,可当成需重试的错误 ErrCodeAccessLimited = 711 // 接口调用过于频繁,触发流控,请降低调用频率 ErrCodeNoAppFood = 805 // 不存在此菜品 ErrCodeNoSuchOrder = 808 // 不存在此订单 ErrCodeSkuCategoryNotExist = 1021 // 菜品分类不存在 ErrCodeSkuCategoryExist = 1037 // 菜品分类已存在 ) type API struct { platformapi.APICookie appID string secret string callbackURL string client *http.Client config *platformapi.APIConfig locker sync.RWMutex userCookies map[string]string } var ( canRetryCodes = map[int]int{ ErrCodeSysErr: 1, ErrCodeAccessLimited: 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, userCookies: make(map[string]string), } } 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) 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 { fmt.Println(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") } retVal = jsonResult1[resultKey] if errObj, ok := jsonResult1["error"]; ok { baseapi.SugarLogger.Debugf("mtwm AccessAPI failed, jsonResult1:%s", utils.Format4Output(jsonResult1, true)) errorInfo := errObj.(map[string]interface{}) newErr := utils.NewErrorIntCode(errorInfo["msg"].(string), int(utils.MustInterface2Int64(errorInfo["code"]))) if canRetryCodes[newErr.IntCode()] == 1 { return platformapi.ErrLevelExceedLimit, newErr } 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, "") }