From 436996d25da04a280bcc0d61bc1cb4323f368318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Mon, 4 Aug 2025 22:02:01 +0800 Subject: [PATCH] add get symbol repository --- .../blockchainservice/blockchain_service.go | 17 +- etc/blockchain.yaml | 8 +- .../app-cloudep-blockchain/blockchain.pb.go | 213 ++++++++++++++++-- .../blockchain_grpc.pb.go | 44 +++- generate/rpc/blockchain.proto | 23 +- go.mod | 1 + go.sum | 2 + internal/config/config.go | 9 +- internal/domain/blockchain/errors.go | 7 + internal/domain/repository/data_source.go | 2 +- internal/domain/usecase/data_source.go | 19 ++ .../blockchainservice/list_symbols_logic.go | 49 ++++ internal/repository/data_source_binance.go | 12 +- .../blockchain_service_server.go | 7 + internal/svc/service_context.go | 30 ++- internal/usecase/binance.go | 59 +++++ 16 files changed, 471 insertions(+), 31 deletions(-) create mode 100644 internal/domain/blockchain/errors.go create mode 100644 internal/domain/usecase/data_source.go create mode 100644 internal/logic/blockchainservice/list_symbols_logic.go create mode 100644 internal/usecase/binance.go diff --git a/client/blockchainservice/blockchain_service.go b/client/blockchainservice/blockchain_service.go index 6636efd..2bc7491 100644 --- a/client/blockchainservice/blockchain_service.go +++ b/client/blockchainservice/blockchain_service.go @@ -14,10 +14,16 @@ import ( ) type ( - NoneReq = app_cloudep_blockchain.NoneReq - OKResp = app_cloudep_blockchain.OKResp + ListSymbolsRequest = app_cloudep_blockchain.ListSymbolsRequest + ListSymbolsResponse = app_cloudep_blockchain.ListSymbolsResponse + NoneReq = app_cloudep_blockchain.NoneReq + OKResp = app_cloudep_blockchain.OKResp + Symbol = app_cloudep_blockchain.Symbol BlockchainService interface { + // ListSymbols retrieves all available trading symbols. + ListSymbols(ctx context.Context, in *ListSymbolsRequest, opts ...grpc.CallOption) (*ListSymbolsResponse, error) + // Ping is a health-check endpoint. Ping(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*OKResp, error) } @@ -32,6 +38,13 @@ func NewBlockchainService(cli zrpc.Client) BlockchainService { } } +// ListSymbols retrieves all available trading symbols. +func (m *defaultBlockchainService) ListSymbols(ctx context.Context, in *ListSymbolsRequest, opts ...grpc.CallOption) (*ListSymbolsResponse, error) { + client := app_cloudep_blockchain.NewBlockchainServiceClient(m.cli.Conn()) + return client.ListSymbols(ctx, in, opts...) +} + +// Ping is a health-check endpoint. func (m *defaultBlockchainService) Ping(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*OKResp, error) { client := app_cloudep_blockchain.NewBlockchainServiceClient(m.cli.Conn()) return client.Ping(ctx, in, opts...) diff --git a/etc/blockchain.yaml b/etc/blockchain.yaml index 85cea11..10be160 100644 --- a/etc/blockchain.yaml +++ b/etc/blockchain.yaml @@ -2,9 +2,13 @@ Name: blockchain.rpc ListenOn: 0.0.0.0:8888 Etcd: Hosts: - - 127.0.0.1:2379 + - 10.0.0.19:2379 Key: blockchain.rpc Binance: Key: "" Secret: "" - TestMode: true \ No newline at end of file + TestMode: true + +RedisCluster: + Host: 127.0.0.1:6379 + Type: node \ No newline at end of file diff --git a/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain.pb.go b/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain.pb.go index a46b30c..c0c0c23 100644 --- a/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain.pb.go +++ b/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain.pb.go @@ -95,6 +95,174 @@ func (*NoneReq) Descriptor() ([]byte, []int) { return file_generate_rpc_blockchain_proto_rawDescGZIP(), []int{1} } +// ListSymbolsRequest is the request for the ListSymbols RPC. +// It is currently empty but can be extended with filtering or pagination fields in the future. +type ListSymbolsRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSymbolsRequest) Reset() { + *x = ListSymbolsRequest{} + mi := &file_generate_rpc_blockchain_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSymbolsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSymbolsRequest) ProtoMessage() {} + +func (x *ListSymbolsRequest) ProtoReflect() protoreflect.Message { + mi := &file_generate_rpc_blockchain_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSymbolsRequest.ProtoReflect.Descriptor instead. +func (*ListSymbolsRequest) Descriptor() ([]byte, []int) { + return file_generate_rpc_blockchain_proto_rawDescGZIP(), []int{2} +} + +// ListSymbolsResponse contains a list of symbols. +type ListSymbolsResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Symbols []*Symbol `protobuf:"bytes,1,rep,name=symbols,proto3" json:"symbols,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListSymbolsResponse) Reset() { + *x = ListSymbolsResponse{} + mi := &file_generate_rpc_blockchain_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListSymbolsResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListSymbolsResponse) ProtoMessage() {} + +func (x *ListSymbolsResponse) ProtoReflect() protoreflect.Message { + mi := &file_generate_rpc_blockchain_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListSymbolsResponse.ProtoReflect.Descriptor instead. +func (*ListSymbolsResponse) Descriptor() ([]byte, []int) { + return file_generate_rpc_blockchain_proto_rawDescGZIP(), []int{3} +} + +func (x *ListSymbolsResponse) GetSymbols() []*Symbol { + if x != nil { + return x.Symbols + } + return nil +} + +// Symbol represents information about a trading pair. +type Symbol struct { + state protoimpl.MessageState `protogen:"open.v1"` + Symbol string `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` // 交易對名稱 (BTCUSDT) + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` // 狀態(如 "TRADING" 表示可交易) + BaseAsset string `protobuf:"bytes,3,opt,name=base_asset,json=baseAsset,proto3" json:"base_asset,omitempty"` // 主幣種(如 BTCUSDT 的 BTC) + BaseAssetPrecision int32 `protobuf:"varint,4,opt,name=base_asset_precision,json=baseAssetPrecision,proto3" json:"base_asset_precision,omitempty"` // 主幣的小數點精度 + QuoteAsset string `protobuf:"bytes,5,opt,name=quote_asset,json=quoteAsset,proto3" json:"quote_asset,omitempty"` // 報價幣種(如 BTCUSDT 的 USDT) + QuoteAssetPrecision int32 `protobuf:"varint,6,opt,name=quote_asset_precision,json=quoteAssetPrecision,proto3" json:"quote_asset_precision,omitempty"` // 報價資產顯示的小數位數 + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Symbol) Reset() { + *x = Symbol{} + mi := &file_generate_rpc_blockchain_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Symbol) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Symbol) ProtoMessage() {} + +func (x *Symbol) ProtoReflect() protoreflect.Message { + mi := &file_generate_rpc_blockchain_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Symbol.ProtoReflect.Descriptor instead. +func (*Symbol) Descriptor() ([]byte, []int) { + return file_generate_rpc_blockchain_proto_rawDescGZIP(), []int{4} +} + +func (x *Symbol) GetSymbol() string { + if x != nil { + return x.Symbol + } + return "" +} + +func (x *Symbol) GetStatus() string { + if x != nil { + return x.Status + } + return "" +} + +func (x *Symbol) GetBaseAsset() string { + if x != nil { + return x.BaseAsset + } + return "" +} + +func (x *Symbol) GetBaseAssetPrecision() int32 { + if x != nil { + return x.BaseAssetPrecision + } + return 0 +} + +func (x *Symbol) GetQuoteAsset() string { + if x != nil { + return x.QuoteAsset + } + return "" +} + +func (x *Symbol) GetQuoteAssetPrecision() int32 { + if x != nil { + return x.QuoteAssetPrecision + } + return 0 +} + var File_generate_rpc_blockchain_proto protoreflect.FileDescriptor const file_generate_rpc_blockchain_proto_rawDesc = "" + @@ -102,8 +270,21 @@ const file_generate_rpc_blockchain_proto_rawDesc = "" + "\x1dgenerate/rpc/blockchain.proto\x12\n" + "blockchain\"\b\n" + "\x06OKResp\"\t\n" + - "\aNoneReq2D\n" + - "\x11BlockchainService\x12/\n" + + "\aNoneReq\"\x14\n" + + "\x12ListSymbolsRequest\"C\n" + + "\x13ListSymbolsResponse\x12,\n" + + "\asymbols\x18\x01 \x03(\v2\x12.blockchain.SymbolR\asymbols\"\xde\x01\n" + + "\x06Symbol\x12\x16\n" + + "\x06symbol\x18\x01 \x01(\tR\x06symbol\x12\x16\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12\x1d\n" + + "\n" + + "base_asset\x18\x03 \x01(\tR\tbaseAsset\x120\n" + + "\x14base_asset_precision\x18\x04 \x01(\x05R\x12baseAssetPrecision\x12\x1f\n" + + "\vquote_asset\x18\x05 \x01(\tR\n" + + "quoteAsset\x122\n" + + "\x15quote_asset_precision\x18\x06 \x01(\x05R\x13quoteAssetPrecision2\x94\x01\n" + + "\x11BlockchainService\x12N\n" + + "\vListSymbols\x12\x1e.blockchain.ListSymbolsRequest\x1a\x1f.blockchain.ListSymbolsResponse\x12/\n" + "\x04Ping\x12\x13.blockchain.NoneReq\x1a\x12.blockchain.OKRespB.Z,code.30cm.net/digimon/app-cloudep-blockchainb\x06proto3" var ( @@ -118,19 +299,25 @@ func file_generate_rpc_blockchain_proto_rawDescGZIP() []byte { return file_generate_rpc_blockchain_proto_rawDescData } -var file_generate_rpc_blockchain_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_generate_rpc_blockchain_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_generate_rpc_blockchain_proto_goTypes = []any{ - (*OKResp)(nil), // 0: blockchain.OKResp - (*NoneReq)(nil), // 1: blockchain.NoneReq + (*OKResp)(nil), // 0: blockchain.OKResp + (*NoneReq)(nil), // 1: blockchain.NoneReq + (*ListSymbolsRequest)(nil), // 2: blockchain.ListSymbolsRequest + (*ListSymbolsResponse)(nil), // 3: blockchain.ListSymbolsResponse + (*Symbol)(nil), // 4: blockchain.Symbol } var file_generate_rpc_blockchain_proto_depIdxs = []int32{ - 1, // 0: blockchain.BlockchainService.Ping:input_type -> blockchain.NoneReq - 0, // 1: blockchain.BlockchainService.Ping:output_type -> blockchain.OKResp - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 4, // 0: blockchain.ListSymbolsResponse.symbols:type_name -> blockchain.Symbol + 2, // 1: blockchain.BlockchainService.ListSymbols:input_type -> blockchain.ListSymbolsRequest + 1, // 2: blockchain.BlockchainService.Ping:input_type -> blockchain.NoneReq + 3, // 3: blockchain.BlockchainService.ListSymbols:output_type -> blockchain.ListSymbolsResponse + 0, // 4: blockchain.BlockchainService.Ping:output_type -> blockchain.OKResp + 3, // [3:5] is the sub-list for method output_type + 1, // [1:3] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_generate_rpc_blockchain_proto_init() } @@ -144,7 +331,7 @@ func file_generate_rpc_blockchain_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_generate_rpc_blockchain_proto_rawDesc), len(file_generate_rpc_blockchain_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 5, NumExtensions: 0, NumServices: 1, }, diff --git a/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain_grpc.pb.go b/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain_grpc.pb.go index 1c454b6..6944943 100644 --- a/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain_grpc.pb.go +++ b/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain/blockchain_grpc.pb.go @@ -19,13 +19,17 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - BlockchainService_Ping_FullMethodName = "/blockchain.BlockchainService/Ping" + BlockchainService_ListSymbols_FullMethodName = "/blockchain.BlockchainService/ListSymbols" + BlockchainService_Ping_FullMethodName = "/blockchain.BlockchainService/Ping" ) // BlockchainServiceClient is the client API for BlockchainService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type BlockchainServiceClient interface { + // ListSymbols retrieves all available trading symbols. + ListSymbols(ctx context.Context, in *ListSymbolsRequest, opts ...grpc.CallOption) (*ListSymbolsResponse, error) + // Ping is a health-check endpoint. Ping(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*OKResp, error) } @@ -37,6 +41,16 @@ func NewBlockchainServiceClient(cc grpc.ClientConnInterface) BlockchainServiceCl return &blockchainServiceClient{cc} } +func (c *blockchainServiceClient) ListSymbols(ctx context.Context, in *ListSymbolsRequest, opts ...grpc.CallOption) (*ListSymbolsResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ListSymbolsResponse) + err := c.cc.Invoke(ctx, BlockchainService_ListSymbols_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *blockchainServiceClient) Ping(ctx context.Context, in *NoneReq, opts ...grpc.CallOption) (*OKResp, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(OKResp) @@ -51,6 +65,9 @@ func (c *blockchainServiceClient) Ping(ctx context.Context, in *NoneReq, opts .. // All implementations must embed UnimplementedBlockchainServiceServer // for forward compatibility. type BlockchainServiceServer interface { + // ListSymbols retrieves all available trading symbols. + ListSymbols(context.Context, *ListSymbolsRequest) (*ListSymbolsResponse, error) + // Ping is a health-check endpoint. Ping(context.Context, *NoneReq) (*OKResp, error) mustEmbedUnimplementedBlockchainServiceServer() } @@ -62,6 +79,9 @@ type BlockchainServiceServer interface { // pointer dereference when methods are called. type UnimplementedBlockchainServiceServer struct{} +func (UnimplementedBlockchainServiceServer) ListSymbols(context.Context, *ListSymbolsRequest) (*ListSymbolsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ListSymbols not implemented") +} func (UnimplementedBlockchainServiceServer) Ping(context.Context, *NoneReq) (*OKResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented") } @@ -86,6 +106,24 @@ func RegisterBlockchainServiceServer(s grpc.ServiceRegistrar, srv BlockchainServ s.RegisterService(&BlockchainService_ServiceDesc, srv) } +func _BlockchainService_ListSymbols_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListSymbolsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BlockchainServiceServer).ListSymbols(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: BlockchainService_ListSymbols_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BlockchainServiceServer).ListSymbols(ctx, req.(*ListSymbolsRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _BlockchainService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(NoneReq) if err := dec(in); err != nil { @@ -111,6 +149,10 @@ var BlockchainService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "blockchain.BlockchainService", HandlerType: (*BlockchainServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "ListSymbols", + Handler: _BlockchainService_ListSymbols_Handler, + }, { MethodName: "Ping", Handler: _BlockchainService_Ping_Handler, diff --git a/generate/rpc/blockchain.proto b/generate/rpc/blockchain.proto index 759ba1d..333e7e5 100644 --- a/generate/rpc/blockchain.proto +++ b/generate/rpc/blockchain.proto @@ -8,6 +8,27 @@ message OKResp {} // NoneReq message NoneReq {} +// ListSymbolsRequest is the request for the ListSymbols RPC. +// It is currently empty but can be extended with filtering or pagination fields in the future. +message ListSymbolsRequest {} + +// ListSymbolsResponse contains a list of symbols. +message ListSymbolsResponse { + repeated Symbol symbols = 1; +} +// Symbol represents information about a trading pair. +message Symbol { + string symbol = 1; // 交易對名稱 (BTCUSDT) + string status = 2; // 狀態(如 "TRADING" 表示可交易) + string base_asset = 3; // 主幣種(如 BTCUSDT 的 BTC) + int32 base_asset_precision = 4; // 主幣的小數點精度 + string quote_asset = 5; // 報價幣種(如 BTCUSDT 的 USDT) + int32 quote_asset_precision = 6; // 報價資產顯示的小數位數 +} + service BlockchainService{ + // ListSymbols retrieves all available trading symbols. + rpc ListSymbols(ListSymbolsRequest) returns(ListSymbolsResponse); + // Ping is a health-check endpoint. rpc Ping(NoneReq) returns(OKResp); -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index a428363..4c5ea26 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( ) require ( + code.30cm.net/digimon/library-go/errs v1.2.14 github.com/adshao/go-binance/v2 v2.8.3 github.com/beorn7/perks v1.0.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect diff --git a/go.sum b/go.sum index d5ebbe1..08eafb8 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +code.30cm.net/digimon/library-go/errs v1.2.14 h1:Un9wcIIjjJW8D2i0ISf8ibzp9oNT4OqLsaSKW0T4RJU= +code.30cm.net/digimon/library-go/errs v1.2.14/go.mod h1:Hs4v7SbXNggDVBGXSYsFMjkii1qLF+rugrIpWePN4/o= github.com/adshao/go-binance/v2 v2.8.3 h1:jwPRcX2u7FIO1pPoXgocyXpXhBI81A41kcmSDzS6uzo= github.com/adshao/go-binance/v2 v2.8.3/go.mod h1:XkkuecSyJKPolaCGf/q4ovJYB3t0P+7RUYTbGr+LMGM= github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI= diff --git a/internal/config/config.go b/internal/config/config.go index c469c10..4bdd9d1 100755 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,10 +1,15 @@ package config -import "github.com/zeromicro/go-zero/zrpc" +import ( + "github.com/zeromicro/go-zero/core/stores/redis" + "github.com/zeromicro/go-zero/zrpc" +) type Config struct { zrpc.RpcServerConf - Binance + Binance Binance + // Redis Cluster + RedisCluster redis.RedisConf } type Binance struct { diff --git a/internal/domain/blockchain/errors.go b/internal/domain/blockchain/errors.go new file mode 100644 index 0000000..75f998b --- /dev/null +++ b/internal/domain/blockchain/errors.go @@ -0,0 +1,7 @@ +package blockchain + +import "code.30cm.net/digimon/library-go/errs" + +const CodeBlockchain uint32 = 10 + +const FailedToGetSymbolFormBinanceErrorCode errs.ErrorCode = 1 diff --git a/internal/domain/repository/data_source.go b/internal/domain/repository/data_source.go index 2600efb..271da25 100644 --- a/internal/domain/repository/data_source.go +++ b/internal/domain/repository/data_source.go @@ -6,5 +6,5 @@ import ( ) type DataSourceRepository interface { - GetSymbols(ctx context.Context) ([]entity.Symbol, error) + GetSymbols(ctx context.Context) ([]*entity.Symbol, error) } diff --git a/internal/domain/usecase/data_source.go b/internal/domain/usecase/data_source.go new file mode 100644 index 0000000..4cb027c --- /dev/null +++ b/internal/domain/usecase/data_source.go @@ -0,0 +1,19 @@ +package usecase + +import ( + "context" +) + +type DataSourceUseCase interface { + GetSymbols(ctx context.Context) ([]*Symbol, error) +} + +// Symbol 代表交易對資訊 +type Symbol struct { + Symbol string `json:"symbol"` // 交易對名稱 (BTCUSDT) + Status string `json:"status"` // 狀態(如 "TRADING" 表示可交易) + BaseAsset string `json:"base_asset"` // 主幣種(如 BTCUSDT 的 BTC) + BaseAssetPrecision int `json:"base_asset_precision"` // 主幣的小數點精度 + QuoteAsset string `json:"quote_asset"` // 報價幣種(如 BTCUSDT 的 USDT) + QuoteAssetPrecision int `json:"quote_asset_precision"` // 報價資產顯示的小數位數 +} diff --git a/internal/logic/blockchainservice/list_symbols_logic.go b/internal/logic/blockchainservice/list_symbols_logic.go new file mode 100644 index 0000000..c3ec708 --- /dev/null +++ b/internal/logic/blockchainservice/list_symbols_logic.go @@ -0,0 +1,49 @@ +package blockchainservicelogic + +import ( + "context" + + app_cloudep_blockchain "blockchain/gen_result/pb/code.30cm.net/digimon/app-cloudep-blockchain" + "blockchain/internal/svc" + + "github.com/zeromicro/go-zero/core/logx" +) + +type ListSymbolsLogic struct { + ctx context.Context + svcCtx *svc.ServiceContext + logx.Logger +} + +func NewListSymbolsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListSymbolsLogic { + return &ListSymbolsLogic{ + ctx: ctx, + svcCtx: svcCtx, + Logger: logx.WithContext(ctx), + } +} + +// ListSymbols retrieves all available trading symbols. +func (l *ListSymbolsLogic) ListSymbols(in *app_cloudep_blockchain.ListSymbolsRequest) (*app_cloudep_blockchain.ListSymbolsResponse, error) { + result, err := l.svcCtx.BinanceDataSource.GetSymbols(l.ctx) + if err != nil { + return nil, err + } + + rpy := make([]*app_cloudep_blockchain.Symbol, 0, len(result)) + + for _, item := range result { + rpy = append(rpy, &app_cloudep_blockchain.Symbol{ + Symbol: item.Symbol, + Status: item.Status, + BaseAsset: item.BaseAsset, + BaseAssetPrecision: int32(item.BaseAssetPrecision), + QuoteAsset: item.QuoteAsset, + QuoteAssetPrecision: int32(item.QuoteAssetPrecision), + }) + } + + return &app_cloudep_blockchain.ListSymbolsResponse{ + Symbols: rpy, + }, nil +} diff --git a/internal/repository/data_source_binance.go b/internal/repository/data_source_binance.go index 58f2a36..ea01ee1 100644 --- a/internal/repository/data_source_binance.go +++ b/internal/repository/data_source_binance.go @@ -41,16 +41,16 @@ func MustBinanceRepository(param BinanceRepositoryParam) repository.DataSourceRe } } -func (repo *BinanceRepository) GetSymbols(ctx context.Context) ([]entity.Symbol, error) { +func (repo *BinanceRepository) GetSymbols(ctx context.Context) ([]*entity.Symbol, error) { // 優先從 redis hash 拿 cached, err := repo.rds.Hgetall(blockchain.RedisKeySymbolList) if err == nil && len(cached) > 0 { - symbols := make([]entity.Symbol, 0, len(cached)) + symbols := make([]*entity.Symbol, 0, len(cached)) canUseCache := true for _, v := range cached { var symbol entity.Symbol if err := json.Unmarshal([]byte(v), &symbol); err == nil { - symbols = append(symbols, symbol) + symbols = append(symbols, &symbol) } else { // 如果任何一個反序列化失敗,代表快取可能已損壞,最好是回源重新拉取 canUseCache = false @@ -68,11 +68,11 @@ func (repo *BinanceRepository) GetSymbols(ctx context.Context) ([]entity.Symbol, if err != nil { return nil, err } - result := make([]entity.Symbol, 0, len(srcSymbols)) + result := make([]*entity.Symbol, 0, len(srcSymbols)) hashData := make(map[string]string, len(srcSymbols)) for _, s := range srcSymbols { // 只挑目前需要的欄位 - symbolEntity := entity.Symbol{ + symbolEntity := &entity.Symbol{ Symbol: s.Symbol, Status: s.Status, BaseAsset: s.BaseAsset, @@ -106,7 +106,7 @@ func (repo *BinanceRepository) GetSymbols(ctx context.Context) ([]entity.Symbol, return nil, err } - return val.([]entity.Symbol), nil + return val.([]*entity.Symbol), nil } func (repo *BinanceRepository) getSymbolsFromSource(ctx context.Context) ([]binance.Symbol, error) { diff --git a/internal/server/blockchainservice/blockchain_service_server.go b/internal/server/blockchainservice/blockchain_service_server.go index 27f1196..210f60a 100644 --- a/internal/server/blockchainservice/blockchain_service_server.go +++ b/internal/server/blockchainservice/blockchain_service_server.go @@ -23,6 +23,13 @@ func NewBlockchainServiceServer(svcCtx *svc.ServiceContext) *BlockchainServiceSe } } +// ListSymbols retrieves all available trading symbols. +func (s *BlockchainServiceServer) ListSymbols(ctx context.Context, in *app_cloudep_blockchain.ListSymbolsRequest) (*app_cloudep_blockchain.ListSymbolsResponse, error) { + l := blockchainservicelogic.NewListSymbolsLogic(ctx, s.svcCtx) + return l.ListSymbols(in) +} + +// Ping is a health-check endpoint. func (s *BlockchainServiceServer) Ping(ctx context.Context, in *app_cloudep_blockchain.NoneReq) (*app_cloudep_blockchain.OKResp, error) { l := blockchainservicelogic.NewPingLogic(ctx, s.svcCtx) return l.Ping(in) diff --git a/internal/svc/service_context.go b/internal/svc/service_context.go index 968259c..e1c6464 100644 --- a/internal/svc/service_context.go +++ b/internal/svc/service_context.go @@ -1,13 +1,37 @@ package svc -import "blockchain/internal/config" +import ( + "blockchain/internal/config" + "blockchain/internal/domain/repository" + "blockchain/internal/domain/usecase" + repo "blockchain/internal/repository" + uc "blockchain/internal/usecase" + + "github.com/zeromicro/go-zero/core/stores/redis" +) type ServiceContext struct { - Config config.Config + Config config.Config + BinanceDataSource usecase.DataSourceUseCase + BinanceRepo repository.DataSourceRepository } func NewServiceContext(c config.Config) *ServiceContext { + + newRedis, err := redis.NewRedis(c.RedisCluster) + if err != nil { + panic(err) + } + binanceRepo := repo.MustBinanceRepository(repo.BinanceRepositoryParam{ + Conf: &c.Binance, + Redis: newRedis, + }) + return &ServiceContext{ - Config: c, + Config: c, + BinanceRepo: binanceRepo, + BinanceDataSource: uc.MustBinanceUseCase(uc.BinanceUseCaseParam{ + BinanceRepo: binanceRepo, + }), } } diff --git a/internal/usecase/binance.go b/internal/usecase/binance.go new file mode 100644 index 0000000..0b090b9 --- /dev/null +++ b/internal/usecase/binance.go @@ -0,0 +1,59 @@ +package usecase + +import ( + "blockchain/internal/domain/blockchain" + "blockchain/internal/domain/repository" + "blockchain/internal/domain/usecase" + "context" + + "code.30cm.net/digimon/library-go/errs" + "github.com/zeromicro/go-zero/core/logx" +) + +type BinanceUseCaseParam struct { + BinanceRepo repository.DataSourceRepository +} + +type BinanceUseCase struct { + BinanceUseCaseParam +} + +func MustBinanceUseCase(param BinanceUseCaseParam) usecase.DataSourceUseCase { + return &BinanceUseCase{ + BinanceUseCaseParam: param, + } +} + +// GetSymbols implements usecase.DataSourceUseCase. +func (use *BinanceUseCase) GetSymbols(ctx context.Context) ([]*usecase.Symbol, error) { + result, err := use.BinanceRepo.GetSymbols(ctx) + if err != nil { + // 錯誤代碼 20-201-04 + e := errs.ThirdPartyErrorL( + blockchain.CodeBlockchain, + blockchain.FailedToGetSymbolFormBinanceErrorCode, + logx.WithContext(ctx), + []logx.LogField{ + {Key: "func", Value: "BinanceUseCase.ThirdPartyErrorL"}, + {Key: "err", Value: err.Error()}, + }, + "failed to get symbols from binance").Wrap(err) + + return nil, e + } + + rpy := make([]*usecase.Symbol, len(result)) + for _, item := range result { + rpy = append(rpy, &usecase.Symbol{ + Symbol: item.Symbol, + Status: item.Status, + BaseAsset: item.BaseAsset, + BaseAssetPrecision: item.BaseAssetPrecision, + QuoteAsset: item.QuoteAsset, + QuoteAssetPrecision: item.QuoteAssetPrecision, + }) + } + + return rpy, nil + +}