package helper import ( "crypto/hmac" "crypto/sha256" "crypto/tls" "encoding/hex" "fmt" "io/ioutil" "net/http" "net/url" "reflect" "strconv" "strings" "time" "github.com/shopspring/decimal" ) type BinanceClient struct { APIKey string APISecret string HTTPClient *http.Client SpotBaseURL string //现货api地址 FutBaseURL string //合约api地址 } // NewBinanceClient creates a new Binance client func NewBinanceClient(apiKey, apiSecret string, proxyType, proxyAddr string) (*BinanceClient, error) { // Create HTTP client with transport settings client := &http.Client{ Transport: &http.Transport{ MaxIdleConns: 1000, IdleConnTimeout: 10 * time.Second, // 设置超时 10 秒 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } err := CreateHtppProxy(proxyType, proxyAddr, client) if err != nil { return nil, err } return &BinanceClient{ APIKey: apiKey, APISecret: apiSecret, HTTPClient: client, SpotBaseURL: "https://api.binance.com", FutBaseURL: "https://fapi.binance.com", }, nil } // Helper to get proxy password from URL func getProxyPassword(proxyURL *url.URL) string { if password, ok := proxyURL.User.Password(); ok { return password } return "" } // signRequest generates HMAC SHA256 signature func (bc *BinanceClient) signRequest(query string) string { mac := hmac.New(sha256.New, []byte(bc.APISecret)) mac.Write([]byte(query)) return hex.EncodeToString(mac.Sum(nil)) } /* binance 现货请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendSpotRequest(endpoint string, method string, params map[string]string) ([]byte, int, error) { // Prepare URL reqURL := bc.SpotBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 0) } /* binance 现货请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendSpotRequestAuth(endpoint string, method string, params map[string]string) ([]byte, int, error) { // Prepare URL reqURL := bc.SpotBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 2) } /* binance 现货请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendSpotAuth(endpoint string, method string, params interface{}) ([]byte, int, error) { // Prepare URL reqURL := bc.SpotBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 2) } /* binance 现货请求 只需要api key - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendSpotRequestByKey(endpoint string, method string, params interface{}) ([]byte, int, error) { // Prepare URL reqURL := bc.SpotBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 1) } /* binance 合约请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendFuturesRequest(endpoint string, method string, params map[string]string) ([]byte, int, error) { // Prepare URL reqURL := bc.FutBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 0) } /* binance 合约请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendFuturesRequestAuth(endpoint string, method string, params map[string]string) ([]byte, int, error) { // Prepare URL reqURL := bc.FutBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 2) } /* binance 合约请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendFuturesRequestByKey(endpoint string, method string, params map[string]string) ([]byte, int, error) { // Prepare URL reqURL := bc.FutBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 1) } /* binance 合约请求 - @endpoint 路由 - @method get、post - @params 参数map */ func (bc *BinanceClient) SendFuturesAuth(endpoint string, method string, params interface{}) ([]byte, int, error) { // Prepare URL reqURL := bc.FutBaseURL + endpoint return bc.SendRequest(reqURL, method, params, 2) } // SendRequest sends a request to Binance API /* 发送请求 - auth 0-不需要 1-只需要key 2-需要key和签名 */ func (bc *BinanceClient) SendRequest(reqURL string, method string, params interface{}, auth int) ([]byte, int, error) { method = strings.ToUpper(method) reqParams := url.Values{} // 处理 `params`,如果是 map[string]string 则添加为 URL 参数 if paramMap, ok := params.(map[string]string); ok { for k, v := range paramMap { reqParams.Add(k, v) } } else if v := reflect.ValueOf(params); v.Kind() == reflect.Struct { for i := 0; i < v.NumField(); i++ { field := v.Type().Field(i) value := v.Field(i) // 获取字段名或 JSON 标签名 key := field.Tag.Get("json") if key == "" { key = field.Name // 如果没有 json 标签,使用字段名 } // 检查字段类型并转换为对应的字符串 var strValue string switch value.Kind() { case reflect.String: strValue = value.String() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: strValue = strconv.FormatInt(value.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: strValue = strconv.FormatUint(value.Uint(), 10) case reflect.Float32, reflect.Float64: strValue = strconv.FormatFloat(value.Float(), 'f', -1, 64) case reflect.Bool: strValue = strconv.FormatBool(value.Bool()) case reflect.Struct: // 处理 decimal.Decimal 类型 if value.Type() == reflect.TypeOf(decimal.Decimal{}) { strValue = value.Interface().(decimal.Decimal).String() } else { continue // 跳过其他 struct 类型 } default: continue // 跳过不支持的类型 } // 添加到 reqParams reqParams.Add(key, strValue) } } // Add timestamp if signature is needed if auth == 2 && bc.APIKey != "" && bc.APISecret != "" { reqParams.Add("timestamp", fmt.Sprintf("%d", time.Now().UnixMilli())) signature := bc.signRequest(reqParams.Encode()) reqParams.Add("signature", signature) } // Create HTTP request var req *http.Request var err error if method == http.MethodGet || method == http.MethodDelete { if len(reqParams) > 0 { reqURL = fmt.Sprintf("%s?%s", reqURL, reqParams.Encode()) } req, err = http.NewRequest(method, reqURL, nil) } else { // req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if len(reqParams) > 0 { reqURL = fmt.Sprintf("%s?%s", reqURL, reqParams.Encode()) } req, err = http.NewRequest(method, reqURL, nil) } if err != nil { return nil, -1, fmt.Errorf("failed to create request: %w", err) } // Set headers if auth > 0 && bc.APIKey != "" { req.Header.Set("X-MBX-APIKEY", bc.APIKey) } // req.Header.Set("Content-Type", "application/x-www-form-urlencoded") // Send request resp, err := bc.HTTPClient.Do(req) if err != nil { return nil, -1, fmt.Errorf("failed to send request: %w", err) } defer resp.Body.Close() // Read response body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, -1, fmt.Errorf("failed to read response: %w", err) } if resp.StatusCode != http.StatusOK { return nil, resp.StatusCode, fmt.Errorf("%s", body) } return body, http.StatusOK, nil }