package excservice import ( "errors" "fmt" "strconv" "sync" "time" "go-admin/models" "go-admin/pkg/timehelper" "go-admin/pkg/utility" "github.com/bytedance/sonic" ) type BinanceWs struct { baseURL string combinedBaseURL string proxyUrl string WorkType string wsConns []*WsConn mu sync.Mutex // 新增互斥锁 tickerCallback func(models.Ticker24, string, string) forceCallback func(models.ForceOrder, string, string) depthCallback func(models.DepthBin, string, string) tradeCallback func(models.NewDealPush, string, string) klineCallback func(models.Kline, int, string, string) allBack func(msg []byte) allBackKline func(msg []byte, tradeSet models.TradeSet) } func NewBinanceWs(wsbaseURL, proxyUrl string) *BinanceWs { return &BinanceWs{ baseURL: wsbaseURL, combinedBaseURL: "wss://stream.binance.com:9443/stream?streams=", proxyUrl: proxyUrl, } } func (bnWs *BinanceWs) SetProxyUrl(proxyUrl string) { bnWs.proxyUrl = proxyUrl } func (bnWs *BinanceWs) SetBaseUrl(baseURL string) { bnWs.baseURL = baseURL } func (bnWs *BinanceWs) SetCombinedBaseURL(combinedBaseURL string) { bnWs.combinedBaseURL = combinedBaseURL } func (bnWs *BinanceWs) SetAllCallbacks(allBack func(msg []byte), allBackKline func(msg []byte, tradeSet models.TradeSet)) { if bnWs.allBack == nil { bnWs.allBack = allBack } if bnWs.allBackKline == nil { bnWs.allBackKline = allBackKline } } // 订阅通用函数 func (bnWs *BinanceWs) subscribe(endpoint string, handle func(msg []byte) error) { wsConn := NewWsBuilder(). WsUrl(endpoint). AutoReconnect(). ProtoHandleFunc(handle). ProxyUrl(bnWs.proxyUrl). ReconnectInterval(time.Millisecond * 5). Build() if wsConn == nil { return } bnWs.mu.Lock() bnWs.wsConns = append(bnWs.wsConns, wsConn) bnWs.mu.Unlock() go bnWs.exitHandler(wsConn) } func (bnWs *BinanceWs) Close() { bnWs.mu.Lock() defer bnWs.mu.Unlock() for _, con := range bnWs.wsConns { con.CloseWs() } } func (bnWs *BinanceWs) Subscribe(streamName string, tradeSet models.TradeSet, callback func(msg []byte, tradeSet models.TradeSet)) error { endpoint := bnWs.baseURL + streamName handle := func(msg []byte) error { callback(msg, tradeSet) return nil } bnWs.subscribe(endpoint, handle) return nil } func (bnWs *BinanceWs) exitHandler(c *WsConn) { pingTicker := time.NewTicker(1 * time.Minute) pongTicker := time.NewTicker(30 * time.Second) defer func() { pingTicker.Stop() pongTicker.Stop() c.CloseWs() if err := recover(); err != nil { fmt.Printf("CloseWs, panic: %s\r\n", err) } }() for { select { case t := <-pingTicker.C: c.SendPingMessage([]byte(strconv.Itoa(int(t.UnixNano() / int64(time.Millisecond))))) case t := <-pongTicker.C: c.SendPongMessage([]byte(strconv.Itoa(int(t.UnixNano() / int64(time.Millisecond))))) } } } func parseJsonToMap(msg []byte) (map[string]interface{}, error) { datamap := make(map[string]interface{}) err := sonic.Unmarshal(msg, &datamap) return datamap, err } func handleForceOrder(msg []byte, tradeSet models.TradeSet, callback func(models.ForceOrder, string, string)) error { datamap, err := parseJsonToMap(msg) if err != nil { return fmt.Errorf("json unmarshal error: %v", err) } msgType, ok := datamap["e"].(string) if !ok || msgType != "forceOrder" { return errors.New("unknown message type") } datamapo := datamap["o"].(map[string]interface{}) order := models.ForceOrder{ Side: datamapo["S"].(string), Symbol: datamapo["s"].(string), Ordertype: datamapo["o"].(string), TimeInForce: datamapo["f"].(string), Num: utility.ToFloat64(datamapo["q"]), Price: utility.ToFloat64(datamapo["p"]), AvgPrice: utility.ToFloat64(datamapo["ap"]), State: datamapo["X"].(string), CreateTime: timehelper.IntToTime(utility.ToInt64(datamapo["T"])), } callback(order, tradeSet.Coin, tradeSet.Currency) return nil } // SubscribeAll 订阅 组合streams的URL格式为 /stream?streams=// // 订阅组合streams时,事件payload会以这样的格式封装: {"stream":"","data":} // 单一原始 streams 格式为 /ws/ func (bnWs *BinanceWs) SubscribeAll(streamName string) error { endpoint := bnWs.baseURL + streamName handle := func(msg []byte) error { bnWs.allBack(msg) return nil } bnWs.subscribe(endpoint, handle) return nil } // SubscribeAllKline 订阅kline推送 组合streams的URL格式为 /stream?streams=// func (bnWs *BinanceWs) SubscribeAllKline(streamName string, tradeSet models.TradeSet) error { endpoint := bnWs.baseURL + streamName handle := func(msg []byte) error { bnWs.allBackKline(msg, tradeSet) return nil } bnWs.subscribe(endpoint, handle) return nil }