diff --git a/platform/elmapi/elmapi.go b/platform/elmapi/elmapi.go new file mode 100644 index 00000000..edd27f46 --- /dev/null +++ b/platform/elmapi/elmapi.go @@ -0,0 +1,199 @@ +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 + } + } +} diff --git a/platform/elmapi/elmapi_test.go b/platform/elmapi/elmapi_test.go new file mode 100644 index 00000000..60c0b63f --- /dev/null +++ b/platform/elmapi/elmapi_test.go @@ -0,0 +1,37 @@ +package elmapi + +import ( + "encoding/json" + "testing" + + "git.rosy.net.cn/baseapi/utils" + "go.uber.org/zap" +) + +var ( + elmapi *ELMAPI + sugarLogger *zap.SugaredLogger +) + +func init() { + logger, _ := zap.NewDevelopment() + sugarLogger = logger.Sugar() + elmapi = NewELMAPI("bab2a27f99562f394b411dbb9a6214da", "KLRDcOZGrk", "1fc221f8265506531da36fb613d5f5ad673f2e9a", sugarLogger, true) +} + +func TestTest(t *testing.T) { + sugarLogger.Debug(utils.GetCurTimeStr()) +} + +func TestAccessELM(t *testing.T) { + result, err := elmapi.AccessELM("eleme.user.getUser", nil) + if err != nil { + t.Fatalf("Error when accessing AccessJDQuery result:%v, error:%v", result, err) + } else { + userIdNumber := result.Result["userId"].(json.Number) + userId, err := userIdNumber.Int64() + if userId != 336072266326420104 || err != nil { + t.Fatalf("userId is not correct:%v", userIdNumber) + } + } +} diff --git a/platform/jdapi/jdapi.go b/platform/jdapi/jdapi.go index 836a6a58..0687d9e3 100644 --- a/platform/jdapi/jdapi.go +++ b/platform/jdapi/jdapi.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net" "net/http" "net/url" @@ -57,11 +56,9 @@ const ( ) var ( - ErrJdParam = errors.New("can not marshal jd param") ErrSystemErrMaxRetry = errors.New("JD System error reach max retry count!") ErrLimitReachMaxRetry = errors.New("JD Reach max retry count!") ErrHttpCode = errors.New("JD HTTP Code is not 200") - ErrFormatWrong = errors.New("JD Result format is strange!") ErrJDCode = errors.New("JD code is not 0") ErrInnerCodeIsNotOk = errors.New("JD result inner code is not ok") @@ -146,12 +143,11 @@ func (j *JDAPI) AccessJDQuery(apiStr string, jdParams map[string]string) (map[st jdParams = make(map[string]string, 0) } jdParamStr, err := json.Marshal(jdParams) - if err == nil { - params["jd_param_json"] = string(jdParamStr) - } else { - return nil, ErrJdParam + if err != nil { + j.sugarLogger.Errorf("Error when marshal %v, error:%v", jdParams, err) + return nil, err } - + params["jd_param_json"] = string(jdParamStr) params["timestamp"] = utils.GetCurTimeStr() sign := signParams(params) params["sign"] = sign @@ -184,16 +180,10 @@ func (j *JDAPI) AccessJDQuery(apiStr string, jdParams map[string]string) (map[st return nil, ErrHttpCode } - var jsonResult map[string]interface{} - bodyData, err := ioutil.ReadAll(response.Body) - if err != nil { - j.sugarLogger.Debugf("ioutil.ReadAll return:%v", err) - return nil, err - } + jsonResult, err := utils.HttpResponse2Json(response) - err = utils.UnmarshalUseNumber(bodyData, &jsonResult) if err != nil { - j.sugarLogger.Debugf("json.Unmarshal return:%v", err) + j.sugarLogger.Warnf("HttpResponse2Json return:%v", err) return nil, err } diff --git a/utils/utils.go b/utils/utils.go index 73c7f4c6..6c8c15ca 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" + "net/http" "reflect" "strconv" "strings" @@ -141,3 +143,17 @@ func GetCurTimestamp() int64 { func GetAPIOperator() string { return time.Now().Format("2006-01-02_15:04:05") } + +func HttpResponse2Json(response *http.Response) (map[string]interface{}, error) { + var jsonResult map[string]interface{} + bodyData, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + + err = UnmarshalUseNumber(bodyData, &jsonResult) + if err != nil { + return nil, err + } + return jsonResult, nil +}