283 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			283 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						|
}
 |