package rest import ( "bytes" "context" "encoding/json" "errors" "fmt" "gitea.zjmud.xyz/phyer/v5sdkgo/utils" "io/ioutil" "net/http" "strings" "time" ) type RESTAPI struct { EndPoint string `json:"endPoint"` // GET/POST Method string `json:"method"` Uri string `json:"uri"` Param map[string]interface{} `json:"param"` Timeout time.Duration ApiKeyInfo *APIKeyInfo isSimulate bool } type APIKeyInfo struct { ApiKey string PassPhrase string SecKey string UserId string } type RESTAPIResult struct { Url string `json:"url"` Param string `json:"param"` Header string `json:"header"` Code int `json:"code"` // 原始返回信息 Body string `json:"body"` // okexV5返回的数据 V5Response Okexv5APIResponse `json:"v5Response"` ReqUsedTime time.Duration `json:"reqUsedTime"` TotalUsedTime time.Duration `json:"totalUsedTime"` } // ResponseData 定义统一的响应数据接口 type ResponseData interface { GetCode() string GetMsg() string GetData() interface{} } type Okexv5APIResponse struct { Code string `json:"code"` Msg string `json:"msg"` Data interface{} `json:"data"` } // 实现 ResponseData 接口 func (r Okexv5APIResponse) GetCode() string { return r.Code } func (r Okexv5APIResponse) GetMsg() string { return r.Msg } func (r Okexv5APIResponse) GetData() interface{} { return r.Data } type CandleDataResponse struct { Code string `json:"code"` Msg string `json:"msg"` Data [][]interface{} `json:"data"` // 用二维数组来接收 candles 数据 } // 实现 ResponseData 接口 func (r CandleDataResponse) GetCode() string { return r.Code } func (r CandleDataResponse) GetMsg() string { return r.Msg } func (r CandleDataResponse) GetData() interface{} { return r.Data } /* endPoint:请求地址 apiKey isSimulate: 是否为模拟环境 */ func NewRESTClient(endPoint string, apiKey *APIKeyInfo, isSimulate bool) *RESTAPI { res := &RESTAPI{ EndPoint: endPoint, ApiKeyInfo: apiKey, isSimulate: isSimulate, Timeout: 5 * time.Second, } return res } func NewRESTAPI(ep, method, uri string, param *map[string]interface{}) *RESTAPI { //TODO:参数校验 reqParam := make(map[string]interface{}) if param != nil { reqParam = *param } res := &RESTAPI{ EndPoint: ep, Method: method, Uri: uri, Param: reqParam, Timeout: 150 * time.Second, } return res } func (this *RESTAPI) SetSimulate(b bool) *RESTAPI { this.isSimulate = b return this } func (this *RESTAPI) SetAPIKey(apiKey, secKey, passPhrase string) *RESTAPI { if this.ApiKeyInfo == nil { this.ApiKeyInfo = &APIKeyInfo{ ApiKey: apiKey, PassPhrase: passPhrase, SecKey: secKey, } } else { this.ApiKeyInfo.ApiKey = apiKey this.ApiKeyInfo.PassPhrase = passPhrase this.ApiKeyInfo.SecKey = secKey } return this } func (this *RESTAPI) SetUserId(userId string) *RESTAPI { if this.ApiKeyInfo == nil { fmt.Println("ApiKey为空") return this } this.ApiKeyInfo.UserId = userId return this } func (this *RESTAPI) SetTimeOut(timeout time.Duration) *RESTAPI { this.Timeout = timeout return this } // GetCandles 获取蜡烛图数据 func (this *RESTAPI) GetCandles(ctx context.Context, uri string, param *map[string]interface{}) (res ResponseData, err error) { this.Method = GET this.Uri = uri reqParam := make(map[string]interface{}) if param != nil { reqParam = *param } this.Param = reqParam return this.Run(ctx) } // POST请求 func (this *RESTAPI) Post(ctx context.Context, uri string, param *map[string]interface{}) (res ResponseData, err error) { this.Method = POST this.Uri = uri reqParam := make(map[string]interface{}) if param != nil { reqParam = *param } this.Param = reqParam return this.Run(ctx) } func (this *RESTAPI) Run(ctx context.Context) (ResponseData, error) { var res *RESTAPIResult var err error if this.ApiKeyInfo == nil { err = errors.New("APIKey不可为空") if err != nil { return nil, err } return res.V5Response, nil } procStart := time.Now() defer func() { if res != nil { res.TotalUsedTime = time.Since(procStart) } }() client := &http.Client{ Timeout: this.Timeout, } uri, body, err := this.GenReqInfo() if err != nil { return nil, err } url := this.EndPoint + uri bodyBuf := new(bytes.Buffer) bodyBuf.ReadFrom(strings.NewReader(body)) req, err := http.NewRequest(this.Method, url, bodyBuf) if err != nil { return nil, err } res = &RESTAPIResult{ Url: url, Param: body, } // Sign and set request headers timestamp := utils.IsoTime() preHash := utils.PreHashString(timestamp, this.Method, uri, body) //log.Println("preHash:", preHash) sign, err := utils.HmacSha256Base64Signer(preHash, this.ApiKeyInfo.SecKey) if err != nil { return nil, err } //log.Println("sign:", sign) headStr := this.SetHeaders(req, timestamp, sign) res.Header = headStr this.PrintRequest(req, body, preHash) resp, err := client.Do(req) if err != nil { fmt.Println("请求失败!", err) return nil, err } defer resp.Body.Close() res.ReqUsedTime = time.Since(procStart) resBuff, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("获取请求结果失败!", err) return nil, err } res.Body = string(resBuff) res.Code = resp.StatusCode // 解析结果 var v5rsp Okexv5APIResponse err = json.Unmarshal(resBuff, &v5rsp) if err != nil { fmt.Println("解析v5返回失败!", err) return nil, err } res.V5Response = v5rsp return nil, err } /* 生成请求对应的参数 */ func (this *RESTAPI) GenReqInfo() (uri string, body string, err error) { uri = this.Uri switch this.Method { case GET: getParam := []string{} if len(this.Param) == 0 { return } for k, v := range this.Param { getParam = append(getParam, fmt.Sprintf("%v=%v", k, v)) } uri = uri + "?" + strings.Join(getParam, "&") case POST: var rawBody []byte rawBody, err = json.Marshal(this.Param) if err != nil { return } body = string(rawBody) default: err = errors.New("request type unknown!") return } return } /* Set http request headers: Accept: application/json Content-Type: application/json; charset=UTF-8 (default) Cookie: locale=en_US (English) OK-ACCESS-KEY: (Your setting) OK-ACCESS-SIGN: (Use your setting, auto sign and add) OK-ACCESS-TIMESTAMP: (Auto add) OK-ACCESS-PASSPHRASE: Your setting */ func (this *RESTAPI) SetHeaders(request *http.Request, timestamp string, sign string) (header string) { request.Header.Add(ACCEPT, APPLICATION_JSON) header += ACCEPT + ":" + APPLICATION_JSON + "\n" request.Header.Add(CONTENT_TYPE, APPLICATION_JSON_UTF8) header += CONTENT_TYPE + ":" + APPLICATION_JSON_UTF8 + "\n" request.Header.Add(COOKIE, LOCALE+ENGLISH) header += COOKIE + ":" + LOCALE + ENGLISH + "\n" request.Header.Add(OK_ACCESS_KEY, this.ApiKeyInfo.ApiKey) header += OK_ACCESS_KEY + ":" + this.ApiKeyInfo.ApiKey + "\n" request.Header.Add(OK_ACCESS_SIGN, sign) header += OK_ACCESS_SIGN + ":" + sign + "\n" request.Header.Add(OK_ACCESS_TIMESTAMP, timestamp) header += OK_ACCESS_TIMESTAMP + ":" + timestamp + "\n" request.Header.Add(OK_ACCESS_PASSPHRASE, this.ApiKeyInfo.PassPhrase) header += OK_ACCESS_PASSPHRASE + ":" + this.ApiKeyInfo.PassPhrase + "\n" //模拟盘交易标记 if this.isSimulate { request.Header.Add(X_SIMULATE_TRADING, "1") header += X_SIMULATE_TRADING + ":1" + "\n" } return } /* 打印header信息 */ func (this *RESTAPI) PrintRequest(request *http.Request, body string, preHash string) { if this.ApiKeyInfo.SecKey != "" { fmt.Println(" Secret-Key: " + this.ApiKeyInfo.SecKey) } fmt.Println(" Request(" + utils.IsoTime() + "):") fmt.Println("\tUrl: " + request.URL.String()) fmt.Println("\tMethod: " + strings.ToUpper(request.Method)) if len(request.Header) > 0 { fmt.Println("\tHeaders: ") for k, v := range request.Header { if strings.Contains(k, "Ok-") { k = strings.ToUpper(k) } fmt.Println("\t\t" + k + ": " + v[0]) } } fmt.Println("\tBody: " + body) if preHash != "" { fmt.Println(" PreHash: " + preHash) } }