package elmapi import ( "crypto/md5" "encoding/json" "errors" "fmt" "io/ioutil" "net" "net/http" "net/url" "sort" "strings" "time" "git.rosy.net.cn/baseapi/utils" "go.uber.org/zap" ) const ( ELM_API_URL_SANDBOX = "https://open-api-sandbox.shop.ele.me/api/v1/" ELM_API_URL_PROD = "https://open-api.shop.ele.me/api/v1/" ) const ( sleepSecondWhenLimited = 6 * time.Second maxRetryCountWhenNetworkException = 3 maxRetryCountWhenReachLimited = 10 ) var ( ErrSystemErrMaxRetry = errors.New("ELM System error reach max retry count!") ErrLimitReachMaxRetry = errors.New("ELM Reach max retry count!") ErrHttpCode = errors.New("ELM HTTP Code is not 200") ErrELMCode = errors.New("ELM code is not 0") ) type ELMResult struct { Id string Result map[string]interface{} Error map[string]interface{} } type ELMAPI struct { token string appKey string secret string sugarLogger *zap.SugaredLogger url *url.URL client http.Client } type ELMPayload 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 NewELMAPI(token, appKey, secret string, sugarLogger *zap.SugaredLogger, isProd bool) *ELMAPI { api := &ELMAPI{ token: token, appKey: appKey, secret: secret, sugarLogger: sugarLogger, client: http.Client{Timeout: time.Second * 10}, } if isProd { api.url, _ = url.Parse(ELM_API_URL_PROD) } else { api.url, _ = url.Parse(ELM_API_URL_SANDBOX) } return api } func (e *ELMAPI) signParams(action string, payload *ELMPayload) string { keyValues := make([]string, 0) allData := []map[string]interface{}{ payload.Metas, payload.Params, } for _, data := range allData { for k, v := range data { vBytes, _ := json.Marshal(v) keyValues = append(keyValues, k+"="+string(vBytes)) } } sort.Strings(keyValues) finalStr := action + e.token + strings.Join(keyValues, "") + e.secret // e.sugarLogger.Debugf("sign str:%v", finalStr) return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) } func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (*ELMResult, error) { if params == nil { params = make(map[string]interface{}, 0) } metas := map[string]interface{}{ "app_key": e.appKey, "timestamp": int(utils.GetCurTimestamp()), } payload := &ELMPayload{ Token: e.token, Nop: "1.0.0", Metas: metas, Params: params, Action: action, Id: utils.GetUUID(), } payload.Signature = e.signParams(action, payload) dataBytes, err := json.Marshal(payload) if err != nil { e.sugarLogger.Errorf("Error when marshal %v, error:%v", payload, err) return nil, err } dataStr := string(dataBytes) exceedLimitRetryCount := 0 systemErrorRetryCount := 0 for { request := &http.Request{ Method: "POST", URL: e.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(dataStr)), } response, err := e.client.Do(request) if err != nil { e.sugarLogger.Debugf("client.Get return err:%v", err) err, ok := err.(net.Error) systemErrorRetryCount++ if ok && err.Timeout() && systemErrorRetryCount <= maxRetryCountWhenNetworkException { continue } else { return nil, err } } defer response.Body.Close() if response.StatusCode != 200 { e.sugarLogger.Debugf("http code is:%d", response.StatusCode) systemErrorRetryCount++ if systemErrorRetryCount <= maxRetryCountWhenNetworkException { continue } return nil, ErrHttpCode } jsonResult1, err := utils.HttpResponse2Json(response) resultError, _ := jsonResult1["error"].(map[string]interface{}) result, _ := jsonResult1["result"].(map[string]interface{}) jsonResult := &ELMResult{ Id: jsonResult1["id"].(string), Error: resultError, Result: result, } if err != nil { e.sugarLogger.Warnf("HttpResponse2Json return:%v", err) return nil, err } errinfoMap := jsonResult.Error if errinfoMap == nil { return jsonResult, nil } errCode := errinfoMap["code"].(string) if errCode == "EXCEED_LIMIT" { exceedLimitRetryCount++ if exceedLimitRetryCount <= maxRetryCountWhenReachLimited { time.Sleep(sleepSecondWhenLimited) } else { return jsonResult, ErrLimitReachMaxRetry } } else if errCode == "SERVER_ERROR" || errCode == "BIZ_SYSTEM_ERROR" || errCode == "BIZ_1006" || errCode == "BUSINESS_ERROR" { if systemErrorRetryCount <= maxRetryCountWhenNetworkException { continue } return jsonResult, ErrSystemErrMaxRetry } else { e.sugarLogger.Debug(jsonResult) return jsonResult, ErrELMCode } } }