package elmapi import ( "crypto/md5" "fmt" "io/ioutil" "net/http" "net/url" "sort" "strings" "time" "git.rosy.net.cn/baseapi/platform/common" "git.rosy.net.cn/baseapi/utils" "go.uber.org/zap" ) const ( clientTimeout = time.Second * 10 sleepSecondWhenLimited = 6 * time.Second maxRetryCountWhenNetworkException = 3 maxRetryCountWhenReachLimited = 10 ) 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/" signKey = "signature" ) type ELMResult struct { Id string Result 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: clientTimeout}, } if isProd { api.url, _ = url.Parse(ELM_API_URL_PROD) } else { api.url, _ = url.Parse(ELM_API_URL_SANDBOX) } return api } func (e *ELMAPI) signParamsMap(mapData map[string]interface{}, prefix string) string { keyValues := make([]string, 0) for k, v := range mapData { if k != signKey { vBytes := utils.MustMarshal(v) keyValues = append(keyValues, k+"="+string(vBytes)) } } sort.Strings(keyValues) finalStr := prefix + strings.Join(keyValues, "") + e.secret // e.sugarLogger.Debugf("sign str:%v", finalStr) return fmt.Sprintf("%X", md5.Sum([]byte(finalStr))) } func (e *ELMAPI) signParams(action string, payload *ELMPayload) string { mapData := utils.MergeMaps(payload.Metas, payload.Params) return e.signParamsMap(mapData, action+e.token) } func (e *ELMAPI) AccessELM(action string, params map[string]interface{}) (retVal *ELMResult, err error) { if params == nil { params = make(map[string]interface{}, 0) } metas := map[string]interface{}{ "app_key": e.appKey, "timestamp": 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) apiAccess := &common.AccessPlatformAPIWithRetryParams{ MaxExceedLimitRetryCount: maxRetryCountWhenReachLimited, MaxRecoverableRetryCount: maxRetryCountWhenNetworkException, SleepSecondWhenExceedLimit: sleepSecondWhenLimited, Client: e.client, 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(string(utils.MustMarshal(payload)))), }, SugarLogger: e.sugarLogger, } err = common.AccessPlatformAPIWithRetry(apiAccess, func(response *http.Response) (result string, err error) { jsonResult1, err := utils.HttpResponse2Json(response) if err != nil { e.sugarLogger.Warnf("HttpResponse2Json return:%v", err) return common.PAErrorLevelGeneralFail, err } resultError, _ := jsonResult1["error"].(map[string]interface{}) retVal = &ELMResult{ Id: jsonResult1["id"].(string), Error: resultError, Result: jsonResult1["result"], } errinfoMap := retVal.Error if errinfoMap == nil { return common.PAErrorLevelSuccess, nil } errCode := errinfoMap["code"].(string) if errCode == "EXCEED_LIMIT" { return common.PAErrorLevelExceedLimit, nil } else if errCode == "SERVER_ERROR" || errCode == "BIZ_SYSTEM_ERROR" || errCode == "BIZ_1006" || errCode == "BUSINESS_ERROR" { return common.PAErrorLevelRecoverable, nil } else { return errCode, nil } }) return retVal, err }