Files
exchange_go/services/bitgetservice/bitget_rest.go
2025-10-14 19:58:59 +08:00

418 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}