Merge remote-tracking branch 'refs/remotes/origin/main'

This commit is contained in:
王性驊 2025-08-08 18:38:35 +08:00
commit 4591d0eff8
15 changed files with 113 additions and 23 deletions

View File

@ -99,8 +99,8 @@ issues:
- gocognit - gocognit
- contextcheck - contextcheck
# exclude-dirs: exclude-dirs:
# - internal/logic - internal/lib/cassandra
exclude-files: exclude-files:
- .*_test.go - .*_test.go

View File

@ -1,5 +1,3 @@
version: '3'
services: services:
docker-etcd: docker-etcd:
hostname: etcd hostname: etcd

View File

@ -1,5 +1,6 @@
Name: blockchain.rpc Name: blockchain.rpc
ListenOn: 0.0.0.0:8888 ListenOn: 0.0.0.0:8888
Timeout: 10000
Etcd: Etcd:
Hosts: Hosts:
- localhost:2379 - localhost:2379
@ -8,7 +9,7 @@ Binance:
Key: "" Key: ""
Secret: "" Secret: ""
TestMode: true TestMode: true
WorkerSize: 10 WorkerSize: 20
RedisCluster: RedisCluster:
Host: 127.0.0.1:6379 Host: 127.0.0.1:6379
@ -18,7 +19,7 @@ Cassandra:
Hosts: Hosts:
- 127.0.0.1 - 127.0.0.1
Port: 9042 Port: 9042
Keyspace: sccflex Keyspace: digimon
UseAuth: true UseAuth: true
Username: cassandra Username: cassandra
Password: cassandra Password: cassandra

View File

@ -0,0 +1 @@
create keyspace kline with replication = {'class': 'SimpleStrategy', 'replication_factor': 1};

View File

@ -1,9 +1,10 @@
package config package config
import ( import (
"time"
"github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/zrpc" "github.com/zeromicro/go-zero/zrpc"
"time"
) )
type Config struct { type Config struct {

View File

@ -4,4 +4,7 @@ import "code.30cm.net/digimon/library-go/errs"
const CodeBlockchain uint32 = 10 const CodeBlockchain uint32 = 10
const FailedToGetSymbolFormBinanceErrorCode errs.ErrorCode = 1 const (
FailedToGetSymbolFormBinanceErrorCode errs.ErrorCode = 1
FailedToUpsertBinanceErrorCode errs.ErrorCode = 2
)

View File

@ -13,9 +13,12 @@ type Kline struct {
TakerBuyBaseAssetVolume string `csv:"taker_buy_base_asset_volume" db:"taker_buy_base_asset_volume" cql:"taker_buy_base_asset_volume"` // 主動買入成交量 TakerBuyBaseAssetVolume string `csv:"taker_buy_base_asset_volume" db:"taker_buy_base_asset_volume" cql:"taker_buy_base_asset_volume"` // 主動買入成交量
TakerBuyQuoteAssetVolume string `csv:"taker_buy_quote_asset_volume" db:"taker_buy_quote_asset_volume" cql:"taker_buy_quote_asset_volume"` // 主動買入成交額 TakerBuyQuoteAssetVolume string `csv:"taker_buy_quote_asset_volume" db:"taker_buy_quote_asset_volume" cql:"taker_buy_quote_asset_volume"` // 主動買入成交額
Symbol string `db:"symbol" partition_key:"true" cql:"symbol"` // 交易對partition key Symbol string `db:"symbol" partition_key:"true" cql:"symbol"` // 交易對partition key
Interval string `db:"interval" partition_key:"true" cql:"interval"` // K 線時間區間partition key // 12h,15m,1d,1h,1m,1s,2h,30m,3m,4h,5m,6h,8h // K 線時間區間partition key // 12h,15m,1d,1h,1m,1s,2h,30m,3m,4h,5m,6h,8h
Interval string `db:"interval" partition_key:"true" cql:"interval"`
} }
func (s *Kline) TableName() string { func (s *Kline) TableName() string {
return "symbol" return "kline"
} }
// todo 未來在分表,每一個交易幣兌一個表

View File

@ -7,10 +7,10 @@ import (
type DataSourceRepository interface { type DataSourceRepository interface {
GetSymbols(ctx context.Context) ([]*entity.Symbol, error) GetSymbols(ctx context.Context) ([]*entity.Symbol, error)
KlineDownloader Downloader
} }
type KlineDownloader interface { type Downloader interface {
// FetchHistoryKline 抓歷史 K 線資料 // FetchHistoryKline 抓歷史 K 線資料
FetchHistoryKline(ctx context.Context, param QueryKline) ([]*entity.Kline, error) FetchHistoryKline(ctx context.Context, param QueryKline) ([]*entity.Kline, error)
SaveHistoryKline(ctx context.Context, data []*entity.Kline) error SaveHistoryKline(ctx context.Context, data []*entity.Kline) error

View File

@ -6,6 +6,7 @@ import (
type DataSourceUseCase interface { type DataSourceUseCase interface {
GetSymbols(ctx context.Context) ([]*Symbol, error) GetSymbols(ctx context.Context) ([]*Symbol, error)
UpsertKline(ctx context.Context, data QueryKline) error
} }
// Symbol 代表交易對資訊 // Symbol 代表交易對資訊
@ -17,3 +18,10 @@ type Symbol struct {
QuoteAsset string `json:"quote_asset"` // 報價幣種(如 BTCUSDT 的 USDT QuoteAsset string `json:"quote_asset"` // 報價幣種(如 BTCUSDT 的 USDT
QuoteAssetPrecision int `json:"quote_asset_precision"` // 報價資產顯示的小數位數 QuoteAssetPrecision int `json:"quote_asset_precision"` // 報價資產顯示的小數位數
} }
type QueryKline struct {
Symbol string
Interval string
StartUnixNano int64
EndUnixNano int64
}

View File

@ -0,0 +1 @@
package websocket_manager

View File

@ -1,7 +1,9 @@
package blockchainservicelogic package blockchainservicelogic
import ( import (
"blockchain/internal/domain/usecase"
"context" "context"
"time"
app_cloudep_blockchain "blockchain/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain" app_cloudep_blockchain "blockchain/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain"
"blockchain/internal/svc" "blockchain/internal/svc"
@ -24,5 +26,15 @@ func NewPingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *PingLogic {
} }
func (l *PingLogic) Ping(_ *app_cloudep_blockchain.NoneReq) (*app_cloudep_blockchain.OKResp, error) { func (l *PingLogic) Ping(_ *app_cloudep_blockchain.NoneReq) (*app_cloudep_blockchain.OKResp, error) {
err := l.svcCtx.BinanceDataSource.UpsertKline(l.ctx, usecase.QueryKline{
Symbol: "BTCUSDT",
Interval: "1m",
StartUnixNano: time.Date(2024, 8, 1, 0, 0, 0, 0, time.UTC).UnixNano(),
EndUnixNano: time.Date(2025, 8, 2, 0, 0, 0, 0, time.UTC).UnixNano(),
})
if err != nil {
return nil, err
}
return &app_cloudep_blockchain.OKResp{}, nil return &app_cloudep_blockchain.OKResp{}, nil
} }

View File

@ -30,9 +30,10 @@ import (
) )
type BinanceRepositoryParam struct { type BinanceRepositoryParam struct {
Conf *config.Binance Conf *config.Binance
Redis *redis.Redis Redis *redis.Redis
DB *cassandra.CassandraDB DB *cassandra.CassandraDB
KeySpace string
} }
type BinanceRepository struct { type BinanceRepository struct {
@ -61,6 +62,7 @@ func MustBinanceRepository(param BinanceRepositoryParam) repository.DataSourceRe
barrier: syncx.NewSingleFlight(), barrier: syncx.NewSingleFlight(),
workerSize: param.Conf.WorkerSize, workerSize: param.Conf.WorkerSize,
workers: workers, workers: workers,
KeySpace: param.KeySpace,
} }
} }
@ -173,11 +175,32 @@ func (repo *BinanceRepository) FetchHistoryKline(ctx context.Context, param repo
} }
func (repo *BinanceRepository) SaveHistoryKline(ctx context.Context, data []*entity.Kline) error { func (repo *BinanceRepository) SaveHistoryKline(ctx context.Context, data []*entity.Kline) error {
ch := make(chan struct{}, repo.workerSize)
var wg sync.WaitGroup
var errList []error
var mu sync.Mutex
for _, item := range data { for _, item := range data {
err := repo.db.Insert(ctx, item, repo.KeySpace) wg.Add(1)
if err != nil { ch <- struct{}{} // block if max concurrency reached
logx.Errorf("failed to insert data: %v", item)
} go func(k *entity.Kline) {
defer wg.Done()
defer func() { <-ch }()
if err := repo.db.Insert(ctx, k, repo.KeySpace); err != nil {
mu.Lock()
errList = append(errList, err)
mu.Unlock()
logx.Errorf("failed to insert data: %v", err)
}
}(item)
}
wg.Wait()
if len(errList) > 0 {
return fmt.Errorf("insert errors: %v", errList)
} }
return nil return nil

View File

@ -28,9 +28,10 @@ func NewServiceContext(c config.Config) *ServiceContext {
} }
binanceRepo := repo.MustBinanceRepository(repo.BinanceRepositoryParam{ binanceRepo := repo.MustBinanceRepository(repo.BinanceRepositoryParam{
Conf: &c.Binance, Conf: &c.Binance,
Redis: newRedis, Redis: newRedis,
DB: cassandra, DB: cassandra,
KeySpace: c.Cassandra.Keyspace,
}) })
return &ServiceContext{ return &ServiceContext{

View File

@ -28,7 +28,6 @@ func MustBinanceUseCase(param BinanceUseCaseParam) usecase.DataSourceUseCase {
func (use *BinanceUseCase) GetSymbols(ctx context.Context) ([]*usecase.Symbol, error) { func (use *BinanceUseCase) GetSymbols(ctx context.Context) ([]*usecase.Symbol, error) {
result, err := use.BinanceRepo.GetSymbols(ctx) result, err := use.BinanceRepo.GetSymbols(ctx)
if err != nil { if err != nil {
// 錯誤代碼 20-201-04
e := errs.ThirdPartyErrorL( e := errs.ThirdPartyErrorL(
blockchain.CodeBlockchain, blockchain.CodeBlockchain,
blockchain.FailedToGetSymbolFormBinanceErrorCode, blockchain.FailedToGetSymbolFormBinanceErrorCode,
@ -56,3 +55,42 @@ func (use *BinanceUseCase) GetSymbols(ctx context.Context) ([]*usecase.Symbol, e
return rpy, nil return rpy, nil
} }
func (use *BinanceUseCase) UpsertKline(ctx context.Context, data usecase.QueryKline) error {
origianData, err := use.BinanceRepo.FetchHistoryKline(ctx, repository.QueryKline{
Symbol: data.Symbol,
Interval: data.Interval,
StartUnixNano: data.StartUnixNano,
EndUnixNano: data.EndUnixNano,
})
if err != nil {
e := errs.ThirdPartyErrorL(
blockchain.CodeBlockchain,
blockchain.FailedToUpsertBinanceErrorCode,
logx.WithContext(ctx),
[]logx.LogField{
{Key: "func", Value: "BinanceRepo.FetchHistoryKline"},
{Key: "err", Value: err.Error()},
},
"failed to get kline history from binance").Wrap(err)
return e
}
err = use.BinanceRepo.SaveHistoryKline(ctx, origianData)
if err != nil {
e := errs.DatabaseErrorWithScopeL(
blockchain.CodeBlockchain,
blockchain.FailedToUpsertBinanceErrorCode,
logx.WithContext(ctx),
[]logx.LogField{
{Key: "func", Value: "BinanceRepo.SaveHistoryKline"},
{Key: "err", Value: err.Error()},
},
"failed save data from binance").Wrap(err)
return e
}
return nil
}