Compare commits

...

4 Commits

  1. 1
      .gitignore
  2. 16
      README.md
  3. 30
      api/exchange.proto
  4. 2
      api/indicator.proto
  5. 8
      api/market.proto
  6. 14
      api/pub.proto
  7. 65
      cmd/exchange/main.go
  8. 4
      cmd/indicator/main.go
  9. 30
      cmd/market/main.go
  10. 22
      cmd/sig-admin/main.go
  11. 6
      config/config.toml
  12. 3
      config/exchange.toml
  13. 1
      config/market.toml
  14. 14
      docker-compose.yml
  15. 21
      go.mod
  16. 172
      go.sum
  17. 27
      internal/exchange/exchange.go
  18. 50
      internal/exchange/exchange_data_persist.go
  19. 34
      internal/exchange/exchange_data_service.go
  20. 409
      internal/exchange/exchange_grpc_server.go
  21. 478
      internal/exchange/exchange_service.go
  22. 3
      internal/exchange/okx/channel_kline.go
  23. 5
      internal/exchange/okx/okx_fetch.go
  24. 5
      internal/exchange/okx/okx_subscriber.go
  25. 17
      internal/indicator/indicator.go
  26. 2
      internal/market/market_grpc_server.go
  27. 2
      internal/market/trade_instance_service.go
  28. 8
      internal/market/validation.go
  29. 11
      internal/sig/sig_server.go
  30. 20
      pkg/aside/trade_instance_client.go
  31. 6
      pkg/config/config.go
  32. 9
      pkg/data/common.go
  33. 173
      pkg/grpc/discovery/consul/resolver.go
  34. 76
      pkg/grpc/discovery/consul_naming.go
  35. 1
      pkg/grpc/discovery/discovery.go
  36. 4
      pkg/grpc/discovery/etcd_naming.go
  37. 23
      pkg/grpc/discovery/health_server.go
  38. 44
      pkg/indicator/rsi.go
  39. 4
      pkg/mapping/market.go
  40. 12
      pkg/storage/tsdb/victoria_metrics/metric.go
  41. 2
      pkg/storage/tsdb/victoria_metrics/vm.go
  42. 3
      pkg/storage/tsdb/victoria_metrics/vm_test.go
  43. 36
      pkg/types/exchange.go
  44. 4
      pkg/types/indicator.go
  45. 4
      pkg/types/instance.go
  46. 95
      pkg/types/interval.go
  47. 4
      pkg/types/kline.go
  48. 33
      pkg/types/series/decimals.go
  49. 26
      pkg/types/series/floats.go
  50. 10
      pkg/types/series/series.go
  51. 27
      pkg/utils/collect/collect.go
  52. 14
      pkg/utils/nets/ip_test.go
  53. 6
      pkg/zlog/exported.go

1
.gitignore vendored

@ -27,3 +27,4 @@ Thumbs.db
/api/pb /api/pb
/fs /fs
/run/*

16
README.md

@ -48,3 +48,19 @@ k线推送
价格/交易量精度->tsdb读写存储 价格/交易量精度->tsdb读写存储
kline时间窗口 kline时间窗口
tradingview advanced-charts:
- https://cn.tradingview.com/advanced-charts/
tradingview lightweight-charts:
- https://tradingview.github.io/lightweight-charts/
- https://github.com/tradingview/lightweight-charts
d3js:
- https://d3js.org/
DXcharts:
- https://devexperts.com/dxcharts/
- https://github.com/devexperts/dxcharts-lite
- https://financefeeds.com/zh-CN/dxcharts-%E5%9B%BE%E8%A1%A8%E7%9A%84%E5%B4%9B%E8%B5%B7%E6%88%90%E4%B8%BA%E4%BA%A4%E6%98%93%E5%B9%B3%E5%8F%B0%E9%87%91%E8%9E%8D%E7%A7%91%E6%8A%80%E7%BD%91%E7%AB%99%E7%9A%84%E7%AB%9E%E4%BA%89%E4%BC%98%E5%8A%BF/
golang-clash:
- https://hub.docker.com/r/dreamacro/clash

30
api/exchange.proto

@ -6,22 +6,42 @@ option go_package = "./pb";
// //,k线图// // //,k线图//
service ExchangeService { service ExchangeService {
rpc SubscribeKline(stream ReqStreamSubscribeKline) // K线
returns (stream RspStreamSubscribeKline); rpc SubscribeKline(stream ReqStreamSubscribeKline) returns (stream RspStreamSubscribeKline);
//
rpc Exchanges(ReqExchanges) returns (RspExchanges);
//
rpc ExchangeInstanceState(ReqExchangeInstanceState) returns (RspExchangeInstanceState);
} }
message ReqStreamSubscribeKline { message ReqStreamSubscribeKline {
SubscribeType subType = 1; SubscribeType subType = 1;
repeated Exchange exchanges = 2; // repeated ExchangeType exchanges = 2; //
repeated string instIds = 3; repeated string instIds = 3;
repeated string intervals = 4; repeated string intervals = 4;
bool onlyConfirm = 5; // confirm的k线 bool onlyConfirm = 5; // confirm的k线
} }
message RspStreamSubscribeKline { StreamKline kline = 1; } message RspStreamSubscribeKline { StreamKline kline = 1; }
message StreamKline { message StreamKline {
repeated Kline klines = 2; repeated Kline klines = 2;
Exchange exchange = 3; // ExchangeType exchange = 3; //
string instId = 4; // id string instId = 4; // id
} }
message ReqExchanges {
}
message RspExchanges {
repeated ExchangeType exchanges = 1;
}
message ReqExchangeInstanceState {
repeated string insts = 1;
bool allExchange = 2;
repeated ExchangeType exchanges = 3; //
}
message RspExchangeInstanceState {
repeated TradeInstanceState instsState = 1; //
}

2
api/indicator.proto

@ -16,7 +16,7 @@ message IndicatorSubReq {
} }
message Indicator { message Indicator {
Exchange exhcange = 1; ExchangeType exhcange = 1;
string instId = 2; string instId = 2;
string indicator = 3; string indicator = 3;
string sub = 4; // , MA5, MA10, MA20 string sub = 4; // , MA5, MA10, MA20

8
api/market.proto

@ -5,7 +5,7 @@ import "api/pub.proto";
option go_package = "./pb"; option go_package = "./pb";
// //
service Market { service MarketService {
rpc GetTradeInstance(ReqGetTradeInstance) returns (RspGetTradeInstance); rpc GetTradeInstance(ReqGetTradeInstance) returns (RspGetTradeInstance);
rpc AddTradeInstance(ReqAddTradeInstance) returns (RspAddTradeInstance); rpc AddTradeInstance(ReqAddTradeInstance) returns (RspAddTradeInstance);
// rpc UpdateTradeInstance(ReqUpdateTradeInstance) returns (RspUpdateTradeInstance); // rpc UpdateTradeInstance(ReqUpdateTradeInstance) returns (RspUpdateTradeInstance);
@ -46,18 +46,18 @@ message RspStatusTradeInstance {
message ReqListTradeInstance { message ReqListTradeInstance {
int32 page = 1; int32 page = 1;
int32 pageSize = 2; int32 pageSize = 2;
Exchange exchange = 3; // ExchangeType exchange = 3; //
string topic = 4; string topic = 4;
string instId = 5; string instId = 5;
} }
message RspListTradeInstance { message RspListTradeInstance {
repeated Kline klines = 1; repeated Kline klines = 1;
Exchange exchange = 2; // ExchangeType exchange = 2; //
string instId = 3; // id string instId = 3; // id
} }
message ReqListExchangeTradeInstance { message ReqListExchangeTradeInstance {
repeated Exchange exchanges = 1; repeated ExchangeType exchanges = 1;
} }
message RspListExchangeTradeInstance { message RspListExchangeTradeInstance {
repeated TradeInstanceExchange exchangeInsts = 1; repeated TradeInstanceExchange exchangeInsts = 1;

14
api/pub.proto

@ -8,7 +8,7 @@ enum SubscribeType {
UnsubscribeAll = 2; UnsubscribeAll = 2;
} }
enum Exchange { enum ExchangeType {
SIG = 0; SIG = 0;
OKX = 1; OKX = 1;
BINANCE = 2; BINANCE = 2;
@ -79,11 +79,19 @@ message TradeInstanceExchange {
string exchangeInstId = 1; string exchangeInstId = 1;
string instId = 2; string instId = 2;
int32 status = 3; int32 status = 3;
int32 exchange = 4; ExchangeType exchange = 4;
string updateBy = 5; string updateBy = 5;
int64 updateTime = 6; int64 updateTime = 6;
} }
//
message TradeInstanceState {
ExchangeType exchange = 1;
string instId = 2;
int32 status = 3;
string last = 4; //
}
message Kline { message Kline {
int64 Ts = 2; int64 Ts = 2;
string interval = 3; // string interval = 3; //
@ -98,7 +106,7 @@ message Kline {
// https://maicoin.github.io/max-websocket-docs/#/private_channels?id=snapshot // https://maicoin.github.io/max-websocket-docs/#/private_channels?id=snapshot
message Order { message Order {
string exchange = 1; ExchangeType exchange = 1;
string symbol = 2; string symbol = 2;
string id = 3; string id = 3;
Side side = 4; Side side = 4;

65
cmd/exchange/main.go

@ -1,7 +1,6 @@
package main package main
import ( import (
"context"
"fmt" "fmt"
"net" "net"
"sig-pub/api/pb" "sig-pub/api/pb"
@ -16,7 +15,7 @@ import (
"sig-pub/pkg/utils/exit" "sig-pub/pkg/utils/exit"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
clientv3 "go.etcd.io/etcd/client/v3" "github.com/hashicorp/consul/api"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
@ -32,34 +31,26 @@ func main() {
conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml") conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml")
exchangeConf := config.MustLoadConfig(new(ExchangeConf), "config/exchange.toml") exchangeConf := config.MustLoadConfig(new(ExchangeConf), "config/exchange.toml")
// tsdb VictoriaMetrics // consul 配置
vmtsdb := vmts.NewVictoriaMetricsTSDB(conf.Tsdb.Victoriametrics) cc := api.DefaultConfig()
tsdbService := exchange.NewExchangeDataService(vmtsdb, nil) cc.Address = conf.Consul.Address
if err := tsdbService.Init(); err != nil { client, err := api.NewClient(cc)
panic(err)
}
etcdClient, err := clientv3.New(conf.Etcd)
if err != nil { if err != nil {
panic(err) panic(fmt.Errorf("consul client error: %v", err))
} }
exit.AddHook(func() { _ = etcdClient.Close() }, exit.WithOrderTail()) dis := discovery.NewConsulDiscovery(client)
resolver := dis.Resolver()
// get market grpc client // new market grpc client
dis := discovery.NewEtcdDiscovery(etcdClient) marketUrl := discovery.ConsulDialUrl(pb.MarketService_ServiceDesc.ServiceName)
resolver, err := dis.Resolver() marketConn, err := grpc.NewClient(marketUrl,
if err != nil {
panic(err)
}
url := fmt.Sprintf("%s:///%s", discovery.EtcdSchema, pb.Market_ServiceDesc.ServiceName)
conn, err := grpc.NewClient(url,
grpc.WithResolvers(resolver), grpc.WithResolvers(resolver),
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithTransportCredentials(insecure.NewCredentials()),
) )
if err != nil { if err != nil {
panic(err) panic(err)
} }
marketClient := pb.NewMarketClient(conn) marketClient := pb.NewMarketServiceClient(marketConn)
tradeInstanceAside := aside.NewTradeInstanceAside(marketClient) tradeInstanceAside := aside.NewTradeInstanceAside(marketClient)
// kvrocks db // kvrocks db
@ -76,12 +67,22 @@ func main() {
okxFetcher := okx.NewOkxFetcher(conf.Exchange.Okx.HttpProxy) okxFetcher := okx.NewOkxFetcher(conf.Exchange.Okx.HttpProxy)
okxExchange := exchange.NewExchange(okxFetcher, okxSubscriber) okxExchange := exchange.NewExchange(okxFetcher, okxSubscriber)
// exhcange main service // tsdb VictoriaMetrics
exchangeService := exchange.NewExchangeGrpcServer(tradeInstanceAside, tsdbService, kvdb, okxExchange) vmtsdb := vmts.NewVictoriaMetricsTSDB(conf.Tsdb.Victoriametrics)
if err := exchangeService.Init(); err != nil { tsdbService := exchange.NewExchangeDataService(vmtsdb, kvdb)
if err := tsdbService.Init(); err != nil {
panic(err) panic(err)
} }
// exchange main service
exchangeService := exchange.NewExchangeService(tradeInstanceAside, tsdbService, okxExchange)
if err := exchangeService.Init(); err != nil {
panic(err)
}
exchangeGrpcServer := exchange.NewExchangeGrpcServer(exchangeService)
if err := exchangeGrpcServer.Init(); err != nil {
panic(err)
}
grpcServer := grpc.NewServer(config.GetGrpcOptions( grpcServer := grpc.NewServer(config.GetGrpcOptions(
conf.Grpc, conf.Grpc,
grpc.UnaryInterceptor(interceptor.RecoverInterceptor))..., grpc.UnaryInterceptor(interceptor.RecoverInterceptor))...,
@ -90,22 +91,16 @@ func main() {
// 注册反射服务 // 注册反射服务
reflection.Register(grpcServer) reflection.Register(grpcServer)
} }
pb.RegisterExchangeServiceServer(grpcServer, exchangeGrpcServer)
pb.RegisterExchangeServiceServer(grpcServer, exchangeService) exit.AddHook(grpcServer.GracefulStop, exit.WithOrderFront())
registry := discovery.NewEtcdDiscovery(etcdClient)
// consul 服务注册
register := exchangeConf.Register register := exchangeConf.Register
if register.Name == "" { register.Name = pb.ExchangeService_ServiceDesc.ServiceName
register.Name = pb.Market_ServiceDesc.ServiceName if err := dis.Registry(grpcServer, register); err != nil {
}
ctx, cancel := context.WithCancel(context.Background())
exit.AddHook(cancel, exit.WithOrderFront())
if err := registry.Registry(ctx, register); err != nil {
panic(err) panic(err)
} }
exit.AddHook(grpcServer.GracefulStop, exit.WithOrderFront())
// run grpc server // run grpc server
go func() { go func() {

4
cmd/indicator/main.go

@ -81,7 +81,7 @@ func subscribeEvents(client pb.ExchangeServiceClient) {
instIds := []string{"BTC-USDT", "DOGE-USDT-SWAP"} instIds := []string{"BTC-USDT", "DOGE-USDT-SWAP"}
msg := &pb.ReqStreamSubscribeKline{ msg := &pb.ReqStreamSubscribeKline{
SubType: pb.SubscribeType_Subscribe, SubType: pb.SubscribeType_Subscribe,
Exchanges: []pb.Exchange{pb.Exchange_OKX}, Exchanges: []pb.ExchangeType{pb.ExchangeType_OKX},
InstIds: instIds, InstIds: instIds,
Intervals: []string{ Intervals: []string{
string(types.Interval1s), string(types.Interval1s),
@ -99,7 +99,7 @@ func subscribeEvents(client pb.ExchangeServiceClient) {
<-time.After(10 * time.Second) <-time.After(10 * time.Second)
msg := &pb.ReqStreamSubscribeKline{ msg := &pb.ReqStreamSubscribeKline{
SubType: pb.SubscribeType_Unsubscribe, SubType: pb.SubscribeType_Unsubscribe,
Exchanges: []pb.Exchange{pb.Exchange_OKX}, Exchanges: []pb.ExchangeType{pb.ExchangeType_OKX},
InstIds: []string{"BTC-USDT"}, InstIds: []string{"BTC-USDT"},
Intervals: []string{ Intervals: []string{
string(types.Interval1s), string(types.Interval1s),

30
cmd/market/main.go

@ -1,7 +1,7 @@
package main package main
import ( import (
"context" "fmt"
"net" "net"
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/internal/market" "sig-pub/internal/market"
@ -12,7 +12,7 @@ import (
"sig-pub/pkg/utils/exit" "sig-pub/pkg/utils/exit"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
clientv3 "go.etcd.io/etcd/client/v3" "github.com/hashicorp/consul/api"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
) )
@ -27,14 +27,13 @@ func main() {
conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml") conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml")
marketConf := config.MustLoadConfig(new(MarketConf), "config/market.toml") marketConf := config.MustLoadConfig(new(MarketConf), "config/market.toml")
// etcd discovery // consul 配置
etcdClient, err := clientv3.New(conf.Etcd) cc := api.DefaultConfig()
cc.Address = conf.Consul.Address
client, err := api.NewClient(cc)
if err != nil { if err != nil {
panic(err) panic(fmt.Errorf("consul client error: %v", err))
} }
exit.AddHook(func() { _ = etcdClient.Close() }, exit.WithOrderTail())
registry := discovery.NewEtcdDiscovery(etcdClient)
// database // database
db, err := conf.Database.Postgres.NewGormDB() db, err := conf.Database.Postgres.NewGormDB()
@ -60,20 +59,17 @@ func main() {
// 注册反射服务 // 注册反射服务
reflection.Register(grpcServer) reflection.Register(grpcServer)
} }
pb.RegisterMarketServiceServer(grpcServer, marketGrpcServer)
pb.RegisterMarketServer(grpcServer, marketGrpcServer) exit.AddHook(grpcServer.GracefulStop, exit.WithOrderFront())
// consul 服务注册
register := marketConf.Register register := marketConf.Register
if register.Name == "" { register.Name = pb.MarketService_ServiceDesc.ServiceName
register.Name = pb.Market_ServiceDesc.ServiceName dis := discovery.NewConsulDiscovery(client)
} if err := dis.Registry(grpcServer, register); err != nil {
ctx, cancel := context.WithCancel(context.Background())
exit.AddHook(cancel, exit.WithOrderFront())
if err := registry.Registry(ctx, register); err != nil {
panic(err) panic(err)
} }
exit.AddHook(grpcServer.GracefulStop, exit.WithOrderFront())
// run grpc server // run grpc server
go func() { go func() {

22
cmd/sig-admin/main.go

@ -1,13 +1,14 @@
package main package main
import ( import (
"fmt"
"sig-pub/internal/sig" "sig-pub/internal/sig"
"sig-pub/pkg/config" "sig-pub/pkg/config"
"sig-pub/pkg/grpc/discovery" "sig-pub/pkg/grpc/discovery"
"sig-pub/pkg/grpc/generic" "sig-pub/pkg/grpc/generic"
"sig-pub/pkg/utils/exit" "sig-pub/pkg/utils/exit"
clientv3 "go.etcd.io/etcd/client/v3" "github.com/hashicorp/consul/api"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
@ -17,20 +18,19 @@ func main() {
// load config // load config
conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml") conf := config.MustLoadConfig(new(config.Configuration), "config/config.toml")
// etcd discovery // consul 配置
etcdClient, err := clientv3.New(conf.Etcd) cc := api.DefaultConfig()
cc.Address = conf.Consul.Address
client, err := api.NewClient(cc)
if err != nil { if err != nil {
panic(err) panic(fmt.Errorf("consul client error: %v", err))
} }
exit.AddHook(func() { _ = etcdClient.Close() }, exit.WithOrderTail())
dis := discovery.NewEtcdDiscovery(etcdClient) // consul service discovery
resolver, err := dis.Resolver() dis := discovery.NewConsulDiscovery(client)
if err != nil { resolver := dis.Resolver()
panic(err)
}
gpcGenericClientFactory := generic.NewGpcGenericClientFactory( gpcGenericClientFactory := generic.NewGpcGenericClientFactory(
discovery.EtcdSchema, discovery.ConsulSchema,
grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithResolvers(resolver), grpc.WithResolvers(resolver),
) )

6
config/config.toml

@ -17,6 +17,9 @@ endpoints = ["127.0.0.1:7079"]
username = "" username = ""
password = "" password = ""
[consul]
address = "127.0.0.1:8500"
[database.mysql] [database.mysql]
logMode = "info" logMode = "info"
# https://gorm.io/zh_CN/docs/connecting_to_the_database.html # https://gorm.io/zh_CN/docs/connecting_to_the_database.html
@ -59,7 +62,8 @@ receiveBuffer = 4096
marketSubscribeLimit = 16 marketSubscribeLimit = 16
consumeBatch = 1024 consumeBatch = 1024
consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume consumeLater = 2000 # 时间到达later或者数据累计到batch触发consume
httpProxy = "http://192.168.1.6:7890" # httpProxy = "http://192.168.1.6:7890"
httpProxy = "http://10.255.183.209:7890"
# 模拟盘API交易地址如下: # 模拟盘API交易地址如下:
# REST:https://www.okx.com # REST:https://www.okx.com

3
config/exchange.toml

@ -1,6 +1,7 @@
grpcReflection = false # 注册grpc反射服务 grpcReflection = true # 注册grpc反射服务
[register] [register]
nodeId = 1 # grpc服务节点id, 多实例唯一
addr = ":8011" # grpc 服务端口 addr = ":8011" # grpc 服务端口
attrs = { weight = 10 } # grpc 服务权重 attrs = { weight = 10 } # grpc 服务权重

1
config/market.toml

@ -2,5 +2,6 @@
grpcReflection = true # 不注册grpc反射服务 grpcReflection = true # 不注册grpc反射服务
[register] [register]
nodeId = 1 # grpc服务节点id, 多实例唯一
addr = ":8001" # grpc服务端口 addr = ":8001" # grpc服务端口
attrs = { weight = 10 } # grpc服务权重 attrs = { weight = 10 } # grpc服务权重

14
docker-compose.yml

@ -89,10 +89,11 @@ services:
sig-vm: sig-vm:
container_name: sig-vm container_name: sig-vm
image: victoriametrics/victoria-metrics:v1.116.0 image: victoriametrics/victoria-metrics:v1.116.0
network_mode: host
volumes: volumes:
- "./fs/victoria-metrics-data:/victoria-metrics-data" - "./fs/victoria-metrics-data:/victoria-metrics-data"
ports: # ports:
- 8428:8428 # - 8428:8428
command: -dedup.minScrapeInterval=1s -retentionPeriod=99y command: -dedup.minScrapeInterval=1s -retentionPeriod=99y
sig-questdb: sig-questdb:
@ -116,3 +117,12 @@ services:
ports: ports:
- 7079:2379 - 7079:2379
- 7080:2380 - 7080:2380
sig-consul:
container_name: sig-consul
image: consul:1.15.4
network_mode: host
volumes:
- "./fs/consul/data:/consul/data"
- "./fs/consul/config:/consul/config"
# ports:
# - 8500:8500

21
go.mod

@ -1,6 +1,8 @@
module sig-pub module sig-pub
go 1.23.4 go 1.23.8
toolchain go1.24.7
require ( require (
github.com/VictoriaMetrics/metrics v1.36.0 github.com/VictoriaMetrics/metrics v1.36.0
@ -34,12 +36,14 @@ require (
require ( require (
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect github.com/gin-contrib/sse v1.0.0 // indirect
@ -53,6 +57,15 @@ require (
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/hashicorp/consul/api v1.32.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
@ -63,7 +76,10 @@ require (
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oapi-codegen/runtime v1.0.0 // indirect github.com/oapi-codegen/runtime v1.0.0 // indirect
@ -71,7 +87,7 @@ require (
github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.12.0 // indirect github.com/spf13/afero v1.12.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@ -82,6 +98,7 @@ require (
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.15.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/crypto v0.36.0 // indirect golang.org/x/crypto v0.36.0 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect

172
go.sum

@ -1,8 +1,23 @@
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
github.com/VictoriaMetrics/metrics v1.36.0 h1:f3SZMpLgIG4hJm2zfDs6wicxQ/QNWBZekY5rEGgbHKs= github.com/VictoriaMetrics/metrics v1.36.0 h1:f3SZMpLgIG4hJm2zfDs6wicxQ/QNWBZekY5rEGgbHKs=
github.com/VictoriaMetrics/metrics v1.36.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8= github.com/VictoriaMetrics/metrics v1.36.0/go.mod h1:r7hveu6xMdUACXvB8TYdAj8WEsKzWB0EkpJN+RDtOf8=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
@ -17,8 +32,11 @@ github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
@ -29,12 +47,18 @@ github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dsnet/golib/unitconv v1.0.2 h1:45gXng3Op1vTrnX1PdM9Bla4mEpBFYA5aC8dlqacmwM= github.com/dsnet/golib/unitconv v1.0.2 h1:45gXng3Op1vTrnX1PdM9Bla4mEpBFYA5aC8dlqacmwM=
github.com/dsnet/golib/unitconv v1.0.2/go.mod h1:86KTUtTJFLreKjc4sS9xE0rhj4lR44Ox0rEQSEXSWwM= github.com/dsnet/golib/unitconv v1.0.2/go.mod h1:86KTUtTJFLreKjc4sS9xE0rhj4lR44Ox0rEQSEXSWwM=
github.com/fanjindong/go-cache v0.0.6 h1:4xl8MnfW8pFLH9cRjs0uNfVbFNqV342yl/pgX3Ql9gM= github.com/fanjindong/go-cache v0.0.6 h1:4xl8MnfW8pFLH9cRjs0uNfVbFNqV342yl/pgX3Ql9gM=
github.com/fanjindong/go-cache v0.0.6/go.mod h1:gxehZ3SqUVta6eFBJAcDlXDT2Q9piXkUqv7s4E0Vj6o= github.com/fanjindong/go-cache v0.0.6/go.mod h1:gxehZ3SqUVta6eFBJAcDlXDT2Q9piXkUqv7s4E0Vj6o=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
@ -45,6 +69,10 @@ github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@ -61,16 +89,24 @@ github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptd
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@ -82,6 +118,39 @@ github.com/govalues/decimal v0.1.36 h1:dojDpsSvrk0ndAx8+saW5h9WDIHdWpIwrH/yhl9ol
github.com/govalues/decimal v0.1.36/go.mod h1:Ee7eI3Llf7hfqDZtpj8Q6NCIgJy1iY3kH1pSwDrNqlM= github.com/govalues/decimal v0.1.36/go.mod h1:Ee7eI3Llf7hfqDZtpj8Q6NCIgJy1iY3kH1pSwDrNqlM=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
github.com/hashicorp/consul/api v1.32.1 h1:0+osr/3t/aZNAdJX558crU3PEjVrG4x6715aZHRgceE=
github.com/hashicorp/consul/api v1.32.1/go.mod h1:mXUWLnxftwTmDv4W3lzxYCPD199iNLLUyLfLGFJbtl4=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0=
github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY=
github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4=
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4= github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI= github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
@ -100,9 +169,12 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
@ -111,51 +183,109 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo= github.com/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A= github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM=
github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo=
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs=
github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
@ -163,6 +293,7 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
@ -199,37 +330,72 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@ -245,11 +411,17 @@ google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI=
google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

27
internal/exchange/exchange.go

@ -3,14 +3,18 @@ package exchange
import ( import (
"context" "context"
"fmt" "fmt"
"sig-pub/api/pb"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"sig-pub/pkg/utils/collect" "sig-pub/pkg/utils/collect"
"sync/atomic"
"github.com/govalues/decimal"
) )
// 交易所行情数据订阅 // 交易所行情数据订阅
type ExchangeSubscriber interface { type ExchangeSubscriber interface {
// 交易所类型 // 交易所类型
ExhcangeType() types.Exchange ExhcangeType() pb.ExchangeType
// 消费k线行情数据 // 消费k线行情数据
ConsumerKline() <-chan *types.ChannelKline ConsumerKline() <-chan *types.ChannelKline
@ -25,26 +29,28 @@ type ExchangeSubscriber interface {
// 交易所行情数据请求 // 交易所行情数据请求
type ExchangeFetcher interface { type ExchangeFetcher interface {
// 交易所类型 // 交易所类型
ExhcangeType() types.Exchange ExhcangeType() pb.ExchangeType
// 获取区间内历史k线数据 // 获取区间内历史k线数据
FetchHistoryKlines(ctx context.Context, instId string, interval types.Interval, after, before int64) (klines []*types.Kline, err error) FetchHistoryKlines(ctx context.Context, instId string, interval types.Interval, after, before int64) (klines []*types.Kline, err error)
} }
// 交易所交互接口 // 交易所交互接口
type Exchange struct { type Exchange struct {
ExType types.Exchange ExchangeType pb.ExchangeType
Fetcher ExchangeFetcher Fetcher ExchangeFetcher
Subscriber ExchangeSubscriber Subscriber ExchangeSubscriber
Insts *collect.SyncMap[string, *ExchangeTradeInstance] // map[string]*ExchangeTradeInstance // <ExchangeInstId, Inst> TradeInstIds *collect.SyncMap[string, string] // map[string]*ExchangeTradeInstance // <TradeInstId, ExchangeInstId>
// sync.RWMutex ExchangeInsts *collect.SyncMap[string, *ExchangeTradeInstance] // map[string]*ExchangeTradeInstance // <ExchangeInstId, Inst>
} }
// 交易所交易产品 // 交易所交易产品
type ExchangeTradeInstance struct { type ExchangeTradeInstance struct {
Inst *types.TradeInstance Inst *types.TradeInstance
Status int32 // 交易产品状态, 0.初始化中 1.正常 Status atomic.Int32 // 交易产品状态, 0.初始化中 1.正常
LiveMarkTs int64 // websocket订阅k线标记时间戳 LiveKline *types.IntervalState[types.Kline] // 实时k线数据
HistoryMarkTs int64 // 拉取历史k线标记时间戳 LiveKStartTs *types.IntervalState[int64] // ws开始订阅k线标记时间戳
HistoryMarkTs *types.IntervalState[int64] // 拉取历史k线标记时间戳
Last decimal.Decimal // 交易产品实时价格tick更新
} }
func NewExchange(fetcher ExchangeFetcher, subscriber ExchangeSubscriber) *Exchange { func NewExchange(fetcher ExchangeFetcher, subscriber ExchangeSubscriber) *Exchange {
@ -54,9 +60,10 @@ func NewExchange(fetcher ExchangeFetcher, subscriber ExchangeSubscriber) *Exchan
} }
return &Exchange{ return &Exchange{
ExType: exType, ExchangeType: exType,
Fetcher: fetcher, Fetcher: fetcher,
Subscriber: subscriber, Subscriber: subscriber,
Insts: collect.NewSyncMap[string, *ExchangeTradeInstance](), TradeInstIds: collect.NewSyncMap[string, string](),
ExchangeInsts: collect.NewSyncMap[string, *ExchangeTradeInstance](),
} }
} }

50
internal/exchange/exchange_data_persist.go

@ -0,0 +1,50 @@
package exchange
import (
"context"
"fmt"
"sig-pub/api/pb"
"sig-pub/pkg/storage/kvrocks"
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics"
"sig-pub/pkg/types"
)
type ExchangeDataPersist struct {
vmtsdb *vmts.VictoriaMetricsTSDB
kvdb *kvrocks.KVRocksDB
// exchangeFetch ExchangeFetcher
}
func NewExchangeDataService(
vmdb *vmts.VictoriaMetricsTSDB,
kvdb *kvrocks.KVRocksDB,
) *ExchangeDataPersist {
return &ExchangeDataPersist{
vmtsdb: vmdb,
kvdb: kvdb,
}
}
func (p *ExchangeDataPersist) Init() (err error) {
return
}
func (p *ExchangeDataPersist) SaveKlines(inst types.TradeInstance, klines []*types.Kline) (err error) {
err = p.vmtsdb.SaveKlines(inst, klines)
if err != nil {
return
}
return
}
func (p *ExchangeDataPersist) GetHistoryKlineMarkTs(exchange pb.ExchangeType, instId string, interval types.Interval) (ts int64, err error) {
tsKey := fmt.Sprintf(HistoryKlineTsKey, exchange, instId, interval)
ts, err = p.kvdb.GetI64(context.Background(), tsKey)
return
}
func (p *ExchangeDataPersist) SaveHistoryKlineMarkTs(exchange pb.ExchangeType, instId string, interval types.Interval, ts int64) (tsKey string, err error) {
tsKey = fmt.Sprintf(HistoryKlineTsKey, exchange, instId, interval)
err = p.kvdb.SetI64(context.Background(), tsKey, ts)
return
}

34
internal/exchange/exchange_data_service.go

@ -1,34 +0,0 @@
package exchange
import (
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics"
"sig-pub/pkg/types"
)
type ExchangeDataService struct {
vmdb *vmts.VictoriaMetricsTSDB
exchangeFetch ExchangeFetcher
}
func NewExchangeDataService(
vmdb *vmts.VictoriaMetricsTSDB,
exchangeFetch ExchangeFetcher,
) *ExchangeDataService {
return &ExchangeDataService{
vmdb: vmdb,
exchangeFetch: exchangeFetch,
}
}
func (svc *ExchangeDataService) Init() (err error) {
return
}
func (svc *ExchangeDataService) SaveKlines(inst types.TradeInstance, klines []*types.Kline) (err error) {
err = svc.vmdb.SaveKlines(inst, klines)
if err != nil {
return
}
// todo klines 时间点回溯检查
return
}

409
internal/exchange/exchange_grpc_server.go

@ -4,196 +4,36 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"runtime"
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/aside"
"sig-pub/pkg/data"
"sig-pub/pkg/storage/kvrocks"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"sync"
"sync/atomic" "sync/atomic"
"time"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
type ExchangeGrpcServer struct { type ExchangeGrpcServer struct {
pb.UnimplementedExchangeServiceServer pb.UnimplementedExchangeServiceServer
exchangeMap map[types.Exchange]*Exchange exchangeService *ExchangeService
tradeInstanceAside *aside.TradeInstanceAside
exchangeDataService *ExchangeDataService
kvdb *kvrocks.KVRocksDB
klineStreamId int64 klineStreamId int64
klinePublisher *Publisher[int64, grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]]
}
// exchanges: 支持的数据源交易所 klineSubscriber *Publisher[int64, grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]]
func NewExchangeGrpcServer(
tradeInstanceAside *aside.TradeInstanceAside,
exchangeDataService *ExchangeDataService,
kvdb *kvrocks.KVRocksDB,
exchanges ...*Exchange,
) *ExchangeGrpcServer {
exchangeMap := make(map[types.Exchange]*Exchange)
for _, exchange := range exchanges {
exchangeMap[exchange.ExType] = exchange
} }
// exchanges: 支持的数据源交易所
func NewExchangeGrpcServer(exchangeService *ExchangeService) *ExchangeGrpcServer {
return &ExchangeGrpcServer{ return &ExchangeGrpcServer{
exchangeMap: exchangeMap, exchangeService: exchangeService,
tradeInstanceAside: tradeInstanceAside,
exchangeDataService: exchangeDataService,
kvdb: kvdb,
klinePublisher: NewPublisher[int64, grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]](16),
}
}
func (svc *ExchangeGrpcServer) Init() (err error) {
svc.subscribeExchanges()
return
} }
// 订阅交易所推送行情
func (svc *ExchangeGrpcServer) subscribeExchanges() {
// consumerKline
// 交易所订阅交易产品
for _, exchange := range svc.exchangeMap {
go func(exchange *Exchange) {
// get exchange trade instances
insts, err := svc.tradeInstanceAside.ListExchangeTradeInstance(context.Background(), exchange.ExType)
if err != nil {
zlog.Error(err)
return
} }
var exchangeInstIds []string func (svr *ExchangeGrpcServer) Init() (err error) {
var processingInsts []types.TradeInstance svr.klineSubscriber = svr.exchangeService.GetKlineSubscriber()
for _, inst := range insts {
exchangeInstIds = append(exchangeInstIds, inst.ExchangeInstId)
tradeInst := &types.TradeInstance{
InstId: inst.InstId,
Status: inst.Status,
PriceSz: 0,
QuantitySz: 0,
ExchangeInstId: inst.ExchangeInstId,
Exchange: exchange.ExType,
}
exchange.Insts.Store(inst.ExchangeInstId, &ExchangeTradeInstance{
Inst: tradeInst,
Status: 0,
LiveMarkTs: 0,
HistoryMarkTs: 0,
})
// 待初始化币种数据
if inst.Status == data.StatusProcessing {
processingInsts = append(processingInsts, *tradeInst)
}
}
// instIds := []string{"BTC-USDT", "DOGE-USDT-SWAP"}
err = exchange.Subscriber.SubscribeKline(exchangeInstIds...)
if err != nil {
zlog.Error(err)
return
}
go func() {
c := exchange.Subscriber.ConsumerKline()
svc.consumerKline(exchange, c)
// todo subscribe books 订单簿
zlog.Infof("unsubscribe exchange: %s", exchange.ExType)
}()
// 初始化k线数据
go svc.initialKlines(exchange, processingInsts)
}(exchange)
}
}
// consumerKline 消费交易所k线数据
func (svc *ExchangeGrpcServer) consumerKline(exchange *Exchange, c <-chan *types.ChannelKline) {
exchangeType := exchange.ExType
for {
channelK, ok := <-c
if !ok {
return return
} }
// 交易所 instid 转 sig-instid
var exInst *types.TradeInstance
if inst, ok := exchange.Insts.Load(channelK.ExgInstId); ok && inst != nil {
exInst = inst.Inst
// 标记交易产品开始订阅k线时间
if inst.LiveMarkTs == 0 && len(channelK.Klines) > 0 {
inst.LiveMarkTs = channelK.Klines[0].Ts
}
} else {
zlog.Errorf("unknown exchange instId: %v, %s", channelK.Exchange, channelK.ExgInstId)
continue
}
// publish to subscribers
pubMsgMap := make(map[string]*pb.StreamKline)
// instId := channelK.InstId
pbExType, err := channelK.Exchange.Exchange2PB()
if err != nil {
zlog.Error(err)
continue
}
var confirmKlines []*types.Kline
for _, kline := range channelK.Klines {
// zlog.Infof("recv kline: %#v", kline)
confirm := 0
if kline.Confirm {
confirm = 1
confirmKlines = append(confirmKlines, kline)
}
pubKey := fmt.Sprintf("/kline/%s/%s/%s/%d", exchangeType, exInst.InstId, kline.Interval, confirm)
// todo 优化没有订阅者就跳过
msg, ok := pubMsgMap[pubKey]
if !ok {
msg = new(pb.StreamKline)
msg.InstId = exInst.InstId
msg.Exchange = pbExType
pubMsgMap[pubKey] = msg
}
pbk := kline.ToPBKline()
msg.Klines = append(msg.Klines, pbk)
}
// tsdb storage
if len(confirmKlines) > 0 {
// todo 异步处理
err := svc.exchangeDataService.SaveKlines(*exInst, confirmKlines)
if err != nil {
zlog.Errorf("kline save to tsdb error: ", err)
}
}
for pubKey, msg := range pubMsgMap {
if len(msg.Klines) == 0 {
continue
}
subs := svc.klinePublisher.Publisher(pubKey)
for _, sub := range subs {
if err := sub.Send(&pb.RspStreamSubscribeKline{Kline: msg}); err != nil {
zlog.Error(err)
}
}
}
}
}
// SubscribeKline 订阅k线stream // SubscribeKline 订阅k线stream
func (svc *ExchangeGrpcServer) SubscribeKline(stream grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]) (err0 error) { func (svr *ExchangeGrpcServer) SubscribeKline(stream grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]) (err0 error) {
streamId := atomic.AddInt64(&svc.klineStreamId, 1) streamId := atomic.AddInt64(&svr.klineStreamId, 1)
// subKey = /kline/exchange/instId/interval/confirm // subKey = /kline/exchange/instId/interval/confirm
// 接收消息的goroutine // 接收消息的goroutine
@ -221,17 +61,17 @@ func (svc *ExchangeGrpcServer) SubscribeKline(stream grpc.BidiStreamingServer[pb
select { select {
case <-stream.Context().Done(): case <-stream.Context().Done():
// 客户端断开连接 // 客户端断开连接
svc.klinePublisher.UnsubscribeAll(streamId) svr.klineSubscriber.UnsubscribeAll(streamId)
return stream.Context().Err() return stream.Context().Err()
case msg, ok := <-recvChan: case msg, ok := <-recvChan:
if !ok { if !ok {
// 接收通道关闭,结束流 // 接收通道关闭,结束流
svc.klinePublisher.UnsubscribeAll(streamId) svr.klineSubscriber.UnsubscribeAll(streamId)
return return
} }
if msg.SubType == pb.SubscribeType_UnsubscribeAll { if msg.SubType == pb.SubscribeType_UnsubscribeAll {
svc.klinePublisher.UnsubscribeAll(streamId) svr.klineSubscriber.UnsubscribeAll(streamId)
continue continue
} }
for _, exchange := range msg.Exchanges { for _, exchange := range msg.Exchanges {
@ -246,238 +86,31 @@ func (svc *ExchangeGrpcServer) SubscribeKline(stream grpc.BidiStreamingServer[pb
zlog.Infof("stream: %d sub: %s", streamId, subKey) zlog.Infof("stream: %d sub: %s", streamId, subKey)
switch msg.SubType { switch msg.SubType {
case pb.SubscribeType_Subscribe: case pb.SubscribeType_Subscribe:
svc.klinePublisher.Subscribe(subKey, streamId, stream) svr.klineSubscriber.Subscribe(subKey, streamId, stream)
case pb.SubscribeType_Unsubscribe: case pb.SubscribeType_Unsubscribe:
svc.klinePublisher.Unsubscribe(subKey, streamId) svr.klineSubscriber.Unsubscribe(subKey, streamId)
}
} }
} }
} }
} }
} }
} }
// for {
// select {
// case <-ctx.Done():
// // 客户端断开连接
// // log.Printf("Client disconnected from topic: %s", topic)
// return
// default:
// // 模拟事件生成
// event := &pb.SubscribeKlineStreamRsp{
// Kline: &pb.StreamKline{
// InstId: "DOGE/USDT",
// Exchange: pb.Exchange_OKX,
// Klines: []*pb.Kline{
// {
// Ts: time.Now().Unix(),
// },
// },
// },
// }
// // 推送事件
// if err := streamRsp.Send(event); err != nil {
// log.Printf("Failed to send event to client: %v", err)
// return err
// }
// // 模拟事件间隔
// time.Sleep(2 * time.Second)
// }
// }
}
const (
KlineBefore0 int64 = 1672502400000 // k线开始数据 2023-01-01 00:00:00 GMT+8
HistoryKlineTsKey string = "history-kline-ts:%s:%s:%s" // exchange:sig-instid:interval
)
type initialKlineTask struct {
inst types.TradeInstance
interval types.Interval
afterTs int64
beforeTs int64
times int32 // 重试次数
} }
func (t initialKlineTask) logKey() string {
return fmt.Sprintf("%s:%s:%s:%d:%d", t.inst.Exchange, t.inst.InstId, t.interval, t.beforeTs, t.afterTs)
} }
// initialKline 初始化交易产品历史k线数据 // 获取支持的交易所列表
func (svc *ExchangeGrpcServer) initialKlines(exchange *Exchange, insts []types.TradeInstance) { func (svr *ExchangeGrpcServer) Exchanges(ctx context.Context, req *pb.ReqExchanges) (rsp *pb.RspExchanges, err error) {
concurrent := max(8, runtime.NumCPU()*2) exchanges, err := svr.exchangeService.Exchanges()
// 任务 channel
taskCh := make(chan initialKlineTask, concurrent)
// 任务生成
go func() {
for _, inst := range insts {
// for interval, intervalAdder := range types.SupportedIntervals {
interval := types.Interval1d
intervalAdder := types.SupportedIntervals[interval]
// history 未补全前, history写 kvdb ts mark, 补全后 ws live 写 ts mark
tsKey := fmt.Sprintf(HistoryKlineTsKey, inst.Exchange, inst.InstId, interval)
beforeTs, err := svc.kvdb.GetI64(context.Background(), tsKey)
if err != nil { if err != nil {
zlog.Error(err)
panic(err)
}
if beforeTs == 0 {
beforeTs = intervalAdder(KlineBefore0, -1)
}
for {
afterTs := intervalAdder(beforeTs, 101)
taskCh <- initialKlineTask{
inst: inst,
interval: interval,
afterTs: afterTs,
beforeTs: beforeTs,
times: 0,
}
beforeTs = intervalAdder(afterTs, -1)
// 对比 ws 获取的实时k线
inst, ok := exchange.Insts.Load(inst.ExchangeInstId)
if !ok {
break
}
// 订阅完成
if inst.LiveMarkTs != 0 && beforeTs > inst.LiveMarkTs {
// history status -> ok
break
}
if beforeTs > time.Now().UnixMilli() {
if inst.LiveMarkTs != 0 {
// history status -> ok
} else {
// live status -> not ok
}
break
}
}
// }
}
close(taskCh)
}()
loc, _ := time.LoadLocation("Asia/Shanghai")
// 任务消费器 8协程并行
wg := new(sync.WaitGroup)
for range concurrent {
wg.Add(1)
go func() {
defer wg.Done()
for {
task, ok := <-taskCh
if !ok {
break
}
if task.times > 0 {
zlog.Infof("retry fetch history kline task %d times: task -> %s", task.times, task.logKey())
}
interval, afterTs, beforeTs := task.interval, task.afterTs, task.beforeTs
klines, err := exchange.Fetcher.FetchHistoryKlines(context.Background(), task.inst.ExchangeInstId, interval, afterTs, beforeTs)
if err != nil {
zlog.Errorf("fetch history kline task error: task -> %s, err -> %v", task.logKey(), err)
// retry task
task.times++
taskCh <- task
return return
} }
if len(klines) == 0 { return &pb.RspExchanges{Exchanges: exchanges}, nil
continue
} }
sts, ets := klines[0].Ts, klines[len(klines)-1].Ts func (svr *ExchangeGrpcServer) ExchangeInstanceState(ctx context.Context, req *pb.ReqExchangeInstanceState) (rsp *pb.RspExchangeInstanceState, err error) {
ss := time.UnixMilli(sts).In(loc).Format(times.FORMAT_DATE) states, err := svr.exchangeService.ExchangeInstanceState(req.AllExchange, req.Exchanges, req.Insts)
ee := time.UnixMilli(ets).In(loc).Format(times.FORMAT_DATE)
zlog.Infof("fetch interval %s %d~%d klines: ret=%d~%d, %d klines, %s~%s", interval, afterTs, beforeTs, sts, ets, len(klines), ss, ee)
// store to tsdb
err = svc.exchangeDataService.SaveKlines(task.inst, klines)
if err != nil { if err != nil {
zlog.Errorf("save history klines to tsdb error: task -> %s, err -> %v", task.logKey(), err)
// retry task
task.times++
taskCh <- task
return return
} }
} return &pb.RspExchangeInstanceState{InstsState: states}, nil
}()
}
wg.Wait()
zlog.Infof("%d insts initial finished", len(insts))
// var intervals []types.Interval
// for interval := range types.SupportedIntervals {
// intervals = append(intervals, interval)
// }
// var intervalsTs = make([]int, len(intervals))
// var index int
// var lock sync.Mutex
// var getTask = func() (interval types.Interval, afterTs, beforeTs int64) {
// lock.Lock()
// ts := intervalsTs[index]
// if ts == -1 {
// index++
// }
// lock.Unlock()
// return
// }
// var finishTask = func(interval types.Interval) {
// }
// ctx := context.Background()
// inst := insts[0]
// // for interval, intervalAdder := range types.SupportedIntervals {
// interval := types.Interval1h
// intervalAdder := types.SupportedIntervals[interval]
// // kvrocks get exchange+inst+interval last/ts
// tsKey := fmt.Sprintf(HistoryKlineTsKey, inst.Exchange, inst.InstId, interval)
// beforeTs, e := svc.kvdb.GetI64(ctx, tsKey)
// if e != nil {
// err = e
// return
// }
// if beforeTs == 0 {
// beforeTs = intervalAdder(KlineBefore0, -1)
// }
// for {
// afterTs := intervalAdder(beforeTs, 101)
// klines, e := exchange.Fetcher.FetchHistoryKlines(ctx, inst.ExchangeInstId, interval, afterTs, beforeTs)
// if e != nil {
// err = e
// return
// }
// if len(klines) == 0 {
// break
// }
// zlog.Infof("fetch interval %s %d~%d klines: ret=%d~%d, %d klines", interval, afterTs, beforeTs, klines[0].Ts, klines[len(klines)-1].Ts, len(klines))
// // store to tsdb
// err = svc.exchangeDataService.SaveKlines(inst, klines)
// if err != nil {
// return
// }
// // set kvdb inst ts mark
// beforeTs = klines[0].Ts
// }
// // }
// zlog.Infof("%s %s initial finished", inst.Exchange, inst.InstId)
} }

478
internal/exchange/exchange_service.go

@ -0,0 +1,478 @@
package exchange
import (
"context"
"errors"
"fmt"
"runtime"
"sig-pub/api/pb"
"sig-pub/pkg/aside"
"sig-pub/pkg/data"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/times"
"sig-pub/pkg/zlog"
"sort"
"sync"
"sync/atomic"
"time"
"google.golang.org/grpc"
)
// ExchangeService 交易所服务
type ExchangeService struct {
exchangeMap map[pb.ExchangeType]*Exchange
tradeInstanceAside *aside.TradeInstanceAside
exchangeDataService *ExchangeDataPersist
klinePublisher *Publisher[int64, grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]]
}
// exchanges: 支持的数据源交易所
func NewExchangeService(
tradeInstanceAside *aside.TradeInstanceAside,
exchangeDataService *ExchangeDataPersist,
exchanges ...*Exchange,
) *ExchangeService {
exchangeMap := make(map[pb.ExchangeType]*Exchange)
for _, exchange := range exchanges {
exchangeMap[exchange.ExchangeType] = exchange
}
return &ExchangeService{
exchangeMap: exchangeMap,
tradeInstanceAside: tradeInstanceAside,
exchangeDataService: exchangeDataService,
klinePublisher: NewPublisher[int64, grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]](16),
}
}
func (svc *ExchangeService) Init() (err error) {
svc.subscribeExchanges()
return
}
// GetKlineSubscriber 订阅k线订阅器
func (svc *ExchangeService) GetKlineSubscriber() (subscriber *Publisher[int64, grpc.BidiStreamingServer[pb.ReqStreamSubscribeKline, pb.RspStreamSubscribeKline]]) {
subscriber = svc.klinePublisher
return
}
// 订阅交易所推送行情
func (svc *ExchangeService) subscribeExchanges() {
// consumerKline
// 交易所订阅交易产品
for _, exchange := range svc.exchangeMap {
go func(exchange *Exchange) {
// get exchange trade instances
insts, err := svc.tradeInstanceAside.ListExchangeTradeInstance(context.Background(), exchange.ExchangeType)
if err != nil {
zlog.Error(err)
return
}
var exchangeInstIds []string
var processingInsts []types.TradeInstance
for _, inst := range insts {
exchangeInstIds = append(exchangeInstIds, inst.ExchangeInstId)
tradeInst := &types.TradeInstance{
InstId: inst.InstId,
Status: inst.Status,
PriceSz: 0,
QuantitySz: 0,
ExchangeInstId: inst.ExchangeInstId,
Exchange: exchange.ExchangeType,
}
exchange.TradeInstIds.Store(inst.InstId, inst.ExchangeInstId)
exchange.ExchangeInsts.Store(inst.ExchangeInstId, &ExchangeTradeInstance{
Inst: tradeInst,
LiveKline: types.NewIntervalState[types.Kline](),
LiveKStartTs: types.NewIntervalState[int64](),
HistoryMarkTs: types.NewIntervalState[int64](),
})
// 待初始化币种数据
if inst.Status == int32(data.StatusProcessing) {
processingInsts = append(processingInsts, *tradeInst)
}
}
// instIds := []string{"BTC-USDT", "DOGE-USDT-SWAP"}
err = exchange.Subscriber.SubscribeKline(exchangeInstIds...)
if err != nil {
zlog.Error(err)
return
}
go func() {
c := exchange.Subscriber.ConsumerKline()
svc.consumerKline(exchange, c)
// todo subscribe books 订单簿
zlog.Infof("unsubscribe exchange: %s", exchange.ExchangeType)
}()
// 初始化k线数据
go svc.initialKlines(exchange, processingInsts)
}(exchange)
}
}
// consumerKline 消费交易所k线数据
func (svc *ExchangeService) consumerKline(exchange *Exchange, c <-chan *types.ChannelKline) {
exchangeType := exchange.ExchangeType
// publish to subscribers
pubStreamKlineMap := make(map[string]*pb.StreamKline)
for {
clear(pubStreamKlineMap)
channelK, ok := <-c
if !ok {
return
}
if len(channelK.Klines) == 0 {
continue
}
if len(channelK.Klines) > 1 {
sort.Slice(channelK.Klines, func(i, j int) bool {
return channelK.Klines[i].Ts < channelK.Klines[j].Ts
})
}
firstKline, lastKline := channelK.Klines[0], channelK.Klines[len(channelK.Klines)-1]
// 交易所 instid 转 sig-instid
var tradeInst *types.TradeInstance
exchangeInst, ok := exchange.ExchangeInsts.Load(channelK.ExgInstId)
if !ok || exchangeInst == nil || exchangeInst.Inst == nil {
zlog.Errorf("unknown exchange instId: %v, %s", channelK.Exchange, channelK.ExgInstId)
continue
}
tradeInst = exchangeInst.Inst
// 标记交易产品开始订阅k线时间
exchangeInst.LiveKStartTs.SetIf(firstKline.Interval, firstKline.Ts, func(old int64) bool { return old == 0 })
// 标记实时k线
exchangeInst.LiveKline.Set(lastKline.Interval, *lastKline)
// 记录实时价格
exchangeInst.Last = lastKline.Close
var confirmKlines []*types.Kline
for _, kline := range channelK.Klines {
// zlog.Infof("recv kline: %#v", kline)
confirm := 0
if kline.Confirm {
confirm = 1
confirmKlines = append(confirmKlines, kline)
}
pubKey := fmt.Sprintf("/kline/%s/%s/%s/%d", exchangeType, tradeInst.InstId, kline.Interval, confirm)
msg, ok := pubStreamKlineMap[pubKey]
if !ok {
msg = new(pb.StreamKline)
msg.InstId = tradeInst.InstId
msg.Exchange = channelK.Exchange
pubStreamKlineMap[pubKey] = msg
}
pbk := kline.ToPBKline()
msg.Klines = append(msg.Klines, pbk)
}
if len(confirmKlines) > 0 {
// tsdb storage todo 异步处理
err := svc.exchangeDataService.SaveKlines(*tradeInst, confirmKlines)
if err != nil {
zlog.Errorf("kline save to tsdb error: ", err)
}
// 初始化状态完成, 检查k线时间戳标记
if exchangeInst.Status.Load() == int32(data.StatusOk) {
lastConfirmKline := confirmKlines[len(confirmKlines)-1]
historyMark := exchangeInst.HistoryMarkTs.Get(lastConfirmKline.Interval)
_ = historyMark
}
}
// publish grpc stream klines
for pubKey, kline := range pubStreamKlineMap {
if len(kline.Klines) == 0 {
continue
}
subs := svc.klinePublisher.Publisher(pubKey)
for _, sub := range subs {
if err := sub.Send(&pb.RspStreamSubscribeKline{Kline: kline}); err != nil {
zlog.Error(err)
}
}
}
}
}
const (
KlineBefore0 int64 = 1672502400000 // k线开始数据 2023-01-01 00:00:00 GMT+8
HistoryKlineTsKey string = "history-kline-ts:%s:%s:%s" // exchange:sig-instid:interval
SingleKlineFetchTaskMaxFailTimes int32 = 100 // 单个k线拉取任务最大失败次数
)
type fetchKlineTask struct {
inst types.TradeInstance
interval types.Interval
afterTs int64
beforeTs int64
times int32 // 重试次数
}
func (t fetchKlineTask) logKey() string {
return fmt.Sprintf("%s:%s:%s:%d:%d", t.inst.Exchange, t.inst.InstId, t.interval, t.beforeTs, t.afterTs)
}
// initialKline 初始化交易产品历史k线数据
func (svc *ExchangeService) initialKlines(exchange *Exchange, insts []types.TradeInstance) {
// 记录成功和失败的交易产品
var success, failed []types.TradeInstance
for _, inst := range insts {
err := svc.initialTradeInstanceKlines(exchange, inst)
if err != nil {
zlog.Errorf("initial fetch trade instance error: %s(%s), err=%v", inst.InstId, inst.Exchange, err)
failed = append(failed, inst)
} else {
success = append(success, inst)
}
}
zlog.Infof("%d insts initial finished, success %d, failed %d", len(insts), len(success), len(failed))
}
// initTradeInstanceKlines 初始化交易产品历史k线数据
func (svc *ExchangeService) initialTradeInstanceKlines(exchange *Exchange, tradeInst types.TradeInstance) (err error) {
exchangeInst, ok := exchange.ExchangeInsts.Load(tradeInst.ExchangeInstId)
if !ok {
err = fmt.Errorf("not load exchange trade instance: %s", tradeInst.ExchangeInstId)
return
}
// 并发数
concurrent := max(8, runtime.NumCPU()*2)
// 任务 channel
taskCh := make(chan fetchKlineTask, concurrent)
retryTaskCh := make(chan fetchKlineTask, concurrent)
// 发布任务数, 成功任务数, 失败任务次数
var pubTasks, subTasks, failTasks atomic.Int32
var pubTaskDone atomic.Bool // 所有任务已发布
ctx, cancel := context.WithCancel(context.Background())
defer func() {
if err != nil {
// 交易所k线初始化失败
exchangeInst.Status.Store(int32(data.StatusFailed))
return
}
exchangeInst.HistoryMarkTs.Range(func(_ int, interval types.Interval, ts int64) {
tsKey, ex := svc.exchangeDataService.SaveHistoryKlineMarkTs(tradeInst.Exchange, tradeInst.InstId, interval, ts)
if ex != nil {
zlog.Errorf("history mark inititaled ts error: key=%s, ts=%d, %v", tsKey, ts, ex)
}
})
exchangeInst.Status.Store(int32(data.StatusOk))
}()
go func() {
defer func() {
pubTaskDone.Store(true)
// 无任务处理
if subTasks.Load() == 0 {
cancel()
}
zlog.Infof("trade instance initial kline %s(%s), pub %d fetch tasks", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load())
}()
for interval, intervalAdder := range types.SupportedIntervals {
// interval := types.Interval1d
// intervalAdder := types.SupportedIntervals[interval]
// history 未补全前, history写 kvdb ts mark, 补全后 ws live 写 ts mark
beforeTs, ex := svc.exchangeDataService.GetHistoryKlineMarkTs(tradeInst.Exchange, tradeInst.InstId, interval)
if ex != nil {
err = ex
zlog.Error(err)
cancel()
return
}
if beforeTs == 0 {
beforeTs = intervalAdder(KlineBefore0, -1)
}
exchangeInst.HistoryMarkTs.Set(interval, beforeTs)
for {
// 判定订阅任务发布完成
liveStartTs := exchangeInst.LiveKStartTs.Get(interval)
if liveStartTs != 0 && beforeTs >= liveStartTs {
break
}
if beforeTs > time.Now().UnixMilli() {
break
}
afterTs := intervalAdder(beforeTs, 101)
task := fetchKlineTask{
inst: tradeInst,
interval: interval,
afterTs: afterTs,
beforeTs: beforeTs,
times: 0,
}
// 发布任务
select {
case taskCh <- task:
pubTasks.Add(1)
case <-ctx.Done():
return
}
beforeTs = intervalAdder(afterTs, -1)
}
}
}()
// 任务消费器 多协程并行
wg := new(sync.WaitGroup)
for range concurrent {
wg.Add(1)
go func() {
defer wg.Done()
var task fetchKlineTask
for {
select {
case <-ctx.Done():
return
case task = <-taskCh:
case task = <-retryTaskCh:
}
if task.times > 0 {
zlog.Infof("retry fetch history kline task %d times: task -> %s", task.times, task.logKey())
}
if lastKlineTs, ex := svc.fetchTaskKlines(exchange, task); ex != nil {
failTasks.Add(1)
if task.times >= SingleKlineFetchTaskMaxFailTimes {
err = fmt.Errorf("task failed to many times %d, key: %s, err: %v", task.times, task.logKey(), ex)
cancel()
return
}
// retry task
task.times++
select {
case retryTaskCh <- task:
case <-ctx.Done():
return
}
} else {
// 周期任务最后kline时间
if lastKlineTs != 0 {
exchangeInst.HistoryMarkTs.SetIf(task.interval, lastKlineTs, func(old int64) bool {
return lastKlineTs > old
})
// historyMarkTsMu.Lock()
// historyMarkTs[task.interval] = max(historyMarkTs[task.interval], lastKlineTs)
// historyMarkTsMu.Unlock()
}
zlog.Debugf("trade instance initial kline tasks processing: %s(%s), pub %d, sub %d, fail %d", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTasks.Load())
// 任务都已执行成功结束
subs := subTasks.Add(1)
if pubTaskDone.Load() && subs >= pubTasks.Load() {
zlog.Infof("trade instance initial kline tasks success finished, %s(%s), pub %d, sub %d, fail %d", tradeInst.InstId, tradeInst.Exchange, pubTasks.Load(), subTasks.Load(), failTasks.Load())
cancel()
return
}
}
}
}()
}
wg.Wait()
return
}
func (svc *ExchangeService) fetchTaskKlines(exchange *Exchange, task fetchKlineTask) (lastKlineTs int64, err error) {
interval, afterTs, beforeTs := task.interval, task.afterTs, task.beforeTs
klines, err := exchange.Fetcher.FetchHistoryKlines(context.Background(), task.inst.ExchangeInstId, interval, afterTs, beforeTs)
if err != nil {
zlog.Errorf("fetch history kline task error: task -> %s, err -> %v", task.logKey(), err)
return
}
if len(klines) == 0 {
return
}
loc, _ := time.LoadLocation("Asia/Shanghai")
sts, ets := klines[0].Ts, klines[len(klines)-1].Ts
lastKlineTs = max(sts, ets)
ss := time.UnixMilli(sts).In(loc).Format(times.FORMAT_DATE)
ee := time.UnixMilli(ets).In(loc).Format(times.FORMAT_DATE)
zlog.Infof("fetch interval %s %d~%d klines: ret=%d~%d, %d klines, %s~%s", interval, beforeTs, afterTs, ets, sts, len(klines), ee, ss)
// store to tsdb
err = svc.exchangeDataService.SaveKlines(task.inst, klines)
if err != nil {
zlog.Errorf("save history klines to tsdb error: task -> %s, err -> %v", task.logKey(), err)
return
}
return
}
// Exchanges 支持的交易所列表
func (svc *ExchangeService) Exchanges() (exchanges []pb.ExchangeType, err error) {
for exchange := range svc.exchangeMap {
exchanges = append(exchanges, exchange)
}
return
}
// ExchangeInstanceState 交易所交易产品状态
func (svc *ExchangeService) ExchangeInstanceState(allExchange bool, exchangeTypes []pb.ExchangeType, instIds []string) (states []*pb.TradeInstanceState, err error) {
var exchanges []*Exchange
if allExchange {
for _, exg := range svc.exchangeMap {
exchanges = append(exchanges, exg)
}
} else {
for _, exchangeType := range exchangeTypes {
exg, ok := svc.exchangeMap[exchangeType]
if !ok {
err = fmt.Errorf("not support exchange: %v", exchangeType)
return
}
exchanges = append(exchanges, exg)
}
}
if len(exchanges) == 0 {
err = errors.New("no support exchanges")
return
}
for _, exchange := range exchanges {
for _, instId := range instIds {
// trade instId to exchangeInstId
exchangeInstId, ok := exchange.TradeInstIds.Load(instId)
if !ok {
continue
}
inst, ok := exchange.ExchangeInsts.Load(exchangeInstId)
if !ok {
continue
}
state := &pb.TradeInstanceState{
Exchange: exchange.ExchangeType,
InstId: instId,
Last: inst.Last.String(),
}
states = append(states, state)
}
}
return
}

3
internal/exchange/okx/channel_kline.go

@ -2,6 +2,7 @@ package okx
import ( import (
"fmt" "fmt"
"sig-pub/api/pb"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"strconv" "strconv"
@ -87,7 +88,7 @@ func (c *ChannelCandle) GetSubscribes() (instIds []string) {
func candleData2Klines(channelData *ChannelData[*CandleData]) (r *types.ChannelKline, err error) { func candleData2Klines(channelData *ChannelData[*CandleData]) (r *types.ChannelKline, err error) {
r = &types.ChannelKline{ r = &types.ChannelKline{
ExgInstId: channelData.InstId, ExgInstId: channelData.InstId,
Exchange: types.ExchangeOKX, Exchange: pb.ExchangeType_OKX,
} }
for _, data := range *channelData.Data { for _, data := range *channelData.Data {
ts, e := strconv.ParseInt(data[0], 10, 64) ts, e := strconv.ParseInt(data[0], 10, 64)

5
internal/exchange/okx/okx_fetch.go

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"sig-pub/api/pb"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"strconv" "strconv"
"strings" "strings"
@ -45,8 +46,8 @@ func NewOkxFetcher(httpProxy string) (f *OkxFetcher) {
return return
} }
func (okx *OkxFetcher) ExhcangeType() types.Exchange { func (okx *OkxFetcher) ExhcangeType() pb.ExchangeType {
return types.ExchangeOKX return pb.ExchangeType_OKX
} }
// FetchHistoryKlines 获取交易产品历史K线数据 // FetchHistoryKlines 获取交易产品历史K线数据

5
internal/exchange/okx/okx_subscriber.go

@ -1,6 +1,7 @@
package okx package okx
import ( import (
"sig-pub/api/pb"
"sig-pub/pkg/config" "sig-pub/pkg/config"
"sig-pub/pkg/types" "sig-pub/pkg/types"
) )
@ -51,8 +52,8 @@ func (okx *OkxSubscriber) Init() (err error) {
return return
} }
func (okx *OkxSubscriber) ExhcangeType() types.Exchange { func (okx *OkxSubscriber) ExhcangeType() pb.ExchangeType {
return types.ExchangeOKX return pb.ExchangeType_OKX
} }
func (okx *OkxSubscriber) ConsumerKline() <-chan *types.ChannelKline { func (okx *OkxSubscriber) ConsumerKline() <-chan *types.ChannelKline {

17
internal/indicator/indicator.go

@ -1,3 +1,20 @@
package indicator package indicator
// load indicator plugin // load indicator plugin
// 热指标 自动加载/实时更新/内存缓存
// 历史指标 实时计算
// 自定义插件化指标
type IndicatorService struct {
}
func NewIndicatorService() *IndicatorService {
return &IndicatorService{}
}
// 加载热指标
// 订阅k线数据 更新指标
func (svc *IndicatorService) Init() {
}

2
internal/market/market_grpc_server.go

@ -9,7 +9,7 @@ import (
) )
type MarketGrpcServer struct { type MarketGrpcServer struct {
pb.UnimplementedMarketServer pb.UnimplementedMarketServiceServer
tradeInstanceService *TradeInstanceService tradeInstanceService *TradeInstanceService
} }

2
internal/market/trade_instance_service.go

@ -140,7 +140,7 @@ func (s *TradeInstanceService) UpdateInstanceStatus(inst *args.UpdateTradeInstan
func (s *TradeInstanceService) ListExchangeTradeInstance(exchanges []int32) (exchangesInsts []*entity.TradeInstanceExchange, err error) { func (s *TradeInstanceService) ListExchangeTradeInstance(exchanges []int32) (exchangesInsts []*entity.TradeInstanceExchange, err error) {
err = s.db.Select(&exchangesInsts, ` err = s.db.Select(&exchangesInsts, `
select * from t_trade_instance_exchange where exchange in ? and status in ? order by inst_id, exchange select * from t_trade_instance_exchange where exchange in ? and status in ? order by inst_id, exchange
`, exchanges, []int{data.StatusOk, data.StatusProcessing}) `, exchanges, []data.Status{data.StatusOk, data.StatusProcessing})
if err != nil { if err != nil {
return return
} }

8
internal/market/validation.go

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/data/entity" "sig-pub/pkg/data/entity"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/validator" "sig-pub/pkg/utils/validator"
) )
@ -17,12 +16,13 @@ func validateTradeInstance(inst *entity.TradeInstance) (err error) {
if validator.IsAnyBlank(inst.InstId, inst.InstPair, inst.InstCoin) { if validator.IsAnyBlank(inst.InstId, inst.InstPair, inst.InstCoin) {
return errors.New("InstId InstPair InstCoin can't be blank") return errors.New("InstId InstPair InstCoin can't be blank")
} }
// todo validate others // 交易所
for _, ex := range inst.Exchanges { for _, ex := range inst.Exchanges {
_, ok := types.ExchangePBParse(pb.Exchange(ex.Exchange)) _, ok := pb.ExchangeType_name[ex.Exchange]
if !ok { if !ok || ex.Exchange == 0 {
return fmt.Errorf("exchange %d not valid", ex.Exchange) return fmt.Errorf("exchange %d not valid", ex.Exchange)
} }
} }
// todo validate others
return return
} }

11
internal/sig/sig_server.go

@ -11,6 +11,7 @@ import (
"sig-pub/pkg/resp" "sig-pub/pkg/resp"
"sig-pub/pkg/utils/strs" "sig-pub/pkg/utils/strs"
"sig-pub/pkg/zlog" "sig-pub/pkg/zlog"
"strings"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -61,14 +62,16 @@ func (s *SigServer) Run(addr string) (err error) {
} }
func (s *SigServer) handleGrpcGenericCall(c *gin.Context) { func (s *SigServer) handleGrpcGenericCall(c *gin.Context) {
svc := strs.UpperInitialLetter(c.Param("svr")) svr := strs.UpperInitialLetter(c.Param("svr"))
method := strs.UpperInitialLetter(c.Param("method")) method := strs.UpperInitialLetter(c.Param("method"))
if svc == "" || method == "" { if svr == "" || method == "" {
c.JSON(http.StatusBadRequest, resp.Error("service not found")) c.JSON(http.StatusBadRequest, resp.Error("service not found"))
return return
} }
if !strings.HasSuffix(svr, "Service") {
svr += "Service"
}
// todo service white list // todo service white list
// get request body // get request body
@ -80,7 +83,7 @@ func (s *SigServer) handleGrpcGenericCall(c *gin.Context) {
ctx1, cancel := context.WithTimeout(context.Background(), time.Second*5) ctx1, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel() defer cancel()
grpcGenericClient, err := s.grpcGenericClientFactory.GetClient(ctx1, svc) grpcGenericClient, err := s.grpcGenericClientFactory.GetClient(ctx1, svr)
if err != nil { if err != nil {
zlog.Error(err) zlog.Error(err)
c.JSON(http.StatusForbidden, resp.Error(err.Error())) c.JSON(http.StatusForbidden, resp.Error(err.Error()))

20
pkg/aside/trade_instance_client.go

@ -5,7 +5,6 @@ import (
"sig-pub/api/pb" "sig-pub/api/pb"
"sig-pub/pkg/data/entity" "sig-pub/pkg/data/entity"
"sig-pub/pkg/mapping" "sig-pub/pkg/mapping"
"sig-pub/pkg/types"
"sig-pub/pkg/utils/kvcache" "sig-pub/pkg/utils/kvcache"
"time" "time"
@ -17,14 +16,14 @@ import (
// todo nats 更新监听 更新缓存 // todo nats 更新监听 更新缓存
// grpc trade instance client // grpc trade instance client
type TradeInstanceAside struct { type TradeInstanceAside struct {
client pb.MarketClient marketClient pb.MarketServiceClient
cache *kvcache.KVCache[*entity.TradeInstance] cache *kvcache.KVCache[*entity.TradeInstance]
cacheSf singleflight.Group cacheSf singleflight.Group
} }
func NewTradeInstanceAside(client pb.MarketClient) *TradeInstanceAside { func NewTradeInstanceAside(marketClient pb.MarketServiceClient) *TradeInstanceAside {
return &TradeInstanceAside{ return &TradeInstanceAside{
client: client, marketClient: marketClient,
cache: kvcache.NewExpireStore[*entity.TradeInstance]( cache: kvcache.NewExpireStore[*entity.TradeInstance](
time.Minute, time.Minute,
cache.WithShards(16), cache.WithShards(16),
@ -52,7 +51,7 @@ func (c *TradeInstanceAside) getTradeInstance0(ctx context.Context, instId strin
return return
} }
// grpc 获取 // grpc 获取
rsp, err := c.client.GetTradeInstance(ctx, &pb.ReqGetTradeInstance{InstId: instId}) rsp, err := c.marketClient.GetTradeInstance(ctx, &pb.ReqGetTradeInstance{InstId: instId})
if err != nil { if err != nil {
return return
} }
@ -62,14 +61,9 @@ func (c *TradeInstanceAside) getTradeInstance0(ctx context.Context, instId strin
} }
// ListExchangeTradeInstance 获取交易所支持的交易实例 // ListExchangeTradeInstance 获取交易所支持的交易实例
func (c *TradeInstanceAside) ListExchangeTradeInstance(ctx context.Context, exchange types.Exchange) (exInsts []*entity.TradeInstanceExchange, err error) { func (c *TradeInstanceAside) ListExchangeTradeInstance(ctx context.Context, exchange pb.ExchangeType) (exInsts []*entity.TradeInstanceExchange, err error) {
pbExType, err := exchange.Exchange2PB() rsp, err := c.marketClient.ListExchangeTradeInstance(ctx, &pb.ReqListExchangeTradeInstance{
if err != nil { Exchanges: []pb.ExchangeType{exchange},
return
}
rsp, err := c.client.ListExchangeTradeInstance(ctx, &pb.ReqListExchangeTradeInstance{
Exchanges: []pb.Exchange{pbExType},
}) })
if err != nil { if err != nil {
return return

6
pkg/config/config.go

@ -17,6 +17,8 @@ type Configuration struct {
Grpc GrpcConfig Grpc GrpcConfig
Etcd clientv3.Config Etcd clientv3.Config
// Consul api.Config
Consul ConsulConfig
Database Database Database Database
Tsdb TsdbConfig Tsdb TsdbConfig
} }
@ -39,6 +41,10 @@ type GrpcKeepalive struct {
MaxLifeTime string MaxLifeTime string
} }
type ConsulConfig struct {
Address string
}
// ============== Gate ============== // ============== Gate ==============
type GateConf struct { type GateConf struct {
HttpAddr string HttpAddr string

9
pkg/data/common.go

@ -6,10 +6,11 @@ import "errors"
type Status int32 type Status int32
const ( const (
StatusDisabled = 0 StatusDisabled Status = 0
StatusOk = 1 StatusOk Status = 1
StatusProcessing = 2 StatusProcessing Status = 2
StatusDeleted = 4 StatusDeleted Status = 4
StatusFailed Status = 5
) )
var ( var (

173
pkg/grpc/discovery/consul/resolver.go

@ -0,0 +1,173 @@
package consul
import (
"context"
"fmt"
"log"
"net/url"
"sync"
"time"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc/attributes"
"google.golang.org/grpc/resolver"
)
const (
ConsulScheme = "consul"
)
// ConsulBuilder implements resolver.Builder
type ConsulBuilder struct {
client *api.Client
}
func NewConsulBuilder(client *api.Client) *ConsulBuilder {
return &ConsulBuilder{
client: client,
}
}
// Build creates a new resolver for the given target
func (b *ConsulBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
// Parse target: consul://consul-host:port/service-name
targetUrl := target.URL.String()
u, err := url.Parse(targetUrl)
if err != nil {
return nil, fmt.Errorf("invalid target URL: %v", err)
}
if u.Scheme != ConsulScheme {
return nil, fmt.Errorf("invalid scheme, expected %s, got %s", ConsulScheme, u.Scheme)
}
serviceName := u.Path[1:] // Remove leading '/'
if serviceName == "" {
return nil, fmt.Errorf("service name is empty")
}
r := &consulResolver{
client: b.client,
cc: cc,
serviceName: serviceName,
ctx: context.Background(),
cancel: nil,
}
r.ctx, r.cancel = context.WithCancel(r.ctx)
r.wg.Add(1)
go r.watch()
return r, nil
}
// Scheme returns the resolver scheme
func (b *ConsulBuilder) Scheme() string {
return ConsulScheme
}
// consulResolver implements resolver.Resolver
type consulResolver struct {
client *api.Client
cc resolver.ClientConn
serviceName string
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
// ResolveNow triggers a resolution immediately
func (r *consulResolver) ResolveNow(_ resolver.ResolveNowOptions) {
// Fetch service instances from Consul
services, _, err := r.client.Health().Service(r.serviceName, "", true, nil)
if err != nil {
log.Printf("ResolveNow error: %v", err)
// serviceconfig.ParseResult
r.cc.UpdateState(resolver.State{ServiceConfig: r.cc.ParseServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)})
return
}
// Convert Consul services to gRPC addresses
var addrs []resolver.Address
for _, s := range services {
addr := resolver.Address{
Addr: fmt.Sprintf("%s:%d", s.Service.Address, s.Service.Port),
Attributes: attributes.New("service_id", s.Service.ID),
}
addrs = append(addrs, addr)
}
// Update gRPC client connection state
state := resolver.State{
Addresses: addrs,
ServiceConfig: r.cc.ParseServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
}
if err := r.cc.UpdateState(state); err != nil {
log.Printf("Failed to update state: %v", err)
}
}
// Close stops the resolver and cleans up
func (r *consulResolver) Close() {
r.cancel()
r.wg.Wait()
}
// watch polls Consul for service changes
func (r *consulResolver) watch() {
defer r.wg.Done()
var lastIndex uint64
for {
select {
case <-r.ctx.Done():
return
default:
services, meta, err := r.client.Health().Service(r.serviceName, "", true, &api.QueryOptions{WaitIndex: lastIndex})
if err != nil {
log.Printf("Watch error: %v", err)
time.Sleep(1 * time.Second)
continue
}
lastIndex = meta.LastIndex
var addrs []resolver.Address
for _, s := range services {
addr := resolver.Address{
Addr: fmt.Sprintf("%s:%d", s.Service.Address, s.Service.Port),
Attributes: attributes.New("service_id", s.Service.ID),
}
addrs = append(addrs, addr)
}
state := resolver.State{
Addresses: addrs,
ServiceConfig: r.cc.ParseServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
}
if err := r.cc.UpdateState(state); err != nil {
log.Printf("Failed to update state: %v", err)
}
time.Sleep(1 * time.Second) // Avoid tight loop
}
}
}
// func init() {
// resolver.Register(&consulBuilder{})
// }
// func main() {
// // Dial gRPC service using Consul resolver
// conn, err := grpc.NewClient(
// "consul://localhost:8500/greeter",
// grpc.WithInsecure(), // Use WithTransportCredentials for TLS in production
// grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
// )
// if err != nil {
// log.Fatalf("Failed to dial: %v", err)
// }
// defer conn.Close()
// client := pb.NewGreeterClient(conn)
// resp, err := client.SayHello(context.Background(), &pb.HelloRequest{Name: "World"})
// if err != nil {
// log.Fatalf("SayHello failed: %v", err)
// }
// log.Printf("Response: %s", resp.Message)
// }

76
pkg/grpc/discovery/consul_naming.go

@ -0,0 +1,76 @@
package discovery
import (
"errors"
"fmt"
"sig-pub/pkg/grpc/discovery/consul"
"sig-pub/pkg/zlog"
"strings"
"github.com/hashicorp/consul/api"
"google.golang.org/grpc"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/resolver"
)
const (
ConsulSchema = "consul"
)
func ConsulDialUrl(svrName string) string {
url := fmt.Sprintf("%s:///%s", ConsulSchema, svrName)
return url
}
type ConsulDiscovery struct {
client *api.Client
}
func NewConsulDiscovery(client *api.Client) *ConsulDiscovery {
return &ConsulDiscovery{
client: client,
}
}
func (r *ConsulDiscovery) Registry(grpcServer grpc.ServiceRegistrar, register Server) (err error) {
if strings.Trim(register.Name, " ") == "" {
err = errors.New("registry name is empty")
return
}
// 健康检查服务
healthSrv := NewHealthServer()
grpc_health_v1.RegisterHealthServer(grpcServer, healthSrv)
// 服务注册
ip, port, err := RegisterIpPort(register.Addr)
if err != nil {
panic(err)
}
reg := &api.AgentServiceRegistration{
ID: fmt.Sprintf("%s-%d", register.Name, register.NodeId), // 唯一 ID
Name: register.Name, // 服务名
Port: port,
Tags: []string{"v1", "grpc"},
Address: ip,
// Weights: &api.AgentWeights{},
Check: &api.AgentServiceCheck{
CheckID: fmt.Sprintf("%s-%d-check", register.Name, register.NodeId),
GRPC: fmt.Sprintf("%s:%d/grpc.health.v1.Health", ip, port), // gRPC 健康检查
Interval: "10s",
Timeout: "3s",
DeregisterCriticalServiceAfter: "30s",
},
}
if err = r.client.Agent().ServiceRegister(reg); err != nil {
zlog.Errorf("registry service %s error: %v", register.Name, err)
return
}
zlog.Infof("service %s registered to consul", register.Name)
return
}
func (r *ConsulDiscovery) Resolver() (builder resolver.Builder) {
builder = consul.NewConsulBuilder(r.client)
return
}

1
pkg/grpc/discovery/discovery.go

@ -36,6 +36,7 @@ type Discovery interface {
type Server struct { type Server struct {
Name string `json:"name"` Name string `json:"name"`
Addr string `json:"addr"` // 地址 Addr string `json:"addr"` // 地址
NodeId int `json:"nodeId"`
Attrs map[string]string `json:"attrs"` // attributes Attrs map[string]string `json:"attrs"` // attributes
} }

4
pkg/grpc/discovery/etcd_naming.go

@ -76,6 +76,10 @@ func (r *EtcdDiscovery) Registry(ctx context.Context, server Server) (err error)
}, },
clientv3.WithLease(lease.ID), clientv3.WithLease(lease.ID),
) )
if err != nil {
zlog.Error("registry AddEndpoint error: ", err)
return
}
// keepalive lease // keepalive lease
keepCtx, keepCancel := context.WithCancel(context.Background()) keepCtx, keepCancel := context.WithCancel(context.Background())

23
pkg/grpc/discovery/health_server.go

@ -0,0 +1,23 @@
package discovery
import (
"context"
"google.golang.org/grpc/health/grpc_health_v1"
)
// HealthServer 实现健康检查
type HealthServer struct {
grpc_health_v1.UnimplementedHealthServer
// healthy bool
}
func NewHealthServer() *HealthServer {
return &HealthServer{}
}
func (s *HealthServer) Check(ctx context.Context, req *grpc_health_v1.HealthCheckRequest) (*grpc_health_v1.HealthCheckResponse, error) {
return &grpc_health_v1.HealthCheckResponse{
Status: grpc_health_v1.HealthCheckResponse_SERVING, // 或 NOT_SERVING
}, nil
}

44
pkg/indicator/rsi.go

@ -0,0 +1,44 @@
package indicator
import (
"fmt"
"sig-pub/pkg/types"
"sig-pub/pkg/types/series"
"github.com/spf13/cast"
)
// RSI: 相对强弱指数 (RSI)
// rsi define: https://www.investopedia.com/terms/r/rsi.asp
type RSI struct {
series.Series
values series.Floats
prices series.Floats
baseline int32
}
func NewRSI(baseline int32) *RSI {
return &RSI{
baseline: baseline,
}
}
func (ind *RSI) Init(prams map[string]any) {
cast.ToIntE("1")
}
func (ind *RSI) Update(klines []types.Kline) (err error) {
for _, kline := range klines {
c, ok := kline.Close.Float64()
if !ok {
err = fmt.Errorf("kline close to float64 error: %s", kline.Close.String())
return
}
ind.prices.Push(c)
}
diff := ind.prices.Diff()
_ = diff
return
}

4
pkg/mapping/market.go

@ -32,7 +32,7 @@ func ExchangeInstance2Proto(exInst *entity.TradeInstanceExchange) (pbExInst *pb.
ExchangeInstId: exInst.ExchangeInstId, ExchangeInstId: exInst.ExchangeInstId,
InstId: exInst.InstId, InstId: exInst.InstId,
Status: exInst.Status, Status: exInst.Status,
Exchange: exInst.Exchange, Exchange: pb.ExchangeType(exInst.Exchange),
UpdateBy: exInst.UpdateBy, UpdateBy: exInst.UpdateBy,
UpdateTime: exInst.UpdateTime, UpdateTime: exInst.UpdateTime,
} }
@ -66,7 +66,7 @@ func Proto2ExchangeTradeInstance(pbExInst *pb.TradeInstanceExchange) (exInst *en
ExchangeInstId: pbExInst.ExchangeInstId, ExchangeInstId: pbExInst.ExchangeInstId,
InstId: pbExInst.InstId, InstId: pbExInst.InstId,
Status: pbExInst.Status, Status: pbExInst.Status,
Exchange: pbExInst.Exchange, Exchange: int32(pbExInst.Exchange),
UpdateBy: pbExInst.UpdateBy, UpdateBy: pbExInst.UpdateBy,
UpdateTime: pbExInst.UpdateTime, UpdateTime: pbExInst.UpdateTime,
} }

12
pkg/storage/tsdb/victoria_metrics/metric.go

@ -55,12 +55,12 @@ func Kline2Metrics(inst types.TradeInstance, klines []*types.Kline) (metrics []*
ms, ok := instMetrics[inst.InstId] ms, ok := instMetrics[inst.InstId]
if !ok || (rawValueLimit > 0 && len(ms[0].Values) >= rawValueLimit) { if !ok || (rawValueLimit > 0 && len(ms[0].Values) >= rawValueLimit) {
ms = [6]*Metric{ ms = [6]*Metric{
NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", string(inst.Exchange), "kind", "open"), NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", inst.Exchange.String(), "kind", "open"),
NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", string(inst.Exchange), "kind", "high"), NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", inst.Exchange.String(), "kind", "high"),
NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", string(inst.Exchange), "kind", "low"), NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", inst.Exchange.String(), "kind", "low"),
NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", string(inst.Exchange), "kind", "close"), NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", inst.Exchange.String(), "kind", "close"),
NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", string(inst.Exchange), "kind", "vol"), NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", inst.Exchange.String(), "kind", "vol"),
NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", string(inst.Exchange), "kind", "volQuote"), NewMetric(inst.InstId, "interval", string(kline.Interval), "exchange", inst.Exchange.String(), "kind", "volQuote"),
} }
instMetrics[inst.InstId] = ms instMetrics[inst.InstId] = ms
for i := range len(ms) { for i := range len(ms) {

2
pkg/storage/tsdb/victoria_metrics/vm.go

@ -100,7 +100,7 @@ func (vm *VictoriaMetricsTSDB) GetRangeKline(inst types.TradeInstance, interval
return return
} }
match := fmt.Sprintf("%s{interval=\"%s\"}", inst.InstId, interval) match := fmt.Sprintf("%s{exchange=\"%s\", interval=\"%s\"}", inst.InstId, inst.Exchange.String(), interval)
params := fmt.Sprintf("start=%d&end=%d&match[]=%s", start, end, url.QueryEscape(match)) params := fmt.Sprintf("start=%d&end=%d&match[]=%s", start, end, url.QueryEscape(match))
resp, err := http.Get(fmt.Sprintf("%s/api/v1/export?%s", vm.addr, params)) resp, err := http.Get(fmt.Sprintf("%s/api/v1/export?%s", vm.addr, params))
if err != nil { if err != nil {

3
pkg/storage/tsdb/victoria_metrics/vm_test.go

@ -1,6 +1,7 @@
package vmts package vmts
import ( import (
"sig-pub/api/pb"
"sig-pub/pkg/config" "sig-pub/pkg/config"
"sig-pub/pkg/types" "sig-pub/pkg/types"
"testing" "testing"
@ -13,7 +14,7 @@ func TestGetRangeKline(t *testing.T) {
vmdb := NewVictoriaMetricsTSDB(config.VictoriaMetricsConfig{Addr: test_addr}) vmdb := NewVictoriaMetricsTSDB(config.VictoriaMetricsConfig{Addr: test_addr})
inst := types.TradeInstance{ inst := types.TradeInstance{
InstId: "BTC_USDT", InstId: "BTC_USDT",
Exchange: types.ExchangeOKX, Exchange: pb.ExchangeType_OKX,
} }
vmdb.GetRangeKline(inst, types.Interval1m, 1753368047691, time.Now().UnixMilli()) vmdb.GetRangeKline(inst, types.Interval1m, 1753368047691, time.Now().UnixMilli())

36
pkg/types/exchange.go

@ -1,36 +0,0 @@
package types
import (
"fmt"
"sig-pub/api/pb"
)
type Exchange string
var (
ExchangeSIG = Exchange(pb.Exchange_SIG.String())
ExchangeOKX = Exchange(pb.Exchange_OKX.String()) // okx
ExchangeBINANCE = Exchange(pb.Exchange_BINANCE.String()) // 币安
)
func (ex Exchange) Exchange2PB() (pb.Exchange, error) {
switch ex {
case ExchangeOKX:
return pb.Exchange_OKX, nil
case ExchangeBINANCE:
return pb.Exchange_BINANCE, nil
default:
return pb.Exchange_SIG, fmt.Errorf("unknown exchange: %v", ex)
}
}
func ExchangePBParse(pbex pb.Exchange) (Exchange, bool) {
switch pbex {
case pb.Exchange_OKX:
return ExchangeOKX, true
case pb.Exchange_BINANCE:
return ExchangeBINANCE, true
default:
return ExchangeSIG, false
}
}

4
pkg/types/indicator.go

@ -15,10 +15,10 @@ type Indicator interface {
Meta() IndicatorMeta Meta() IndicatorMeta
// init id, 计算完成 publish 时使用id, // init id, 计算完成 publish 时使用id,
// exchange: 封装indicator访问, 封装历史k线访问 // exchange: 封装indicator访问, 封装历史k线访问
// args: 动态指标参数, 执行时创建 // args: 动态指标参数定义, 执行时创建
Init(indId int64, exchange any, args map[string]any) Init(indId int64, exchange any, args map[string]any)
Dependes() []IndicatorDef // 需要订阅的指标列表(包括k线, 实时k线/关闭k线) Dependes() []IndicatorDef // 需要订阅的指标列表(包括k线, 实时k线/关闭k线)
OnKline(Kline) // 驱动k线数据, 待驱动k线到达后, 再待subscribe计算完成后执行 OnKline([]Kline) // 驱动k线数据, 待驱动k线到达后, 再待subscribe计算完成后执行
Emit(func(klineTs int64, indicators map[string]decimal.Decimal)) // 指标计算完成后发送, 由指标执行器进行存储或分发 Emit(func(klineTs int64, indicators map[string]decimal.Decimal)) // 指标计算完成后发送, 由指标执行器进行存储或分发
} }

4
pkg/types/instance.go

@ -1,5 +1,7 @@
package types package types
import "sig-pub/api/pb"
// TradeInstance 交易产品 // TradeInstance 交易产品
type TradeInstance struct { type TradeInstance struct {
InstId string // 交易产品系统id InstId string // 交易产品系统id
@ -7,5 +9,5 @@ type TradeInstance struct {
QuantitySz int32 // 交易量精度 QuantitySz int32 // 交易量精度
Status int32 // 交易所交易产品状态 Status int32 // 交易所交易产品状态
ExchangeInstId string // 交易所交易产品id ExchangeInstId string // 交易所交易产品id
Exchange Exchange // 当前处理交易产品交易所 Exchange pb.ExchangeType // 当前处理交易产品交易所
} }

95
pkg/types/interval.go

@ -1,6 +1,10 @@
package types package types
import "time" import (
"sig-pub/pkg/zlog"
"sort"
"time"
)
var LossEmoji = "🔥" var LossEmoji = "🔥"
var ProfitEmoji = "💰" var ProfitEmoji = "💰"
@ -16,31 +20,7 @@ func (i Interval) AddMul(ts, mul int64) (int64, bool) {
return c(ts, mul), true return c(ts, mul), true
} }
// func (i Interval) Minutes() (int64, bool) { const (
// c, ok := SupportedIntervals[i]
// if !ok || c <= 0 {
// return c, false
// }
// return c / 60, true
// }
// func (i Interval) Seconds() (int64, bool) {
// m, ok := SupportedIntervals[i]
// if !ok || m <= 0 {
// return m, false
// }
// return m, true
// }
// func (i Interval) Milliseconds() (int64, bool) {
// m, ok := SupportedIntervals[i]
// if !ok || m <= 0 {
// return m, false
// }
// return m * 1000, true
// }
var (
Interval1s = Interval("1s") Interval1s = Interval("1s")
Interval1m = Interval("1m") Interval1m = Interval("1m")
Interval3m = Interval("3m") Interval3m = Interval("3m")
@ -97,3 +77,66 @@ var SupportedIntervals = IntervalMap{
Interval1mo: func(ts, mul int64) (ret int64) { return time.UnixMilli(ts).AddDate(0, int(mul), 0).UnixMilli() }, Interval1mo: func(ts, mul int64) (ret int64) { return time.UnixMilli(ts).AddDate(0, int(mul), 0).UnixMilli() },
Interval3mo: func(ts, mul int64) (ret int64) { return time.UnixMilli(ts).AddDate(0, int(3*mul), 0).UnixMilli() }, Interval3mo: func(ts, mul int64) (ret int64) { return time.UnixMilli(ts).AddDate(0, int(3*mul), 0).UnixMilli() },
} }
var (
iotasIntervals []Interval
intervalIotaMax int
intervalIotas = map[Interval]int{}
)
func init() {
iotasIntervals = make([]Interval, len(SupportedIntervals))
var tss = make([]int64, len(SupportedIntervals))
var i = 0
for interval, adder := range SupportedIntervals {
iotasIntervals[i] = interval
tss[i] = adder(0, 1)
i++
}
sort.Slice(iotasIntervals, func(i, j int) bool {
return tss[i] < tss[j]
})
for i, interval := range iotasIntervals {
index := i + 1 // 0保留
intervalIotas[interval] = index
intervalIotaMax = max(intervalIotaMax, index)
}
zlog.Debugf("init intervals: %#v", iotasIntervals)
zlog.Debugf("init intervalsIotas: %#v", intervalIotas)
}
type IntervalState[T any] struct {
state []T
}
func NewIntervalState[T any]() *IntervalState[T] {
return &IntervalState[T]{
state: make([]T, intervalIotaMax+1),
}
}
func (s *IntervalState[T]) Get(interval Interval) T {
i := intervalIotas[interval]
return s.state[i]
}
func (s *IntervalState[T]) Set(interval Interval, v T) {
i := intervalIotas[interval]
s.state[i] = v
}
func (s *IntervalState[T]) Range(f func(i int, interval Interval, v T)) {
for i, interval := range iotasIntervals {
index := i + 1 // 0保留
v := s.state[index]
f(i, interval, v)
}
}
func (s *IntervalState[T]) SetIf(interval Interval, v T, cond func(old T) bool) {
i := intervalIotas[interval]
old := s.state[i]
if cond(old) {
s.state[i] = v
}
}

4
pkg/types/kline.go

@ -25,7 +25,7 @@ type Kline struct {
// okx.CandleData{[]string{"1746842757000", "0.20545", "0.20545", "0.2054", "0.2054", "7.57", "7570", "1555.2542", "1"}} // okx.CandleData{[]string{"1746842757000", "0.20545", "0.20545", "0.2054", "0.2054", "7.57", "7570", "1555.2542", "1"}}
func (k *Kline) ParsePBKline(exchange pb.Exchange, kline *pb.Kline) { func (k *Kline) ParsePBKline(exchange pb.ExchangeType, kline *pb.Kline) {
// k.Exchange = exchange.String() // k.Exchange = exchange.String()
k.Interval = Interval(kline.Interval) k.Interval = Interval(kline.Interval)
k.Ts = kline.Ts k.Ts = kline.Ts
@ -56,6 +56,6 @@ func (k *Kline) ToPBKline() (kline *pb.Kline) {
// ChannelKline k线订阅消息 // ChannelKline k线订阅消息
type ChannelKline struct { type ChannelKline struct {
ExgInstId string `json:"instId"` // 交易所交易产品id,如 BTC_USDT_SWAP ExgInstId string `json:"instId"` // 交易所交易产品id,如 BTC_USDT_SWAP
Exchange Exchange `json:"exchange"` // 交易所 Exchange pb.ExchangeType `json:"exchange"` // 交易所
Klines []*Kline `json:"klines"` Klines []*Kline `json:"klines"`
} }

33
pkg/types/series/decimals.go

@ -0,0 +1,33 @@
package series
import "github.com/govalues/decimal"
// 值列表
type Decimals []decimal.Decimal
func NewDecimals(a ...decimal.Decimal) Decimals {
return Decimals(a)
}
func (s *Decimals) Push(v decimal.Decimal) {
*s = append(*s, v)
}
func (s *Decimals) Append(vs ...decimal.Decimal) {
*s = append(*s, vs...)
}
func (s Decimals) Diff() (values Decimals, err error) {
var r decimal.Decimal
for i, v := range s {
if i == 0 {
values.Push(decimal.Zero)
continue
}
if r, err = v.Sub(s[i-1]); err != nil {
return
}
values.Push(r)
}
return
}

26
pkg/types/series/floats.go

@ -0,0 +1,26 @@
package series
type Floats []float64
func NewFloats(a ...float64) Floats {
return Floats(a)
}
func (s *Floats) Push(v float64) {
*s = append(*s, v)
}
func (s *Floats) Append(vs ...float64) {
*s = append(*s, vs...)
}
func (s Floats) Diff() (values Floats) {
for i, v := range s {
if i == 0 {
values.Push(0)
continue
}
values.Push(v - s[i-1])
}
return values
}

10
pkg/types/series/series.go

@ -0,0 +1,10 @@
package series
type Series interface {
Last(i int) float64
Index(i int) float64
Length() int
}
type Series0 struct {
}

27
pkg/utils/collect/collect.go

@ -1,6 +1,9 @@
package collect package collect
import "sort" import (
"cmp"
"sort"
)
func In[T comparable](value T, values ...T) bool { func In[T comparable](value T, values ...T) bool {
if len(values) == 0 { if len(values) == 0 {
@ -69,17 +72,23 @@ func Sum[T int32 | int64 | int](nums []T) T {
return sum return sum
} }
func Max[T int32 | int64 | int](nums []T, dv T) T { func MustMax[T any, C cmp.Ordered](slice []T, compare func(T) C) (max T) {
if len(nums) == 0 { if len(slice) == 0 {
return dv panic("MustMax slice length 0")
}
max = slice[0]
if len(slice) == 1 {
return
} }
var max T = nums[0]
for i := 1; i < len(nums); i++ { maxC := compare(max)
if nums[i] > max { for i := 1; i < len(slice); i++ {
max = nums[i] if c := compare(slice[i]); cmp.Compare(c, maxC) > 0 {
max = slice[i]
maxC = c
} }
} }
return max return
} }
func Slice2Map[T any, K comparable](slice []T, k func(int, T) K) map[K]T { func Slice2Map[T any, K comparable](slice []T, k func(int, T) K) map[K]T {

14
pkg/utils/nets/ip_test.go

@ -1,6 +1,9 @@
package nets package nets
import "testing" import (
"fmt"
"testing"
)
func TestIpConvert(t *testing.T) { func TestIpConvert(t *testing.T) {
ip := "192.168.1.110" ip := "192.168.1.110"
@ -26,3 +29,12 @@ func TestAddressConvert(t *testing.T) {
t.Error("address parse error") t.Error("address parse error")
} }
} }
func TestGetHostIpv4(t *testing.T) {
ip, err := GetHostIpv4()
if err != nil {
t.Error(err)
return
}
fmt.Println(ip)
}

6
pkg/zlog/exported.go

@ -1,5 +1,11 @@
package zlog package zlog
func Debug(args ...any) {
zSugar.Debug(args...)
}
func Debugf(format string, args ...any) {
zSugar.Debugf(format, args...)
}
func Info(args ...any) { func Info(args ...any) {
zSugar.Info(args...) zSugar.Info(args...)
} }

Loading…
Cancel
Save