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
							 | 
						||
| 
								 | 
							
								}
							 |