package bitgetservice import ( "fmt" "go-admin/common/global" "go-admin/common/helper" "go-admin/config" "go-admin/models" "go-admin/pkg/utility" "strconv" "strings" "github.com/bytedance/sonic" log "github.com/go-admin-team/go-admin-core/logger" ) // BitgetRestApi Bitget REST API服务 type BitgetRestApi struct { httpClient *helper.BitgetClient } // NewBitgetRestApi 创建新的Bitget REST API实例 func NewBitgetRestApi() *BitgetRestApi { // 从配置中获取代理设置 proxyUrl := config.ExtConfig.ProxyUrl // 使用代理配置创建HTTP客户端 httpClient, err := helper.NewBitgetClient("", "", "", proxyUrl) if err != nil { log.Error("创建Bitget HTTP客户端失败", err) // 如果代理创建失败,使用无代理的客户端 httpClient, _ = helper.NewBitgetClient("", "", "", "") } return &BitgetRestApi{ httpClient: httpClient, } } // NewBitgetSimpleClient 创建简单的Bitget客户端(兼容性函数) func NewBitgetSimpleClient() *BitgetClient { // 从配置中获取代理设置 proxyUrl := config.ExtConfig.ProxyUrl client, err := NewBitgetClient("", "", "", proxyUrl) if err != nil { log.Error("创建Bitget简单客户端失败", err) // 如果代理创建失败,使用无代理的客户端 client, _ = NewBitgetClient("", "", "", "") } return client } // bitgetSymbolResponse 通用的符号响应结构体 type bitgetSymbolResponse struct { Code string `json:"code"` Msg string `json:"msg"` Data []struct { Symbol string `json:"symbol"` BaseCoin string `json:"baseCoin"` QuoteCoin string `json:"quoteCoin"` Status string `json:"status"` MinTradeAmount string `json:"minTradeAmount"` MaxTradeAmount string `json:"maxTradeAmount"` TakerFeeRate string `json:"takerFeeRate"` MakerFeeRate string `json:"makerFeeRate"` PricePrecision string `json:"pricePrecision"` QuantityPrecision string `json:"quantityPrecision"` } `json:"data"` } // getSymbolsFromAPI 通用的符号获取方法 // symbolType: "spot" 现货, "futures" 合约 func (api *BitgetRestApi) getSymbolsFromAPI(symbolType string) ([]BitgetSymbol, error) { var url string var errorPrefix string switch symbolType { case "spot": url = "/api/v2/spot/public/symbols" errorPrefix = "现货" case "futures": url = "/api/v2/mix/market/contracts?productType=USDT-FUTURES" errorPrefix = "合约" default: return nil, fmt.Errorf("不支持的符号类型: %s", symbolType) } // 发送API请求 resp, _, err := api.httpClient.SendSpotRequestAuth(url, "GET", map[string]interface{}{}) if err != nil { return nil, fmt.Errorf("获取bitget %s交易所信息失败: %v", errorPrefix, err) } // 解析响应数据 var response bitgetSymbolResponse if err := sonic.Unmarshal(resp, &response); err != nil { return nil, fmt.Errorf("解析bitget %s交易所信息失败: %v", errorPrefix, err) } // 检查API响应状态 if response.Code != "00000" { return nil, fmt.Errorf("bitget API错误: %s", response.Msg) } // 转换数据格式 return api.convertToSymbols(response.Data), nil } // convertToSymbols 将API响应数据转换为BitgetSymbol切片 func (api *BitgetRestApi) convertToSymbols(data []struct { Symbol string `json:"symbol"` BaseCoin string `json:"baseCoin"` QuoteCoin string `json:"quoteCoin"` Status string `json:"status"` MinTradeAmount string `json:"minTradeAmount"` MaxTradeAmount string `json:"maxTradeAmount"` TakerFeeRate string `json:"takerFeeRate"` MakerFeeRate string `json:"makerFeeRate"` PricePrecision string `json:"pricePrecision"` QuantityPrecision string `json:"quantityPrecision"` }) []BitgetSymbol { symbols := make([]BitgetSymbol, 0, len(data)) for _, item := range data { if item.Status == "online" { priceDigit, _ := strconv.Atoi(item.PricePrecision) quantileDigit, _ := strconv.Atoi(item.QuantityPrecision) symbol := BitgetSymbol{ Symbol: item.Symbol, BaseAsset: item.BaseCoin, QuoteAsset: item.QuoteCoin, Status: item.Status, MinQty: item.MinTradeAmount, MaxQty: item.MaxTradeAmount, PriceDigit: priceDigit, QuantityDigit: quantileDigit, } symbols = append(symbols, symbol) } } return symbols } // GetSpotSymbols 获取现货交易对信息 func (api *BitgetRestApi) GetSpotSymbols() ([]BitgetSymbol, error) { return api.getSymbolsFromAPI("spot") } // GetFutSymbols 获取合约交易对信息 func (api *BitgetRestApi) GetFutSymbols() ([]BitgetSymbol, error) { return api.getSymbolsFromAPI("futures") } // symbolConfig 符号配置结构体 type symbolConfig struct { symbolType string quoteAssetSymbols []string } // buildTradeSetFromSymbols 通用的TradeSet构建方法 func buildTradeSetFromSymbols(symbols []BitgetSymbol, config symbolConfig) (map[string]models.TradeSet, []string) { tradeSets := make(map[string]models.TradeSet, len(symbols)) deleteSymbols := make([]string, 0) for _, item := range symbols { if utility.ContainsStr(config.quoteAssetSymbols, item.QuoteAsset) && item.Status == "online" { tradeSet := models.TradeSet{ Coin: item.BaseAsset, Currency: item.QuoteAsset, PriceDigit: item.PriceDigit, AmountDigit: item.QuantityDigit, MinQty: utility.StringAsFloat(item.MinQty), } tradeSets[item.Symbol] = tradeSet } } return tradeSets, deleteSymbols } // GetSpotSymbols 获取现货交易对并构建TradeSet映射 func GetSpotSymbols() (map[string]models.TradeSet, []string, error) { api := NewBitgetRestApi() symbols, err := api.GetSpotSymbols() if err != nil { log.Error("获取bitget规范信息失败", err) return make(map[string]models.TradeSet, 0), []string{}, err } config := symbolConfig{ symbolType: "spot", quoteAssetSymbols: []string{"USDT", "ETH", "BTC", "SOL", "BNB", "DOGE"}, } tradeSets, deleteSymbols := buildTradeSetFromSymbols(symbols, config) return tradeSets, deleteSymbols, nil } // GetFutSymbols 获取合约交易对并构建TradeSet映射 func GetFutSymbols() (map[string]models.TradeSet, []string, error) { api := NewBitgetRestApi() symbols, err := api.GetFutSymbols() if err != nil { log.Error("获取bitget合约规范信息失败", err) return make(map[string]models.TradeSet, 0), []string{}, err } config := symbolConfig{ symbolType: "futures", quoteAssetSymbols: []string{"USDT"}, } tradeSets, deleteSymbols := buildTradeSetFromSymbols(symbols, config) return tradeSets, deleteSymbols, nil } // getTicker24hData 获取24小时行情数据的通用方法 func (api *BitgetRestApi) getTicker24hData(url, tickerType, description string, tradeSet *map[string]models.TradeSet) ([]string, error) { // 定义响应数据结构 type tickerResponse struct { Code string `json:"code"` Msg string `json:"msg"` Data []struct { Symbol string `json:"symbol"` High24h string `json:"high24h"` Low24h string `json:"low24h"` Close string `json:"lastPr"` Open string `json:"open"` Volume24h string `json:"baseVolume"` // 基础币成交量 QuoteVol string `json:"usdtVolume"` // USDT成交量 Change24h string `json:"changeUtc24h"` // UTC 24h涨跌幅 Change string `json:"change24h"` // 24h涨跌幅 } `json:"data"` } // 发送API请求 resp, _, err := api.httpClient.SendSpotRequestAuth(url, "GET", map[string]interface{}{}) if err != nil { return []string{}, fmt.Errorf("获取bitget %s 24h行情失败: %v", description, err) } // 解析响应数据 var response tickerResponse if err := sonic.Unmarshal(resp, &response); err != nil { return []string{}, fmt.Errorf("解析bitget %s 24h行情失败: %v", description, err) } // 检查API响应状态 if response.Code != "00000" { return []string{}, fmt.Errorf("bitget %s API错误: %s", description, response.Msg) } // 处理行情数据 deleteSymbols := make([]string, 0) processedCount := 0 errorCount := 0 for _, item := range response.Data { // 构建缓存键 key := fmt.Sprintf(tickerType, global.EXCHANGE_BITGET, item.Symbol) // 检查交易对是否存在 symbol, exists := (*tradeSet)[item.Symbol] if !exists { // 清理不存在的交易对缓存 if err := helper.DefaultRedis.DeleteString(key); err != nil { log.Warn("删除无效缓存失败: %s, error: %v", key, err) } continue } // 解析交易对获取coin和currency var coin, currency string if strings.HasSuffix(item.Symbol, symbol.Currency) { coin = strings.TrimSuffix(item.Symbol, symbol.Currency) currency = symbol.Currency } else { deleteSymbols = append(deleteSymbols, item.Symbol) continue } // 构建交易对数据 symbol.Coin = coin symbol.Currency = currency symbol.OpenPrice = utility.StringAsFloat(item.Open) symbol.PriceChange = utility.StringAsFloat(item.Change) symbol.LowPrice = item.Low24h symbol.HighPrice = item.High24h symbol.Volume = item.Volume24h symbol.QuoteVolume = item.QuoteVol symbol.LastPrice = item.Close // 验证数据有效性 if symbol.Coin == "" || symbol.Currency == "" || utility.StringToFloat64(symbol.QuoteVolume) <= 0 || symbol.LastPrice == "" || utility.StringToFloat64(symbol.LastPrice) <= 0 { if err := helper.DefaultRedis.DeleteString(key); err != nil { log.Warn("删除无效数据缓存失败: %s, error: %v", key, err) } deleteSymbols = append(deleteSymbols, item.Symbol) continue } // 缓存数据 val, err := sonic.Marshal(symbol) if err != nil { log.Error("序列化交易对数据失败: %s, error: %v", item.Symbol, err) errorCount++ continue } if err := helper.DefaultRedis.SetString(key, string(val)); err != nil { log.Error("缓存交易对失败: %s, error: %v", item.Symbol, err) errorCount++ continue } processedCount++ } log.Info("处理%s行情数据完成: 成功=%d, 错误=%d, 删除=%d", description, processedCount, errorCount, len(deleteSymbols)) return deleteSymbols, nil } // GetSpotTicker24h 获取现货24小时行情变更并缓存 func (api *BitgetRestApi) GetSpotTicker24h(tradeSet *map[string]models.TradeSet) ([]string, error) { return api.getTicker24hData("/api/v2/spot/market/tickers", global.TICKER_SPOT, "现货", tradeSet) } // GetFutTicker24h 获取U本位合约24小时行情变更并缓存 func (api *BitgetRestApi) GetFutTicker24h(tradeSet *map[string]models.TradeSet) ([]string, error) { return api.getTicker24hData("/api/v2/mix/market/tickers?productType=USDT-FUTURES", global.TICKER_FUTURES, "U本位合约", tradeSet) } // BitgetSymbol Bitget交易对信息 type BitgetSymbol struct { Symbol string `json:"symbol"` BaseCoin string `json:"baseCoin"` QuoteCoin string `json:"quoteCoin"` BaseAsset string `json:"baseAsset"` QuoteAsset string `json:"quoteAsset"` Status string `json:"status"` MinQty string `json:"minQty"` MaxQty string `json:"maxQty"` PriceDigit int `json:"priceDigit"` QuantityDigit int `json:"quantityDigit"` } // GetAndReloadSymbols 获取并重新加载交易对信息 // symbolType 1-现货 2-合约 func GetAndReloadSymbols(data *map[string]models.TradeSet, symbolType int) error { var tradeSets map[string]models.TradeSet var err error switch symbolType { case 1: tradeSets, _, err = GetSpotSymbols() case 2: tradeSets, _, err = GetFutSymbols() default: return fmt.Errorf("无效的symbolType: %d", symbolType) } if err != nil { return err } *data = tradeSets return nil } // InitSymbolsTicker24h 初始化24小时行情数据 // symbolType 1-现货 2-合约 func InitSymbolsTicker24h(data *map[string]models.TradeSet, symbolType int) ([]string, error) { api := NewBitgetRestApi() switch symbolType { case 1: return api.GetSpotTicker24h(data) case 2: return api.GetFutTicker24h(data) default: return []string{}, fmt.Errorf("无效的symbolType: %d", symbolType) } } // GetSpotTicker24 获取单个交易对24h行情 func (api *BitgetRestApi) GetSpotTicker24(symbol string, data *models.Ticker24, tradeSet *models.TradeSet) error { key := fmt.Sprintf(global.TICKER_SPOT, global.EXCHANGE_BITGET, symbol) val, err := helper.DefaultRedis.GetString(key) if err != nil { return err } err = sonic.Unmarshal([]byte(val), tradeSet) if err != nil { return err } if tradeSet.Coin != "" { data.HighPrice = tradeSet.HighPrice data.ChangePercent = fmt.Sprintf("%g", tradeSet.PriceChange) data.LastPrice = tradeSet.LastPrice data.LowPrice = tradeSet.LowPrice data.OpenPrice = fmt.Sprintf("%g", tradeSet.OpenPrice) data.QuoteVolume = tradeSet.QuoteVolume data.Volume = tradeSet.Volume } return nil }