From a68d465272dd1b3ac7be087f84db18d4967f24a8 Mon Sep 17 00:00:00 2001 From: zhx Date: Tue, 26 May 2026 03:06:02 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B9=B8=E8=BF=90=E7=A4=BC=E7=89=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/activity/v1/activity.pb.go | 23 +- api/proto/activity/v1/activity.proto | 2 + api/proto/wallet/v1/wallet.pb.go | 600 ++++++++++++------ api/proto/wallet/v1/wallet.proto | 22 + api/proto/wallet/v1/wallet_grpc.pb.go | 38 ++ docs/flutter对接/幸运礼物Flutter对接.md | 16 +- .../幸运礼物接口闭环Flutter对接.md | 171 +++++ docs/幸运礼物线上落地文档.md | 12 +- .../configs/config.docker.yaml | 7 + .../configs/config.tencent.example.yaml | 7 + services/activity-service/configs/config.yaml | 7 + .../mysql/initdb/001_activity_service.sql | 5 + services/activity-service/internal/app/app.go | 30 +- .../internal/config/config.go | 38 ++ .../internal/domain/luckygift/lucky_gift.go | 32 +- .../internal/service/luckygift/service.go | 259 +++++++- .../service/luckygift/service_test.go | 104 +++ .../storage/mysql/lucky_gift_repository.go | 191 +++++- .../internal/storage/mysql/repository.go | 87 +++ .../transport/grpc/lucky_gift_server.go | 20 +- services/gateway-service/internal/app/app.go | 2 + .../internal/client/lucky_gift_client.go | 25 + .../transport/http/activityapi/handler.go | 4 + .../http/activityapi/lucky_gift_handler.go | 71 +++ .../activityapi/lucky_gift_handler_test.go | 64 ++ .../internal/transport/http/handler.go | 6 + .../transport/http/httproutes/router.go | 2 + .../internal/transport/http/router.go | 1 + .../internal/room/service/gift.go | 22 +- .../internal/domain/ledger/ledger.go | 21 + .../internal/service/wallet/service.go | 23 + .../internal/service/wallet/service_test.go | 37 ++ .../internal/storage/mysql/repository.go | 160 +++++ .../internal/transport/grpc/server.go | 32 + 34 files changed, 1881 insertions(+), 260 deletions(-) create mode 100644 docs/flutter对接/幸运礼物接口闭环Flutter对接.md create mode 100644 services/gateway-service/internal/client/lucky_gift_client.go create mode 100644 services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler.go create mode 100644 services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler_test.go diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index 41812c65..af5b4664 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -9955,6 +9955,8 @@ type LuckyGiftMeta struct { CoinSpent int64 `protobuf:"varint,8,opt,name=coin_spent,json=coinSpent,proto3" json:"coin_spent,omitempty"` PaidAtMs int64 `protobuf:"varint,9,opt,name=paid_at_ms,json=paidAtMs,proto3" json:"paid_at_ms,omitempty"` PoolId string `protobuf:"bytes,10,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + TargetUserId int64 `protobuf:"varint,11,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + GiftCount int32 `protobuf:"varint,12,opt,name=gift_count,json=giftCount,proto3" json:"gift_count,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -10059,6 +10061,20 @@ func (x *LuckyGiftMeta) GetPoolId() string { return "" } +func (x *LuckyGiftMeta) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *LuckyGiftMeta) GetGiftCount() int32 { + if x != nil { + return x.GiftCount + } + return 0 +} + type LuckyGiftTier struct { state protoimpl.MessageState `protogen:"open.v1"` Pool string `protobuf:"bytes,1,opt,name=pool,proto3" json:"pool,omitempty"` @@ -12599,7 +12615,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "conditions\"\x8b\x01\n" + "#UpsertAchievementDefinitionResponse\x12J\n" + "\vachievement\x18\x01 \x01(\v2(.hyapp.activity.v1.AchievementDefinitionR\vachievement\x12\x18\n" + - "\acreated\x18\x02 \x01(\bR\acreated\"\xbd\x02\n" + + "\acreated\x18\x02 \x01(\bR\acreated\"\x82\x03\n" + "\rLuckyGiftMeta\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x1d\n" + "\n" + @@ -12614,7 +12630,10 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\n" + "paid_at_ms\x18\t \x01(\x03R\bpaidAtMs\x12\x17\n" + "\apool_id\x18\n" + - " \x01(\tR\x06poolId\"\xe0\x01\n" + + " \x01(\tR\x06poolId\x12$\n" + + "\x0etarget_user_id\x18\v \x01(\x03R\ftargetUserId\x12\x1d\n" + + "\n" + + "gift_count\x18\f \x01(\x05R\tgiftCount\"\xe0\x01\n" + "\rLuckyGiftTier\x12\x12\n" + "\x04pool\x18\x01 \x01(\tR\x04pool\x12\x17\n" + "\atier_id\x18\x02 \x01(\tR\x06tierId\x12!\n" + diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index 7fbf42ef..32fbc496 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -1123,6 +1123,8 @@ message LuckyGiftMeta { int64 coin_spent = 8; int64 paid_at_ms = 9; string pool_id = 10; + int64 target_user_id = 11; + int32 gift_count = 12; } message LuckyGiftTier { diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index 6d9176c8..ade872a5 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -9492,6 +9492,184 @@ func (x *CreditTaskRewardResponse) GetGrantedAtMs() int64 { return 0 } +// CreditLuckyGiftRewardRequest 是 activity-service 抽奖 outbox 补偿 worker 调用的钱包入账命令。 +type CreditLuckyGiftRewardRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + CommandId string `protobuf:"bytes,1,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + TargetUserId int64 `protobuf:"varint,3,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + Amount int64 `protobuf:"varint,4,opt,name=amount,proto3" json:"amount,omitempty"` + DrawId string `protobuf:"bytes,5,opt,name=draw_id,json=drawId,proto3" json:"draw_id,omitempty"` + RoomId string `protobuf:"bytes,6,opt,name=room_id,json=roomId,proto3" json:"room_id,omitempty"` + GiftId string `protobuf:"bytes,7,opt,name=gift_id,json=giftId,proto3" json:"gift_id,omitempty"` + PoolId string `protobuf:"bytes,8,opt,name=pool_id,json=poolId,proto3" json:"pool_id,omitempty"` + Reason string `protobuf:"bytes,9,opt,name=reason,proto3" json:"reason,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreditLuckyGiftRewardRequest) Reset() { + *x = CreditLuckyGiftRewardRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreditLuckyGiftRewardRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditLuckyGiftRewardRequest) ProtoMessage() {} + +func (x *CreditLuckyGiftRewardRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] + 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 CreditLuckyGiftRewardRequest.ProtoReflect.Descriptor instead. +func (*CreditLuckyGiftRewardRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{106} +} + +func (x *CreditLuckyGiftRewardRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +func (x *CreditLuckyGiftRewardRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *CreditLuckyGiftRewardRequest) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + +func (x *CreditLuckyGiftRewardRequest) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *CreditLuckyGiftRewardRequest) GetDrawId() string { + if x != nil { + return x.DrawId + } + return "" +} + +func (x *CreditLuckyGiftRewardRequest) GetRoomId() string { + if x != nil { + return x.RoomId + } + return "" +} + +func (x *CreditLuckyGiftRewardRequest) GetGiftId() string { + if x != nil { + return x.GiftId + } + return "" +} + +func (x *CreditLuckyGiftRewardRequest) GetPoolId() string { + if x != nil { + return x.PoolId + } + return "" +} + +func (x *CreditLuckyGiftRewardRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + +// CreditLuckyGiftRewardResponse 返回幸运礼物返奖入账流水和用户 COIN 账后余额。 +type CreditLuckyGiftRewardResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + TransactionId string `protobuf:"bytes,1,opt,name=transaction_id,json=transactionId,proto3" json:"transaction_id,omitempty"` + Balance *AssetBalance `protobuf:"bytes,2,opt,name=balance,proto3" json:"balance,omitempty"` + Amount int64 `protobuf:"varint,3,opt,name=amount,proto3" json:"amount,omitempty"` + GrantedAtMs int64 `protobuf:"varint,4,opt,name=granted_at_ms,json=grantedAtMs,proto3" json:"granted_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreditLuckyGiftRewardResponse) Reset() { + *x = CreditLuckyGiftRewardResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreditLuckyGiftRewardResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreditLuckyGiftRewardResponse) ProtoMessage() {} + +func (x *CreditLuckyGiftRewardResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] + 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 CreditLuckyGiftRewardResponse.ProtoReflect.Descriptor instead. +func (*CreditLuckyGiftRewardResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{107} +} + +func (x *CreditLuckyGiftRewardResponse) GetTransactionId() string { + if x != nil { + return x.TransactionId + } + return "" +} + +func (x *CreditLuckyGiftRewardResponse) GetBalance() *AssetBalance { + if x != nil { + return x.Balance + } + return nil +} + +func (x *CreditLuckyGiftRewardResponse) GetAmount() int64 { + if x != nil { + return x.Amount + } + return 0 +} + +func (x *CreditLuckyGiftRewardResponse) GetGrantedAtMs() int64 { + if x != nil { + return x.GrantedAtMs + } + return 0 +} + // ApplyGameCoinChangeRequest 是 game-service 唯一可以调用的钱包游戏改账入口。 type ApplyGameCoinChangeRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -9513,7 +9691,7 @@ type ApplyGameCoinChangeRequest struct { func (x *ApplyGameCoinChangeRequest) Reset() { *x = ApplyGameCoinChangeRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9525,7 +9703,7 @@ func (x *ApplyGameCoinChangeRequest) String() string { func (*ApplyGameCoinChangeRequest) ProtoMessage() {} func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[106] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9538,7 +9716,7 @@ func (x *ApplyGameCoinChangeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeRequest.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{106} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{108} } func (x *ApplyGameCoinChangeRequest) GetRequestId() string { @@ -9637,7 +9815,7 @@ type ApplyGameCoinChangeResponse struct { func (x *ApplyGameCoinChangeResponse) Reset() { *x = ApplyGameCoinChangeResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9649,7 +9827,7 @@ func (x *ApplyGameCoinChangeResponse) String() string { func (*ApplyGameCoinChangeResponse) ProtoMessage() {} func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[107] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9662,7 +9840,7 @@ func (x *ApplyGameCoinChangeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ApplyGameCoinChangeResponse.ProtoReflect.Descriptor instead. func (*ApplyGameCoinChangeResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{107} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{109} } func (x *ApplyGameCoinChangeResponse) GetWalletTransactionId() string { @@ -9704,7 +9882,7 @@ type RedPacketConfig struct { func (x *RedPacketConfig) Reset() { *x = RedPacketConfig{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9716,7 +9894,7 @@ func (x *RedPacketConfig) String() string { func (*RedPacketConfig) ProtoMessage() {} func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[108] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9729,7 +9907,7 @@ func (x *RedPacketConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketConfig.ProtoReflect.Descriptor instead. func (*RedPacketConfig) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{108} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{110} } func (x *RedPacketConfig) GetAppCode() string { @@ -9821,7 +9999,7 @@ type RedPacketClaim struct { func (x *RedPacketClaim) Reset() { *x = RedPacketClaim{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9833,7 +10011,7 @@ func (x *RedPacketClaim) String() string { func (*RedPacketClaim) ProtoMessage() {} func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[109] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9846,7 +10024,7 @@ func (x *RedPacketClaim) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacketClaim.ProtoReflect.Descriptor instead. func (*RedPacketClaim) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{109} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{111} } func (x *RedPacketClaim) GetAppCode() string { @@ -9953,7 +10131,7 @@ type RedPacket struct { func (x *RedPacket) Reset() { *x = RedPacket{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9965,7 +10143,7 @@ func (x *RedPacket) String() string { func (*RedPacket) ProtoMessage() {} func (x *RedPacket) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[110] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9978,7 +10156,7 @@ func (x *RedPacket) ProtoReflect() protoreflect.Message { // Deprecated: Use RedPacket.ProtoReflect.Descriptor instead. func (*RedPacket) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{110} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{112} } func (x *RedPacket) GetAppCode() string { @@ -10124,7 +10302,7 @@ type GetRedPacketConfigRequest struct { func (x *GetRedPacketConfigRequest) Reset() { *x = GetRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10136,7 +10314,7 @@ func (x *GetRedPacketConfigRequest) String() string { func (*GetRedPacketConfigRequest) ProtoMessage() {} func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[111] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10149,7 +10327,7 @@ func (x *GetRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{111} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{113} } func (x *GetRedPacketConfigRequest) GetRequestId() string { @@ -10176,7 +10354,7 @@ type GetRedPacketConfigResponse struct { func (x *GetRedPacketConfigResponse) Reset() { *x = GetRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10188,7 +10366,7 @@ func (x *GetRedPacketConfigResponse) String() string { func (*GetRedPacketConfigResponse) ProtoMessage() {} func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[112] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10201,7 +10379,7 @@ func (x *GetRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{112} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{114} } func (x *GetRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -10235,7 +10413,7 @@ type UpdateRedPacketConfigRequest struct { func (x *UpdateRedPacketConfigRequest) Reset() { *x = UpdateRedPacketConfigRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10247,7 +10425,7 @@ func (x *UpdateRedPacketConfigRequest) String() string { func (*UpdateRedPacketConfigRequest) ProtoMessage() {} func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[113] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10260,7 +10438,7 @@ func (x *UpdateRedPacketConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigRequest.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{113} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{115} } func (x *UpdateRedPacketConfigRequest) GetRequestId() string { @@ -10336,7 +10514,7 @@ type UpdateRedPacketConfigResponse struct { func (x *UpdateRedPacketConfigResponse) Reset() { *x = UpdateRedPacketConfigResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10348,7 +10526,7 @@ func (x *UpdateRedPacketConfigResponse) String() string { func (*UpdateRedPacketConfigResponse) ProtoMessage() {} func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[114] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10361,7 +10539,7 @@ func (x *UpdateRedPacketConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpdateRedPacketConfigResponse.ProtoReflect.Descriptor instead. func (*UpdateRedPacketConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{114} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{116} } func (x *UpdateRedPacketConfigResponse) GetConfig() *RedPacketConfig { @@ -10395,7 +10573,7 @@ type CreateRedPacketRequest struct { func (x *CreateRedPacketRequest) Reset() { *x = CreateRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10407,7 +10585,7 @@ func (x *CreateRedPacketRequest) String() string { func (*CreateRedPacketRequest) ProtoMessage() {} func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[115] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10420,7 +10598,7 @@ func (x *CreateRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketRequest.ProtoReflect.Descriptor instead. func (*CreateRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{115} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{117} } func (x *CreateRedPacketRequest) GetRequestId() string { @@ -10497,7 +10675,7 @@ type CreateRedPacketResponse struct { func (x *CreateRedPacketResponse) Reset() { *x = CreateRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10509,7 +10687,7 @@ func (x *CreateRedPacketResponse) String() string { func (*CreateRedPacketResponse) ProtoMessage() {} func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[116] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10522,7 +10700,7 @@ func (x *CreateRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CreateRedPacketResponse.ProtoReflect.Descriptor instead. func (*CreateRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{116} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{118} } func (x *CreateRedPacketResponse) GetPacket() *RedPacket { @@ -10559,7 +10737,7 @@ type ClaimRedPacketRequest struct { func (x *ClaimRedPacketRequest) Reset() { *x = ClaimRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10571,7 +10749,7 @@ func (x *ClaimRedPacketRequest) String() string { func (*ClaimRedPacketRequest) ProtoMessage() {} func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[117] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10584,7 +10762,7 @@ func (x *ClaimRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketRequest.ProtoReflect.Descriptor instead. func (*ClaimRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{117} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{119} } func (x *ClaimRedPacketRequest) GetRequestId() string { @@ -10633,7 +10811,7 @@ type ClaimRedPacketResponse struct { func (x *ClaimRedPacketResponse) Reset() { *x = ClaimRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10645,7 +10823,7 @@ func (x *ClaimRedPacketResponse) String() string { func (*ClaimRedPacketResponse) ProtoMessage() {} func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[118] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10658,7 +10836,7 @@ func (x *ClaimRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimRedPacketResponse.ProtoReflect.Descriptor instead. func (*ClaimRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{118} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} } func (x *ClaimRedPacketResponse) GetClaim() *RedPacketClaim { @@ -10702,7 +10880,7 @@ type ListRedPacketsRequest struct { func (x *ListRedPacketsRequest) Reset() { *x = ListRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10714,7 +10892,7 @@ func (x *ListRedPacketsRequest) String() string { func (*ListRedPacketsRequest) ProtoMessage() {} func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[119] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10727,7 +10905,7 @@ func (x *ListRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ListRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{119} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} } func (x *ListRedPacketsRequest) GetRequestId() string { @@ -10825,7 +11003,7 @@ type ListRedPacketsResponse struct { func (x *ListRedPacketsResponse) Reset() { *x = ListRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10837,7 +11015,7 @@ func (x *ListRedPacketsResponse) String() string { func (*ListRedPacketsResponse) ProtoMessage() {} func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[120] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10850,7 +11028,7 @@ func (x *ListRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ListRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{120} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} } func (x *ListRedPacketsResponse) GetPackets() []*RedPacket { @@ -10887,7 +11065,7 @@ type GetRedPacketRequest struct { func (x *GetRedPacketRequest) Reset() { *x = GetRedPacketRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10899,7 +11077,7 @@ func (x *GetRedPacketRequest) String() string { func (*GetRedPacketRequest) ProtoMessage() {} func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[121] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10912,7 +11090,7 @@ func (x *GetRedPacketRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketRequest.ProtoReflect.Descriptor instead. func (*GetRedPacketRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{121} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} } func (x *GetRedPacketRequest) GetRequestId() string { @@ -10960,7 +11138,7 @@ type GetRedPacketResponse struct { func (x *GetRedPacketResponse) Reset() { *x = GetRedPacketResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10972,7 +11150,7 @@ func (x *GetRedPacketResponse) String() string { func (*GetRedPacketResponse) ProtoMessage() {} func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[122] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10985,7 +11163,7 @@ func (x *GetRedPacketResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRedPacketResponse.ProtoReflect.Descriptor instead. func (*GetRedPacketResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{122} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} } func (x *GetRedPacketResponse) GetPacket() *RedPacket { @@ -11013,7 +11191,7 @@ type ExpireRedPacketsRequest struct { func (x *ExpireRedPacketsRequest) Reset() { *x = ExpireRedPacketsRequest{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11025,7 +11203,7 @@ func (x *ExpireRedPacketsRequest) String() string { func (*ExpireRedPacketsRequest) ProtoMessage() {} func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[123] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11038,7 +11216,7 @@ func (x *ExpireRedPacketsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsRequest.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsRequest) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{123} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{125} } func (x *ExpireRedPacketsRequest) GetRequestId() string { @@ -11073,7 +11251,7 @@ type ExpireRedPacketsResponse struct { func (x *ExpireRedPacketsResponse) Reset() { *x = ExpireRedPacketsResponse{} - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11085,7 +11263,7 @@ func (x *ExpireRedPacketsResponse) String() string { func (*ExpireRedPacketsResponse) ProtoMessage() {} func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_wallet_v1_wallet_proto_msgTypes[124] + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11098,7 +11276,7 @@ func (x *ExpireRedPacketsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExpireRedPacketsResponse.ProtoReflect.Descriptor instead. func (*ExpireRedPacketsResponse) Descriptor() ([]byte, []int) { - return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{124} + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{126} } func (x *ExpireRedPacketsResponse) GetExpiredCount() int32 { @@ -12117,6 +12295,22 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x0etransaction_id\x18\x01 \x01(\tR\rtransactionId\x127\n" + "\abalance\x18\x02 \x01(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\abalance\x12\x16\n" + "\x06amount\x18\x03 \x01(\x03R\x06amount\x12\"\n" + + "\rgranted_at_ms\x18\x04 \x01(\x03R\vgrantedAtMs\"\x92\x02\n" + + "\x1cCreditLuckyGiftRewardRequest\x12\x1d\n" + + "\n" + + "command_id\x18\x01 \x01(\tR\tcommandId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12$\n" + + "\x0etarget_user_id\x18\x03 \x01(\x03R\ftargetUserId\x12\x16\n" + + "\x06amount\x18\x04 \x01(\x03R\x06amount\x12\x17\n" + + "\adraw_id\x18\x05 \x01(\tR\x06drawId\x12\x17\n" + + "\aroom_id\x18\x06 \x01(\tR\x06roomId\x12\x17\n" + + "\agift_id\x18\a \x01(\tR\x06giftId\x12\x17\n" + + "\apool_id\x18\b \x01(\tR\x06poolId\x12\x16\n" + + "\x06reason\x18\t \x01(\tR\x06reason\"\xbb\x01\n" + + "\x1dCreditLuckyGiftRewardResponse\x12%\n" + + "\x0etransaction_id\x18\x01 \x01(\tR\rtransactionId\x127\n" + + "\abalance\x18\x02 \x01(\v2\x1d.hyapp.wallet.v1.AssetBalanceR\abalance\x12\x16\n" + + "\x06amount\x18\x03 \x01(\x03R\x06amount\x12\"\n" + "\rgranted_at_ms\x18\x04 \x01(\x03R\vgrantedAtMs\"\x9a\x03\n" + "\x1aApplyGameCoinChangeRequest\x12\x1d\n" + "\n" + @@ -12280,7 +12474,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x18ExpireRedPacketsResponse\x12#\n" + "\rexpired_count\x18\x01 \x01(\x05R\fexpiredCount\x12'\n" + "\x0frefunded_amount\x18\x02 \x01(\x03R\x0erefundedAmount\x12$\n" + - "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs2\x86-\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs2\xfe-\n" + "\rWalletService\x12R\n" + "\tDebitGift\x12!.hyapp.wallet.v1.DebitGiftRequest\x1a\".hyapp.wallet.v1.DebitGiftResponse\x12X\n" + "\vGetBalances\x12#.hyapp.wallet.v1.GetBalancesRequest\x1a$.hyapp.wallet.v1.GetBalancesResponse\x12g\n" + @@ -12327,7 +12521,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\bGrantVip\x12 .hyapp.wallet.v1.GrantVipRequest\x1a!.hyapp.wallet.v1.GrantVipResponse\x12m\n" + "\x12ListAdminVipLevels\x12*.hyapp.wallet.v1.ListAdminVipLevelsRequest\x1a+.hyapp.wallet.v1.ListAdminVipLevelsResponse\x12s\n" + "\x14UpdateAdminVipLevels\x12,.hyapp.wallet.v1.UpdateAdminVipLevelsRequest\x1a-.hyapp.wallet.v1.UpdateAdminVipLevelsResponse\x12g\n" + - "\x10CreditTaskReward\x12(.hyapp.wallet.v1.CreditTaskRewardRequest\x1a).hyapp.wallet.v1.CreditTaskRewardResponse\x12p\n" + + "\x10CreditTaskReward\x12(.hyapp.wallet.v1.CreditTaskRewardRequest\x1a).hyapp.wallet.v1.CreditTaskRewardResponse\x12v\n" + + "\x15CreditLuckyGiftReward\x12-.hyapp.wallet.v1.CreditLuckyGiftRewardRequest\x1a..hyapp.wallet.v1.CreditLuckyGiftRewardResponse\x12p\n" + "\x13ApplyGameCoinChange\x12+.hyapp.wallet.v1.ApplyGameCoinChangeRequest\x1a,.hyapp.wallet.v1.ApplyGameCoinChangeResponse\x12m\n" + "\x12GetRedPacketConfig\x12*.hyapp.wallet.v1.GetRedPacketConfigRequest\x1a+.hyapp.wallet.v1.GetRedPacketConfigResponse\x12v\n" + "\x15UpdateRedPacketConfig\x12-.hyapp.wallet.v1.UpdateRedPacketConfigRequest\x1a..hyapp.wallet.v1.UpdateRedPacketConfigResponse\x12d\n" + @@ -12349,7 +12544,7 @@ func file_proto_wallet_v1_wallet_proto_rawDescGZIP() []byte { return file_proto_wallet_v1_wallet_proto_rawDescData } -var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 125) +var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 127) var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse @@ -12457,25 +12652,27 @@ var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*UpdateAdminVipLevelsResponse)(nil), // 103: hyapp.wallet.v1.UpdateAdminVipLevelsResponse (*CreditTaskRewardRequest)(nil), // 104: hyapp.wallet.v1.CreditTaskRewardRequest (*CreditTaskRewardResponse)(nil), // 105: hyapp.wallet.v1.CreditTaskRewardResponse - (*ApplyGameCoinChangeRequest)(nil), // 106: hyapp.wallet.v1.ApplyGameCoinChangeRequest - (*ApplyGameCoinChangeResponse)(nil), // 107: hyapp.wallet.v1.ApplyGameCoinChangeResponse - (*RedPacketConfig)(nil), // 108: hyapp.wallet.v1.RedPacketConfig - (*RedPacketClaim)(nil), // 109: hyapp.wallet.v1.RedPacketClaim - (*RedPacket)(nil), // 110: hyapp.wallet.v1.RedPacket - (*GetRedPacketConfigRequest)(nil), // 111: hyapp.wallet.v1.GetRedPacketConfigRequest - (*GetRedPacketConfigResponse)(nil), // 112: hyapp.wallet.v1.GetRedPacketConfigResponse - (*UpdateRedPacketConfigRequest)(nil), // 113: hyapp.wallet.v1.UpdateRedPacketConfigRequest - (*UpdateRedPacketConfigResponse)(nil), // 114: hyapp.wallet.v1.UpdateRedPacketConfigResponse - (*CreateRedPacketRequest)(nil), // 115: hyapp.wallet.v1.CreateRedPacketRequest - (*CreateRedPacketResponse)(nil), // 116: hyapp.wallet.v1.CreateRedPacketResponse - (*ClaimRedPacketRequest)(nil), // 117: hyapp.wallet.v1.ClaimRedPacketRequest - (*ClaimRedPacketResponse)(nil), // 118: hyapp.wallet.v1.ClaimRedPacketResponse - (*ListRedPacketsRequest)(nil), // 119: hyapp.wallet.v1.ListRedPacketsRequest - (*ListRedPacketsResponse)(nil), // 120: hyapp.wallet.v1.ListRedPacketsResponse - (*GetRedPacketRequest)(nil), // 121: hyapp.wallet.v1.GetRedPacketRequest - (*GetRedPacketResponse)(nil), // 122: hyapp.wallet.v1.GetRedPacketResponse - (*ExpireRedPacketsRequest)(nil), // 123: hyapp.wallet.v1.ExpireRedPacketsRequest - (*ExpireRedPacketsResponse)(nil), // 124: hyapp.wallet.v1.ExpireRedPacketsResponse + (*CreditLuckyGiftRewardRequest)(nil), // 106: hyapp.wallet.v1.CreditLuckyGiftRewardRequest + (*CreditLuckyGiftRewardResponse)(nil), // 107: hyapp.wallet.v1.CreditLuckyGiftRewardResponse + (*ApplyGameCoinChangeRequest)(nil), // 108: hyapp.wallet.v1.ApplyGameCoinChangeRequest + (*ApplyGameCoinChangeResponse)(nil), // 109: hyapp.wallet.v1.ApplyGameCoinChangeResponse + (*RedPacketConfig)(nil), // 110: hyapp.wallet.v1.RedPacketConfig + (*RedPacketClaim)(nil), // 111: hyapp.wallet.v1.RedPacketClaim + (*RedPacket)(nil), // 112: hyapp.wallet.v1.RedPacket + (*GetRedPacketConfigRequest)(nil), // 113: hyapp.wallet.v1.GetRedPacketConfigRequest + (*GetRedPacketConfigResponse)(nil), // 114: hyapp.wallet.v1.GetRedPacketConfigResponse + (*UpdateRedPacketConfigRequest)(nil), // 115: hyapp.wallet.v1.UpdateRedPacketConfigRequest + (*UpdateRedPacketConfigResponse)(nil), // 116: hyapp.wallet.v1.UpdateRedPacketConfigResponse + (*CreateRedPacketRequest)(nil), // 117: hyapp.wallet.v1.CreateRedPacketRequest + (*CreateRedPacketResponse)(nil), // 118: hyapp.wallet.v1.CreateRedPacketResponse + (*ClaimRedPacketRequest)(nil), // 119: hyapp.wallet.v1.ClaimRedPacketRequest + (*ClaimRedPacketResponse)(nil), // 120: hyapp.wallet.v1.ClaimRedPacketResponse + (*ListRedPacketsRequest)(nil), // 121: hyapp.wallet.v1.ListRedPacketsRequest + (*ListRedPacketsResponse)(nil), // 122: hyapp.wallet.v1.ListRedPacketsResponse + (*GetRedPacketRequest)(nil), // 123: hyapp.wallet.v1.GetRedPacketRequest + (*GetRedPacketResponse)(nil), // 124: hyapp.wallet.v1.GetRedPacketResponse + (*ExpireRedPacketsRequest)(nil), // 125: hyapp.wallet.v1.ExpireRedPacketsRequest + (*ExpireRedPacketsResponse)(nil), // 126: hyapp.wallet.v1.ExpireRedPacketsResponse } var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 2, // 0: hyapp.wallet.v1.GetBalancesResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance @@ -12527,126 +12724,129 @@ var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 99, // 46: hyapp.wallet.v1.UpdateAdminVipLevelsRequest.levels:type_name -> hyapp.wallet.v1.AdminVipLevelInput 89, // 47: hyapp.wallet.v1.UpdateAdminVipLevelsResponse.levels:type_name -> hyapp.wallet.v1.VipLevel 2, // 48: hyapp.wallet.v1.CreditTaskRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance - 109, // 49: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim - 108, // 50: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 108, // 51: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig - 110, // 52: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 109, // 53: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim - 110, // 54: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket - 110, // 55: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 0, // 56: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest - 3, // 57: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest - 5, // 58: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest - 7, // 59: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest - 9, // 60: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest - 20, // 61: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest - 22, // 62: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest - 24, // 63: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest - 25, // 64: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest - 26, // 65: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest - 28, // 66: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest - 30, // 67: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest - 32, // 68: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest - 33, // 69: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest - 34, // 70: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest - 36, // 71: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest - 38, // 72: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest - 42, // 73: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest - 43, // 74: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest - 44, // 75: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest - 40, // 76: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest - 46, // 77: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest - 47, // 78: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest - 49, // 79: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest - 51, // 80: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest - 53, // 81: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest - 56, // 82: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest - 59, // 83: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest - 62, // 84: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest - 65, // 85: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest - 68, // 86: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest - 70, // 87: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest - 72, // 88: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest - 74, // 89: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest - 75, // 90: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest - 76, // 91: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest - 80, // 92: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest - 83, // 93: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest - 86, // 94: hyapp.wallet.v1.WalletService.ApplyWithdrawal:input_type -> hyapp.wallet.v1.ApplyWithdrawalRequest - 91, // 95: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest - 93, // 96: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest - 95, // 97: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest - 97, // 98: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest - 100, // 99: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest - 102, // 100: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest - 104, // 101: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest - 106, // 102: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest - 111, // 103: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest - 113, // 104: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest - 115, // 105: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest - 117, // 106: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest - 119, // 107: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest - 121, // 108: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest - 123, // 109: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest - 1, // 110: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 4, // 111: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse - 6, // 112: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse - 8, // 113: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse - 10, // 114: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse - 21, // 115: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse - 23, // 116: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse - 27, // 117: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 27, // 118: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 27, // 119: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse - 29, // 120: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse - 31, // 121: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse - 35, // 122: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 35, // 123: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 35, // 124: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 37, // 125: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse - 39, // 126: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse - 45, // 127: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 45, // 128: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 45, // 129: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse - 41, // 130: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse - 48, // 131: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 48, // 132: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 50, // 133: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse - 52, // 134: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse - 54, // 135: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse - 57, // 136: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse - 60, // 137: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse - 63, // 138: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse - 66, // 139: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse - 69, // 140: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse - 71, // 141: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse - 73, // 142: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse - 77, // 143: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 77, // 144: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 78, // 145: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse - 81, // 146: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse - 84, // 147: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse - 87, // 148: hyapp.wallet.v1.WalletService.ApplyWithdrawal:output_type -> hyapp.wallet.v1.ApplyWithdrawalResponse - 92, // 149: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse - 94, // 150: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse - 96, // 151: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse - 98, // 152: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse - 101, // 153: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse - 103, // 154: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse - 105, // 155: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse - 107, // 156: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse - 112, // 157: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse - 114, // 158: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse - 116, // 159: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse - 118, // 160: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse - 120, // 161: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse - 122, // 162: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse - 124, // 163: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse - 110, // [110:164] is the sub-list for method output_type - 56, // [56:110] is the sub-list for method input_type - 56, // [56:56] is the sub-list for extension type_name - 56, // [56:56] is the sub-list for extension extendee - 0, // [0:56] is the sub-list for field type_name + 2, // 49: hyapp.wallet.v1.CreditLuckyGiftRewardResponse.balance:type_name -> hyapp.wallet.v1.AssetBalance + 111, // 50: hyapp.wallet.v1.RedPacket.claims:type_name -> hyapp.wallet.v1.RedPacketClaim + 110, // 51: hyapp.wallet.v1.GetRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 110, // 52: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig + 112, // 53: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 111, // 54: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim + 112, // 55: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket + 112, // 56: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 0, // 57: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest + 3, // 58: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest + 5, // 59: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest + 7, // 60: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest + 9, // 61: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest + 20, // 62: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest + 22, // 63: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest + 24, // 64: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest + 25, // 65: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest + 26, // 66: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest + 28, // 67: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest + 30, // 68: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest + 32, // 69: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest + 33, // 70: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest + 34, // 71: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest + 36, // 72: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest + 38, // 73: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest + 42, // 74: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest + 43, // 75: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest + 44, // 76: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest + 40, // 77: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest + 46, // 78: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest + 47, // 79: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest + 49, // 80: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest + 51, // 81: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest + 53, // 82: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest + 56, // 83: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest + 59, // 84: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest + 62, // 85: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest + 65, // 86: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest + 68, // 87: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest + 70, // 88: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest + 72, // 89: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest + 74, // 90: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest + 75, // 91: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest + 76, // 92: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest + 80, // 93: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest + 83, // 94: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest + 86, // 95: hyapp.wallet.v1.WalletService.ApplyWithdrawal:input_type -> hyapp.wallet.v1.ApplyWithdrawalRequest + 91, // 96: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest + 93, // 97: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest + 95, // 98: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest + 97, // 99: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest + 100, // 100: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest + 102, // 101: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest + 104, // 102: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest + 106, // 103: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest + 108, // 104: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest + 113, // 105: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest + 115, // 106: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest + 117, // 107: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest + 119, // 108: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest + 121, // 109: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest + 123, // 110: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest + 125, // 111: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest + 1, // 112: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 4, // 113: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 6, // 114: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 8, // 115: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse + 10, // 116: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse + 21, // 117: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse + 23, // 118: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse + 27, // 119: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 27, // 120: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 27, // 121: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse + 29, // 122: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse + 31, // 123: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse + 35, // 124: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 35, // 125: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 35, // 126: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 37, // 127: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse + 39, // 128: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse + 45, // 129: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 45, // 130: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 45, // 131: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse + 41, // 132: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse + 48, // 133: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 48, // 134: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 50, // 135: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse + 52, // 136: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse + 54, // 137: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse + 57, // 138: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse + 60, // 139: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse + 63, // 140: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse + 66, // 141: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse + 69, // 142: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse + 71, // 143: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse + 73, // 144: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse + 77, // 145: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 77, // 146: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 78, // 147: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse + 81, // 148: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse + 84, // 149: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse + 87, // 150: hyapp.wallet.v1.WalletService.ApplyWithdrawal:output_type -> hyapp.wallet.v1.ApplyWithdrawalResponse + 92, // 151: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse + 94, // 152: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse + 96, // 153: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse + 98, // 154: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse + 101, // 155: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse + 103, // 156: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse + 105, // 157: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse + 107, // 158: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse + 109, // 159: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse + 114, // 160: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse + 116, // 161: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse + 118, // 162: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse + 120, // 163: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse + 122, // 164: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse + 124, // 165: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse + 126, // 166: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse + 112, // [112:167] is the sub-list for method output_type + 57, // [57:112] is the sub-list for method input_type + 57, // [57:57] is the sub-list for extension type_name + 57, // [57:57] is the sub-list for extension extendee + 0, // [0:57] is the sub-list for field type_name } func init() { file_proto_wallet_v1_wallet_proto_init() } @@ -12662,7 +12862,7 @@ func file_proto_wallet_v1_wallet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wallet_v1_wallet_proto_rawDesc), len(file_proto_wallet_v1_wallet_proto_rawDesc)), NumEnums: 0, - NumMessages: 125, + NumMessages: 127, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index 7fbe4862..484a1816 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -1049,6 +1049,27 @@ message CreditTaskRewardResponse { int64 granted_at_ms = 4; } +// CreditLuckyGiftRewardRequest 是 activity-service 抽奖 outbox 补偿 worker 调用的钱包入账命令。 +message CreditLuckyGiftRewardRequest { + string command_id = 1; + string app_code = 2; + int64 target_user_id = 3; + int64 amount = 4; + string draw_id = 5; + string room_id = 6; + string gift_id = 7; + string pool_id = 8; + string reason = 9; +} + +// CreditLuckyGiftRewardResponse 返回幸运礼物返奖入账流水和用户 COIN 账后余额。 +message CreditLuckyGiftRewardResponse { + string transaction_id = 1; + AssetBalance balance = 2; + int64 amount = 3; + int64 granted_at_ms = 4; +} + // ApplyGameCoinChangeRequest 是 game-service 唯一可以调用的钱包游戏改账入口。 message ApplyGameCoinChangeRequest { string request_id = 1; @@ -1274,6 +1295,7 @@ service WalletService { rpc ListAdminVipLevels(ListAdminVipLevelsRequest) returns (ListAdminVipLevelsResponse); rpc UpdateAdminVipLevels(UpdateAdminVipLevelsRequest) returns (UpdateAdminVipLevelsResponse); rpc CreditTaskReward(CreditTaskRewardRequest) returns (CreditTaskRewardResponse); + rpc CreditLuckyGiftReward(CreditLuckyGiftRewardRequest) returns (CreditLuckyGiftRewardResponse); rpc ApplyGameCoinChange(ApplyGameCoinChangeRequest) returns (ApplyGameCoinChangeResponse); rpc GetRedPacketConfig(GetRedPacketConfigRequest) returns (GetRedPacketConfigResponse); rpc UpdateRedPacketConfig(UpdateRedPacketConfigRequest) returns (UpdateRedPacketConfigResponse); diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index 75372705..f656cac8 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -65,6 +65,7 @@ const ( WalletService_ListAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/ListAdminVipLevels" WalletService_UpdateAdminVipLevels_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateAdminVipLevels" WalletService_CreditTaskReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditTaskReward" + WalletService_CreditLuckyGiftReward_FullMethodName = "/hyapp.wallet.v1.WalletService/CreditLuckyGiftReward" WalletService_ApplyGameCoinChange_FullMethodName = "/hyapp.wallet.v1.WalletService/ApplyGameCoinChange" WalletService_GetRedPacketConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/GetRedPacketConfig" WalletService_UpdateRedPacketConfig_FullMethodName = "/hyapp.wallet.v1.WalletService/UpdateRedPacketConfig" @@ -127,6 +128,7 @@ type WalletServiceClient interface { ListAdminVipLevels(ctx context.Context, in *ListAdminVipLevelsRequest, opts ...grpc.CallOption) (*ListAdminVipLevelsResponse, error) UpdateAdminVipLevels(ctx context.Context, in *UpdateAdminVipLevelsRequest, opts ...grpc.CallOption) (*UpdateAdminVipLevelsResponse, error) CreditTaskReward(ctx context.Context, in *CreditTaskRewardRequest, opts ...grpc.CallOption) (*CreditTaskRewardResponse, error) + CreditLuckyGiftReward(ctx context.Context, in *CreditLuckyGiftRewardRequest, opts ...grpc.CallOption) (*CreditLuckyGiftRewardResponse, error) ApplyGameCoinChange(ctx context.Context, in *ApplyGameCoinChangeRequest, opts ...grpc.CallOption) (*ApplyGameCoinChangeResponse, error) GetRedPacketConfig(ctx context.Context, in *GetRedPacketConfigRequest, opts ...grpc.CallOption) (*GetRedPacketConfigResponse, error) UpdateRedPacketConfig(ctx context.Context, in *UpdateRedPacketConfigRequest, opts ...grpc.CallOption) (*UpdateRedPacketConfigResponse, error) @@ -605,6 +607,16 @@ func (c *walletServiceClient) CreditTaskReward(ctx context.Context, in *CreditTa return out, nil } +func (c *walletServiceClient) CreditLuckyGiftReward(ctx context.Context, in *CreditLuckyGiftRewardRequest, opts ...grpc.CallOption) (*CreditLuckyGiftRewardResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreditLuckyGiftRewardResponse) + err := c.cc.Invoke(ctx, WalletService_CreditLuckyGiftReward_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *walletServiceClient) ApplyGameCoinChange(ctx context.Context, in *ApplyGameCoinChangeRequest, opts ...grpc.CallOption) (*ApplyGameCoinChangeResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ApplyGameCoinChangeResponse) @@ -737,6 +749,7 @@ type WalletServiceServer interface { ListAdminVipLevels(context.Context, *ListAdminVipLevelsRequest) (*ListAdminVipLevelsResponse, error) UpdateAdminVipLevels(context.Context, *UpdateAdminVipLevelsRequest) (*UpdateAdminVipLevelsResponse, error) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) + CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) GetRedPacketConfig(context.Context, *GetRedPacketConfigRequest) (*GetRedPacketConfigResponse, error) UpdateRedPacketConfig(context.Context, *UpdateRedPacketConfigRequest) (*UpdateRedPacketConfigResponse, error) @@ -893,6 +906,9 @@ func (UnimplementedWalletServiceServer) UpdateAdminVipLevels(context.Context, *U func (UnimplementedWalletServiceServer) CreditTaskReward(context.Context, *CreditTaskRewardRequest) (*CreditTaskRewardResponse, error) { return nil, status.Error(codes.Unimplemented, "method CreditTaskReward not implemented") } +func (UnimplementedWalletServiceServer) CreditLuckyGiftReward(context.Context, *CreditLuckyGiftRewardRequest) (*CreditLuckyGiftRewardResponse, error) { + return nil, status.Error(codes.Unimplemented, "method CreditLuckyGiftReward not implemented") +} func (UnimplementedWalletServiceServer) ApplyGameCoinChange(context.Context, *ApplyGameCoinChangeRequest) (*ApplyGameCoinChangeResponse, error) { return nil, status.Error(codes.Unimplemented, "method ApplyGameCoinChange not implemented") } @@ -1766,6 +1782,24 @@ func _WalletService_CreditTaskReward_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _WalletService_CreditLuckyGiftReward_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreditLuckyGiftRewardRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).CreditLuckyGiftReward(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_CreditLuckyGiftReward_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).CreditLuckyGiftReward(ctx, req.(*CreditLuckyGiftRewardRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _WalletService_ApplyGameCoinChange_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ApplyGameCoinChangeRequest) if err := dec(in); err != nil { @@ -2101,6 +2135,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "CreditTaskReward", Handler: _WalletService_CreditTaskReward_Handler, }, + { + MethodName: "CreditLuckyGiftReward", + Handler: _WalletService_CreditLuckyGiftReward_Handler, + }, { MethodName: "ApplyGameCoinChange", Handler: _WalletService_ApplyGameCoinChange_Handler, diff --git a/docs/flutter对接/幸运礼物Flutter对接.md b/docs/flutter对接/幸运礼物Flutter对接.md index 12df1c3e..925dbad2 100644 --- a/docs/flutter对接/幸运礼物Flutter对接.md +++ b/docs/flutter对接/幸运礼物Flutter对接.md @@ -60,19 +60,14 @@ App 不要展示后台 RTP 数值,不要展示实时概率,不要本地推 | --- | --- | | `POST /api/v1/rooms/gift/send` 支持 `pool_id` 入参 | 已有 | | 房间送礼 IM `room_gift_sent` 可携带 `pool_id` | 已有 | +| `POST /api/v1/activities/lucky-gifts/check` | 已有 | | `activity-service` 内部 gRPC `CheckLuckyGift` / `ExecuteLuckyGiftDraw` | 已有 | | 后台按 `pool_id` 配置多奖池 | 已有 | +| 送礼响应直接返回抽奖结果 `data.lucky_gift` | 已有 | +| 房间 IM `lucky_gift_drawn` | 已有 | +| 中奖返奖入账并触发 `WalletBalanceChanged` | 已有 | -Flutter 完整体验还需要后端补齐: - -| 能力 | 建议 | -| --- | --- | -| App 查询某个奖池是否可抽 | 新增 HTTP `POST /api/v1/activities/lucky-gifts/check` | -| 送礼响应直接返回抽奖结果 | 在 `/rooms/gift/send` 成功 `data` 中增加 `lucky_gift` | -| 中奖房间表现 | 新增房间 IM `lucky_gift_drawn` | -| 返奖到账 | 钱包侧新增幸运礼物返奖入账后继续发 `WalletBalanceChanged` | - -下面文档按最终 Flutter 契约编写;已存在字段可直接对接,标注为“建议新增”的字段需要后端继续落地。 +简洁版接入清单见 [幸运礼物接口闭环Flutter对接.md](/Users/hy/Documents/hy/hyapp-server/docs/flutter对接/幸运礼物接口闭环Flutter对接.md)。 ## 客户端流程 @@ -585,4 +580,3 @@ Payload 示例: 6. 监听房间 IM `room_gift_sent`、`lucky_gift_drawn`,同步其他用户看到的表现。 7. 监听 C2C IM `WalletBalanceChanged`,更新金币余额。 8. 任意 IM 丢失、页面重进或余额不一致时,重新拉取礼物面板和钱包余额纠正状态。 - diff --git a/docs/flutter对接/幸运礼物接口闭环Flutter对接.md b/docs/flutter对接/幸运礼物接口闭环Flutter对接.md new file mode 100644 index 00000000..4a3ff69a --- /dev/null +++ b/docs/flutter对接/幸运礼物接口闭环Flutter对接.md @@ -0,0 +1,171 @@ +# 幸运礼物 Flutter 对接 + +本文只描述 App 需要接入的 HTTP、送礼返回、错误处理和腾讯云 IM 自定义消息。所有接口响应都使用 gateway 业务 envelope: + +```json +{ + "code": "OK", + "message": "", + "request_id": "req_xxx", + "data": {} +} +``` + +## 1. 送礼前检查 + +`POST /api/v1/activities/lucky-gifts/check` + +请求头: + +| 参数 | 必填 | 说明 | +| --- | --- | --- | +| `Authorization` | 是 | `Bearer ` | +| `X-App-Code` | 否 | 不传时使用默认 App | + +请求体: + +| 参数 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `room_id` | string | 是 | 当前房间 ID | +| `gift_id` | string | 是 | 实际送出的礼物 ID,例如 `rose` | +| `pool_id` | string | 是 | 幸运礼物奖池 ID,例如 `lucky`、`super_lucky` | + +返回 `data`: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `enabled` | bool | 是否可按幸运礼物发送 | +| `reason` | string | `enabled`、`not_configured`、`disabled` | +| `room_id` | string | 请求房间 ID | +| `gift_id` | string | 请求礼物 ID | +| `pool_id` | string | 生效奖池 ID | +| `gift_price` | int | 单次抽奖价格,金币 | +| `rule_version` | int | 后端规则版本 | +| `target_rtp_ppm` | int | RTP 目标,ppm | +| `experience_pool` | string | `novice`、`intermediate`、`advanced` | + +客户端处理: + +- `enabled=true`:允许用户继续点击送礼。 +- `enabled=false`:不要走幸运礼物送礼;可降级普通礼物或提示当前不可用。 +- 检查结果只用于展示和拦截,最终仍以送礼接口为准。 + +## 2. 发送礼物 + +`POST /api/v1/rooms/gift/send` + +请求体关键字段: + +| 参数 | 类型 | 必填 | 说明 | +| --- | --- | --- | --- | +| `room_id` | string | 是 | 房间 ID | +| `target_user_id` | int/string | 是 | 收礼用户 ID | +| `gift_id` | string | 是 | 礼物 ID,例如 `rose` | +| `gift_count` | int | 是 | 数量,必须大于 0 | +| `pool_id` | string | 幸运礼物必填 | `lucky` 或 `super_lucky` | +| `client_sequence` | string | 建议 | 客户端幂等序列 | + +返回 `data.lucky_gift`: + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `draw_id` | string | 抽奖 ID,客户端去重锚点 | +| `command_id` | string | 送礼命令 ID | +| `pool_id` | string | 生效奖池 | +| `gift_id` | string | 礼物 ID | +| `rule_version` | int | 抽奖规则版本 | +| `experience_pool` | string | 体验池 | +| `selected_tier_id` | string | 命中奖档 | +| `multiplier_ppm` | int | 倍率,`2000000` 表示 2 倍 | +| `base_reward_coins` | int | RTP 基础返奖 | +| `room_atmosphere_reward_coins` | int | 房间气氛池返奖 | +| `activity_subsidy_coins` | int | 活动补贴返奖 | +| `effective_reward_coins` | int | 用户实际获得金币 | +| `reward_status` | string | 初始通常为 `pending`,到账由后端 outbox 补偿 | +| `stage_feedback` | bool | 是否阶段反馈,可播放轻量中奖表现 | +| `high_multiplier` | bool | 是否高倍 | +| `created_at_ms` | int | UTC epoch ms | + +客户端处理: + +- `data.lucky_gift != null`:立即调用 `_applyLuckyGiftResult()`,播放中奖内容和数量 SVGA。 +- `effective_reward_coins > 0`:展示中奖金额;余额不要本地相加,以 `WalletBalanceChanged` 或余额刷新为准。 +- `data.lucky_gift == null`:按普通礼物表现处理。 + +## 3. 错误处理 + +| code | 场景 | 客户端处理 | +| --- | --- | --- | +| `INVALID_ARGUMENT` | 参数缺失、数量非法、pool_id 缺失 | 停止提交,提示用户重试或刷新礼物面板 | +| `UNAUTHENTICATED` | token 失效 | 重新登录 | +| `INSUFFICIENT_BALANCE` | 金币不足 | 打开充值入口或提示余额不足 | +| `RULE_NOT_ACTIVE` | 幸运礼物规则未启用 | 降级普通礼物或刷新礼物配置 | +| `UPSTREAM_ERROR`/`UNAVAILABLE` | 内部服务或 IM/钱包临时不可用 | 不重复扣本地余额;允许用户稍后重试 | +| `IDEMPOTENCY_CONFLICT`/`CONFLICT` | 同一客户端命令参数变化 | 重新生成 `client_sequence` 后再提交 | + +送礼失败时后端不会返回 `lucky_gift`,客户端不要播放中奖 UI。送礼成功但返奖到账延迟时,房间 IM 和钱包 IM 会继续补偿。 + +## 4. 房间 IM + +腾讯云 IM 群自定义消息: + +| 字段 | 值 | +| --- | --- | +| `MsgType` | `TIMCustomElem` | +| `Desc` | `lucky_gift_drawn` | +| `Ext` | `room_system_message` | +| `Data` | JSON 字符串 | + +`Data` 示例: + +```json +{ + "event_type": "lucky_gift_drawn", + "event_id": "lucky_gift_drawn:lucky_draw_xxx", + "draw_id": "lucky_draw_xxx", + "command_id": "cmd_xxx", + "pool_id": "super_lucky", + "room_id": "room_1", + "gift_id": "rose", + "gift_count": 1, + "user_id": 42, + "sender_user_id": 42, + "target_user_id": 99, + "coin_spent": 100, + "multiplier_ppm": 2000000, + "effective_reward_coins": 200, + "stage_feedback": true, + "high_multiplier": false, + "created_at_ms": 1760000000000 +} +``` + +处理规则: + +- 只处理 `Desc == "lucky_gift_drawn"` 且 `event_type == "lucky_gift_drawn"`。 +- 用 `event_id` 或 `draw_id` 去重;同一抽奖不要重复播放。 +- 当前用户已通过 HTTP 送礼响应播放过同一 `draw_id` 时,收到 IM 只更新状态,不重复播放。 + +## 5. 钱包余额 IM + +中奖金币到账后,wallet-service 会写 `WalletBalanceChanged`,notice-service 通过腾讯云 IM C2C 投递。 + +| 字段 | 值 | +| --- | --- | +| `MsgType` | `TIMCustomElem` | +| `Desc` | `WalletBalanceChanged` | +| `Ext` | `wallet_notice` | + +客户端处理: + +- 只处理 `Desc == "WalletBalanceChanged"`。 +- 用 `event_id` 去重。 +- 当 `asset_type == "COIN"` 时,用 `available_after` 更新金币余额;字段缺失时调用 `GET /api/v1/wallet/me/balances` 刷新。 + +## 6. 接入顺序 + +1. 礼物面板拿到 `pool_id` 后调用 check。 +2. check 可用后调用送礼接口,并把 `pool_id` 放入请求。 +3. 送礼响应含 `data.lucky_gift` 时立即播放本机中奖 UI。 +4. 房间内监听 `lucky_gift_drawn`,给其他用户同步中奖 UI。 +5. 私聊监听 `WalletBalanceChanged`,更新金币余额。 diff --git a/docs/幸运礼物线上落地文档.md b/docs/幸运礼物线上落地文档.md index b7bf6d29..09fe13e6 100644 --- a/docs/幸运礼物线上落地文档.md +++ b/docs/幸运礼物线上落地文档.md @@ -18,8 +18,11 @@ | 风控计数 | done | user hour/day、device day、room hour、anchor day | | 房间气氛池 | done | 独立预算,不污染基础 RTP | | 活动预算池 | done | 独立补贴,不污染基础 RTP | +| 返奖入账 | done | `activity_outbox` worker 调 `wallet-service.CreditLuckyGiftReward`,钱包 outbox 继续触发余额私信 | +| 房间中奖 IM | done | `activity_outbox` worker 投递腾讯云 IM 群消息 `lucky_gift_drawn` | +| App 检查接口 | done | gateway 暴露 `POST /api/v1/activities/lucky-gifts/check` | -暂不在 `activity-service` 事务内调用钱包或房间 RPC。抽奖事务只写事实和 `activity_outbox`;后续由 outbox dispatcher 或房间链路消费结果。 +`activity-service` 抽奖事务内仍不调用钱包、房间 RPC 或腾讯云 IM。抽奖事务只写事实和 `activity_outbox`;事务外由 lucky gift worker 抢占 outbox,先幂等返奖,再投递房间中奖 IM,最后标记 draw 和 outbox 状态。 ## Call Flow @@ -27,9 +30,12 @@ room-service SendGift 1. CheckLuckyGift(meta, user_id, room_id, gift_id, pool_id) 2. wallet-service 扣费成功 - 3. ExecuteLuckyGiftDraw(command_id, user_id, device_id, room_id, anchor_id, gift_id, pool_id, coin_spent, paid_at_ms) + 3. ExecuteLuckyGiftDraw(command_id, user_id, target_user_id, gift_count, device_id, room_id, anchor_id, gift_id, pool_id, coin_spent, paid_at_ms) 4. activity-service 本地事务写 draw fact + outbox - 5. 事务后由钱包/房间表现链路处理 reward_status=pending 的结果 + 5. lucky gift worker 幂等调用 wallet-service.CreditLuckyGiftReward + 6. wallet-service 写 WalletBalanceChanged,notice-service 投递私信余额通知 + 7. lucky gift worker 投递房间群 IM lucky_gift_drawn + 8. activity-service 标记 reward_status=granted、outbox=delivered ``` `command_id` 是抽奖幂等键。重复 `ExecuteLuckyGiftDraw` 会返回同一条 `lucky_draw_records`,不会重复消耗 RTP、奖池或预算。 diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index 4abfc739..dcce12bc 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -17,6 +17,13 @@ first_recharge_reward_worker: enabled: false red_packet_broadcast_worker: enabled: false +lucky_gift_worker: + enabled: true + worker_poll_interval: "1s" + worker_batch_size: 100 + worker_lock_ttl: "30s" + worker_max_retry: 8 + publish_timeout: "5s" tencent_im: # Docker 本地联调全局/区域播报群;必须与 gateway/room-service 使用同一组腾讯 IM 应用配置。 enabled: true diff --git a/services/activity-service/configs/config.tencent.example.yaml b/services/activity-service/configs/config.tencent.example.yaml index e976addd..4057b2a7 100644 --- a/services/activity-service/configs/config.tencent.example.yaml +++ b/services/activity-service/configs/config.tencent.example.yaml @@ -17,6 +17,13 @@ first_recharge_reward_worker: enabled: true red_packet_broadcast_worker: enabled: true +lucky_gift_worker: + enabled: true + worker_poll_interval: "1s" + worker_batch_size: 100 + worker_lock_ttl: "30s" + worker_max_retry: 8 + publish_timeout: "5s" tencent_im: # 腾讯云 IM 应用配置;必须和 gateway 的 UserSig、room-service 房间群配置属于同一个 SDKAppID。 enabled: true diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index 18d095e3..884ce5c9 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -17,6 +17,13 @@ first_recharge_reward_worker: enabled: false red_packet_broadcast_worker: enabled: false +lucky_gift_worker: + enabled: true + worker_poll_interval: "1s" + worker_batch_size: 100 + worker_lock_ttl: "30s" + worker_max_retry: 8 + publish_timeout: "5s" tencent_im: # activity-service 只负责全局/区域播报群;必须与 gateway/room-service 使用同一组腾讯 IM 应用配置。 enabled: true diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index 0fdaf0b1..cabed0d0 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -38,6 +38,9 @@ CREATE TABLE IF NOT EXISTS activity_outbox ( status VARCHAR(32) NOT NULL COMMENT '业务状态', retry_count INT NOT NULL DEFAULT 0 COMMENT '重试次数', next_retry_at_ms BIGINT NOT NULL COMMENT '下一次重试时间,UTC epoch ms', + locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前锁定 worker', + lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '锁过期时间,UTC epoch ms', + last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次失败原因', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', PRIMARY KEY (app_code, outbox_id), @@ -194,6 +197,8 @@ CREATE TABLE IF NOT EXISTS lucky_draw_records ( pool_snapshot_json JSON NOT NULL COMMENT '奖池快照', rtp_snapshot_json JSON NOT NULL COMMENT 'RTP 快照', reward_status VARCHAR(32) NOT NULL COMMENT 'pending/granted/failed', + reward_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '返奖钱包交易 ID', + reward_failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '返奖失败原因', paid_at_ms BIGINT NOT NULL COMMENT '扣费时间,UTC epoch ms', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', diff --git a/services/activity-service/internal/app/app.go b/services/activity-service/internal/app/app.go index ec787130..9c128c09 100644 --- a/services/activity-service/internal/app/app.go +++ b/services/activity-service/internal/app/app.go @@ -49,9 +49,12 @@ type App struct { healthHTTP *healthhttp.Server mysqlRepo *mysqlstorage.Repository broadcast *broadcastservice.Service + luckyGift *luckygiftservice.Service firstRechargeReward *firstrechargeservice.Service broadcastWorkerEnabled bool + luckyGiftWorkerEnabled bool workerNodeID string + luckyGiftWorkerOptions luckygiftservice.WorkerOptions mqConsumers []*rocketmqx.Consumer userConn *grpc.ClientConn walletConn *grpc.ClientConn @@ -105,10 +108,10 @@ func New(cfg config.Config) (*App, error) { sevenDayCheckInSvc := sevendaycheckinservice.New(repository, walletv1.NewWalletServiceClient(walletConn)) growthSvc := growthservice.New(repository, walletv1.NewWalletServiceClient(walletConn)) achievementSvc := achievementservice.New(repository, walletv1.NewWalletServiceClient(walletConn)) - luckyGiftSvc := luckygiftservice.New(repository) + var tencentClient *tencentim.RESTClient var broadcastPublisher broadcastservice.Publisher if cfg.TencentIM.Enabled { - tencentClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig()) + tencentClient, err = tencentim.NewRESTClient(cfg.TencentIM.RESTConfig()) if err != nil { _ = walletConn.Close() _ = userConn.Close() @@ -118,6 +121,10 @@ func New(cfg config.Config) (*App, error) { } broadcastPublisher = tencentClient } + luckyGiftSvc := luckygiftservice.New(repository, + luckygiftservice.WithWallet(walletv1.NewWalletServiceClient(walletConn)), + luckygiftservice.WithRoomPublisher(tencentClient), + ) if cfg.Broadcast.Enabled && broadcastPublisher == nil { _ = walletConn.Close() _ = userConn.Close() @@ -263,9 +270,12 @@ func New(cfg config.Config) (*App, error) { healthHTTP: healthHTTP, mysqlRepo: repository, broadcast: broadcastSvc, + luckyGift: luckyGiftSvc, firstRechargeReward: firstRechargeRewardSvc, broadcastWorkerEnabled: cfg.Broadcast.Enabled, + luckyGiftWorkerEnabled: cfg.LuckyGiftWorker.Enabled, workerNodeID: cfg.NodeID, + luckyGiftWorkerOptions: luckyGiftWorkerOptions(cfg.NodeID, cfg.LuckyGiftWorker), mqConsumers: mqConsumers, userConn: userConn, walletConn: walletConn, @@ -287,6 +297,11 @@ func (a *App) Run() error { a.broadcast.RunWorker(a.workerCtx, broadcastservice.WorkerOptions{}) }) } + if a.luckyGiftWorkerEnabled && a.luckyGift != nil && a.workerCtx != nil { + a.workerWG.Go(func() { + a.luckyGift.RunWorker(a.workerCtx, a.luckyGiftWorkerOptions) + }) + } err := a.server.Serve(a.listener) if a.workerStop != nil { a.workerStop() @@ -481,3 +496,14 @@ func redPacketRegionIDFromWalletPayload(payload string) int64 { return 0 } } + +func luckyGiftWorkerOptions(nodeID string, cfg config.LuckyGiftWorkerConfig) luckygiftservice.WorkerOptions { + return luckygiftservice.WorkerOptions{ + WorkerID: nodeID + "-lucky-gift", + PollInterval: cfg.WorkerPollInterval, + BatchSize: cfg.WorkerBatchSize, + LockTTL: cfg.WorkerLockTTL, + MaxRetry: cfg.WorkerMaxRetry, + PublishTimeout: cfg.PublishTimeout, + } +} diff --git a/services/activity-service/internal/config/config.go b/services/activity-service/internal/config/config.go index 3eb09c3d..59fdce61 100644 --- a/services/activity-service/internal/config/config.go +++ b/services/activity-service/internal/config/config.go @@ -29,6 +29,8 @@ type Config struct { FirstRechargeRewardWorker FirstRechargeRewardWorkerConfig `yaml:"first_recharge_reward_worker"` // RedPacketBroadcastWorker 控制红包创建事实转区域飘屏 outbox 的本地消费。 RedPacketBroadcastWorker RedPacketBroadcastWorkerConfig `yaml:"red_packet_broadcast_worker"` + // LuckyGiftWorker 控制幸运礼物 draw outbox 的返奖和房间 IM 补偿。 + LuckyGiftWorker LuckyGiftWorkerConfig `yaml:"lucky_gift_worker"` // TencentIM 是 activity-service 发送全局/区域播报群消息的 REST 配置。 TencentIM TencentIMConfig `yaml:"tencent_im"` // Broadcast 控制 IM 播报群 reconciler、outbox worker 和贵重礼物阈值。 @@ -65,6 +67,16 @@ type RedPacketBroadcastWorkerConfig struct { Enabled bool `yaml:"enabled"` } +// LuckyGiftWorkerConfig 保存幸运礼物抽奖副作用补偿策略。 +type LuckyGiftWorkerConfig struct { + Enabled bool `yaml:"enabled"` + WorkerPollInterval time.Duration `yaml:"worker_poll_interval"` + WorkerBatchSize int `yaml:"worker_batch_size"` + WorkerLockTTL time.Duration `yaml:"worker_lock_ttl"` + WorkerMaxRetry int `yaml:"worker_max_retry"` + PublishTimeout time.Duration `yaml:"publish_timeout"` +} + // TencentIMConfig 描述 activity-service 调用腾讯云 IM REST API 的配置。 type TencentIMConfig struct { Enabled bool `yaml:"enabled"` @@ -153,6 +165,14 @@ func Default() Config { RedPacketBroadcastWorker: RedPacketBroadcastWorkerConfig{ Enabled: false, }, + LuckyGiftWorker: LuckyGiftWorkerConfig{ + Enabled: false, + WorkerPollInterval: time.Second, + WorkerBatchSize: 100, + WorkerLockTTL: 30 * time.Second, + WorkerMaxRetry: 8, + PublishTimeout: 5 * time.Second, + }, TencentIM: TencentIMConfig{ Enabled: false, AdminIdentifier: "administrator", @@ -271,6 +291,21 @@ func Load(path string) (Config, error) { if cfg.Broadcast.WorkerMaxRetry <= 0 { cfg.Broadcast.WorkerMaxRetry = 8 } + if cfg.LuckyGiftWorker.WorkerPollInterval <= 0 { + cfg.LuckyGiftWorker.WorkerPollInterval = time.Second + } + if cfg.LuckyGiftWorker.WorkerBatchSize <= 0 { + cfg.LuckyGiftWorker.WorkerBatchSize = 100 + } + if cfg.LuckyGiftWorker.WorkerLockTTL <= 0 { + cfg.LuckyGiftWorker.WorkerLockTTL = 30 * time.Second + } + if cfg.LuckyGiftWorker.WorkerMaxRetry <= 0 { + cfg.LuckyGiftWorker.WorkerMaxRetry = 8 + } + if cfg.LuckyGiftWorker.PublishTimeout <= 0 { + cfg.LuckyGiftWorker.PublishTimeout = 5 * time.Second + } rocketMQ, err := normalizeRocketMQConfig(cfg.RocketMQ) if err != nil { return Config{}, err @@ -279,6 +314,9 @@ func Load(path string) (Config, error) { if (cfg.FirstRechargeRewardWorker.Enabled || cfg.RedPacketBroadcastWorker.Enabled) && !cfg.RocketMQ.WalletOutbox.Enabled { return Config{}, errors.New("wallet outbox workers require rocketmq.wallet_outbox.enabled") } + if cfg.LuckyGiftWorker.Enabled && !cfg.TencentIM.Enabled { + return Config{}, errors.New("lucky gift worker requires tencent_im.enabled") + } return cfg, nil } diff --git a/services/activity-service/internal/domain/luckygift/lucky_gift.go b/services/activity-service/internal/domain/luckygift/lucky_gift.go index 64d6cd6e..c4e99874 100644 --- a/services/activity-service/internal/domain/luckygift/lucky_gift.go +++ b/services/activity-service/internal/domain/luckygift/lucky_gift.go @@ -8,6 +8,8 @@ const ( StatusPaused = "paused" StatusClosed = "closed" StatusPending = "pending" + StatusGranted = "granted" + StatusFailed = "failed" PoolNovice = "novice" PoolIntermediate = "intermediate" @@ -82,15 +84,17 @@ type CheckCommand struct { } type DrawCommand struct { - CommandID string - PoolID string - UserID int64 - DeviceID string - RoomID string - AnchorID string - GiftID string - CoinSpent int64 - PaidAtMS int64 + CommandID string + PoolID string + UserID int64 + TargetUserID int64 + DeviceID string + RoomID string + AnchorID string + GiftID string + GiftCount int32 + CoinSpent int64 + PaidAtMS int64 } type CheckResult struct { @@ -157,3 +161,13 @@ type DrawQuery struct { Page int32 PageSize int32 } + +// DrawOutbox 是幸运礼物抽奖事务写出的异步副作用载体,worker 只消费这个持久事实。 +type DrawOutbox struct { + AppCode string + OutboxID string + EventType string + PayloadJSON string + RetryCount int + CreatedAtMS int64 +} diff --git a/services/activity-service/internal/service/luckygift/service.go b/services/activity-service/internal/service/luckygift/service.go index de7106d2..bc1a2f7c 100644 --- a/services/activity-service/internal/service/luckygift/service.go +++ b/services/activity-service/internal/service/luckygift/service.go @@ -2,10 +2,17 @@ package luckygift import ( "context" + "encoding/json" + "fmt" + "log/slog" "strings" "time" + "google.golang.org/grpc" + walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" + "hyapp/pkg/logx" + "hyapp/pkg/tencentim" "hyapp/pkg/xerr" domain "hyapp/services/activity-service/internal/domain/luckygift" ) @@ -19,16 +26,54 @@ type Repository interface { ExecuteLuckyGiftDraw(ctx context.Context, cmd domain.DrawCommand, nowMS int64) (domain.DrawResult, error) ListLuckyGiftDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) GetLuckyGiftDrawSummary(ctx context.Context, query domain.DrawQuery) (domain.DrawSummary, error) + ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.DrawOutbox, error) + MarkLuckyGiftOutboxDelivered(ctx context.Context, event domain.DrawOutbox, nowMS int64) error + MarkLuckyGiftOutboxRetryable(ctx context.Context, event domain.DrawOutbox, retryCount int, nextRetryAtMS int64, lastErr string, nowMS int64) error + MarkLuckyGiftOutboxFailed(ctx context.Context, event domain.DrawOutbox, retryCount int, lastErr string, nowMS int64) error + MarkLuckyGiftDrawGranted(ctx context.Context, appCode string, drawID string, walletTransactionID string, nowMS int64) error + MarkLuckyGiftDrawFailed(ctx context.Context, appCode string, drawID string, failureReason string, nowMS int64) error +} + +// WalletClient 是幸运礼物返奖唯一账务依赖;实际余额变更通知继续由 wallet outbox -> notice-service 投递。 +type WalletClient interface { + CreditLuckyGiftReward(ctx context.Context, req *walletv1.CreditLuckyGiftRewardRequest, opts ...grpc.CallOption) (*walletv1.CreditLuckyGiftRewardResponse, error) +} + +// RoomPublisher 只负责投递房间群自定义消息,不拥有房间状态。 +type RoomPublisher interface { + PublishGroupCustomMessage(ctx context.Context, message tencentim.CustomGroupMessage) error } // Service 承载幸运礼物查询、抽奖命令和后台规则配置。 type Service struct { repository Repository + wallet WalletClient + publisher RoomPublisher now func() time.Time } -func New(repository Repository) *Service { - return &Service{repository: repository, now: time.Now} +type Option func(*Service) + +func WithWallet(client WalletClient) Option { + return func(s *Service) { + s.wallet = client + } +} + +func WithRoomPublisher(publisher RoomPublisher) Option { + return func(s *Service) { + s.publisher = publisher + } +} + +func New(repository Repository, options ...Option) *Service { + service := &Service{repository: repository, now: time.Now} + for _, option := range options { + if option != nil { + option(service) + } + } + return service } func (s *Service) SetClock(now func() time.Time) { @@ -63,12 +108,131 @@ func (s *Service) Draw(ctx context.Context, cmd domain.DrawCommand) (domain.Draw if cmd.CommandID == "" || cmd.UserID <= 0 || cmd.DeviceID == "" || cmd.RoomID == "" || cmd.AnchorID == "" || cmd.GiftID == "" || cmd.CoinSpent <= 0 { return domain.DrawResult{}, xerr.New(xerr.InvalidArgument, "lucky gift draw command is incomplete") } + if cmd.TargetUserID < 0 || cmd.GiftCount < 0 { + return domain.DrawResult{}, xerr.New(xerr.InvalidArgument, "lucky gift draw metadata is invalid") + } if cmd.PaidAtMS <= 0 { cmd.PaidAtMS = s.now().UTC().UnixMilli() } return s.repository.ExecuteLuckyGiftDraw(ctx, cmd, s.now().UTC().UnixMilli()) } +type WorkerOptions struct { + WorkerID string + PollInterval time.Duration + BatchSize int + LockTTL time.Duration + MaxRetry int + PublishTimeout time.Duration +} + +func (s *Service) RunWorker(ctx context.Context, options WorkerOptions) { + options = normalizeWorkerOptions(options) + ticker := time.NewTicker(options.PollInterval) + defer ticker.Stop() + for { + if _, err := s.ProcessPendingDrawOutbox(ctx, options); err != nil && ctx.Err() == nil { + // 批处理失败不删除 outbox;锁过期后下一轮或其他实例会继续补偿。 + logx.Error(ctx, "lucky_gift_outbox_batch_failed", err, slog.String("worker_id", options.WorkerID)) + } + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + } +} + +func (s *Service) ProcessPendingDrawOutbox(ctx context.Context, options WorkerOptions) (int, error) { + if err := s.requireRepository(); err != nil { + return 0, err + } + options = normalizeWorkerOptions(options) + events, err := s.repository.ClaimPendingLuckyGiftOutbox(ctx, options.WorkerID, s.now().UTC().UnixMilli(), options.LockTTL, options.BatchSize) + if err != nil { + return 0, err + } + processed := 0 + for _, event := range events { + if err := s.processDrawOutbox(ctx, event, options); err != nil { + return processed, err + } + processed++ + } + return processed, nil +} + +func (s *Service) processDrawOutbox(ctx context.Context, event domain.DrawOutbox, options WorkerOptions) error { + payload, err := decodeDrawOutboxPayload(event.PayloadJSON) + if err != nil { + return s.markOutboxFailed(ctx, event, options, err, false) + } + eventCtx := appcode.WithContext(ctx, event.AppCode) + walletTransactionID := "" + credited := false + if payload.EffectiveRewardCoins > 0 { + if s.wallet == nil { + return s.markOutboxFailed(eventCtx, event, options, fmt.Errorf("lucky gift wallet client is not configured"), false) + } + resp, err := s.wallet.CreditLuckyGiftReward(eventCtx, &walletv1.CreditLuckyGiftRewardRequest{ + CommandId: "lucky_reward:" + payload.DrawID, + AppCode: event.AppCode, + TargetUserId: payload.UserID, + Amount: payload.EffectiveRewardCoins, + DrawId: payload.DrawID, + RoomId: payload.RoomID, + GiftId: payload.GiftID, + PoolId: payload.PoolID, + Reason: "lucky_gift_reward", + }) + if err != nil { + return s.markOutboxFailed(eventCtx, event, options, err, false) + } + walletTransactionID = resp.GetTransactionId() + credited = true + } + if s.publisher == nil { + return s.markOutboxFailed(eventCtx, event, options, fmt.Errorf("lucky gift room publisher is not configured"), credited) + } + if err := s.publishLuckyGiftDrawn(eventCtx, payload, options); err != nil { + return s.markOutboxFailed(eventCtx, event, options, err, credited) + } + nowMS := s.now().UTC().UnixMilli() + if err := s.repository.MarkLuckyGiftDrawGranted(eventCtx, event.AppCode, payload.DrawID, walletTransactionID, nowMS); err != nil { + return err + } + return s.repository.MarkLuckyGiftOutboxDelivered(eventCtx, event, nowMS) +} + +func (s *Service) publishLuckyGiftDrawn(ctx context.Context, payload luckyGiftDrawnPayload, options WorkerOptions) error { + body, err := json.Marshal(payload) + if err != nil { + return err + } + publishCtx, cancel := context.WithTimeout(ctx, options.PublishTimeout) + defer cancel() + return s.publisher.PublishGroupCustomMessage(publishCtx, tencentim.CustomGroupMessage{ + GroupID: payload.RoomID, + EventID: payload.EventID, + Desc: "lucky_gift_drawn", + Ext: "room_system_message", + PayloadJSON: body, + }) +} + +func (s *Service) markOutboxFailed(ctx context.Context, event domain.DrawOutbox, options WorkerOptions, cause error, alreadyCredited bool) error { + nowMS := s.now().UTC().UnixMilli() + nextRetryCount := event.RetryCount + 1 + if nextRetryCount >= options.MaxRetry { + if !alreadyCredited { + _ = s.repository.MarkLuckyGiftDrawFailed(ctx, event.AppCode, drawIDFromPayload(event.PayloadJSON), cause.Error(), nowMS) + } + return s.repository.MarkLuckyGiftOutboxFailed(ctx, event, nextRetryCount, cause.Error(), nowMS) + } + nextRetryAtMS := nowMS + luckyGiftBackoff(nextRetryCount).Milliseconds() + return s.repository.MarkLuckyGiftOutboxRetryable(ctx, event, nextRetryCount, nextRetryAtMS, cause.Error(), nowMS) +} + func (s *Service) GetConfig(ctx context.Context, poolID string) (domain.Config, error) { if err := s.requireRepository(); err != nil { return domain.Config{}, err @@ -143,3 +307,94 @@ func normalizeDrawQuery(query domain.DrawQuery) domain.DrawQuery { query.Status = strings.TrimSpace(query.Status) return query } + +type luckyGiftDrawnPayload struct { + EventType string `json:"event_type"` + EventID string `json:"event_id"` + AppCode string `json:"app_code"` + DrawID string `json:"draw_id"` + CommandID string `json:"command_id"` + PoolID string `json:"pool_id"` + RoomID string `json:"room_id"` + GiftID string `json:"gift_id"` + GiftCount int32 `json:"gift_count"` + UserID int64 `json:"user_id"` + SenderUserID int64 `json:"sender_user_id"` + TargetUserID int64 `json:"target_user_id"` + CoinSpent int64 `json:"coin_spent"` + RuleVersion int64 `json:"rule_version"` + ExperiencePool string `json:"experience_pool"` + SelectedTierID string `json:"selected_tier_id"` + MultiplierPPM int64 `json:"multiplier_ppm"` + BaseRewardCoins int64 `json:"base_reward_coins"` + RoomAtmosphereRewardCoins int64 `json:"room_atmosphere_reward_coins"` + ActivitySubsidyCoins int64 `json:"activity_subsidy_coins"` + EffectiveRewardCoins int64 `json:"effective_reward_coins"` + StageFeedback bool `json:"stage_feedback"` + HighMultiplier bool `json:"high_multiplier"` + CreatedAtMS int64 `json:"created_at_ms"` +} + +func decodeDrawOutboxPayload(payloadJSON string) (luckyGiftDrawnPayload, error) { + var payload luckyGiftDrawnPayload + if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil { + return luckyGiftDrawnPayload{}, fmt.Errorf("invalid lucky gift outbox payload: %w", err) + } + if payload.EventType == "" { + payload.EventType = "lucky_gift_drawn" + } + if payload.EventID == "" && payload.DrawID != "" { + payload.EventID = "lucky_gift_drawn:" + payload.DrawID + } + if payload.SenderUserID == 0 { + payload.SenderUserID = payload.UserID + } + if payload.DrawID == "" || payload.EventID == "" || payload.RoomID == "" || payload.GiftID == "" || payload.UserID <= 0 { + return luckyGiftDrawnPayload{}, fmt.Errorf("lucky gift outbox payload is incomplete") + } + return payload, nil +} + +func drawIDFromPayload(payloadJSON string) string { + payload, err := decodeDrawOutboxPayload(payloadJSON) + if err != nil { + return "" + } + return payload.DrawID +} + +func normalizeWorkerOptions(options WorkerOptions) WorkerOptions { + options.WorkerID = strings.TrimSpace(options.WorkerID) + if options.WorkerID == "" { + options.WorkerID = "lucky-gift-worker" + } + if options.PollInterval <= 0 { + options.PollInterval = time.Second + } + if options.BatchSize <= 0 { + options.BatchSize = 100 + } + if options.BatchSize > 500 { + options.BatchSize = 500 + } + if options.LockTTL <= 0 { + options.LockTTL = 30 * time.Second + } + if options.MaxRetry <= 0 { + options.MaxRetry = 8 + } + if options.PublishTimeout <= 0 { + options.PublishTimeout = 5 * time.Second + } + return options +} + +func luckyGiftBackoff(retryCount int) time.Duration { + if retryCount < 1 { + retryCount = 1 + } + if retryCount > 6 { + retryCount = 6 + } + return time.Duration(1< 0 || input.StageFeedback || input.Candidate.Presentation { payload, _ := json.Marshal(map[string]any{ - "draw_id": input.DrawID, - "pool_id": input.Config.GiftID, - "user_id": input.Command.UserID, - "room_id": input.Command.RoomID, - "gift_id": input.Command.GiftID, - "effective_reward_coins": input.Candidate.effectiveReward(), - "stage_feedback": input.StageFeedback, + "event_type": "lucky_gift_drawn", + "event_id": "lucky_gift_drawn:" + input.DrawID, + "app_code": input.AppCode, + "draw_id": input.DrawID, + "command_id": input.Command.CommandID, + "pool_id": input.Config.GiftID, + "user_id": input.Command.UserID, + "sender_user_id": input.Command.UserID, + "target_user_id": input.Command.TargetUserID, + "room_id": input.Command.RoomID, + "gift_id": input.Command.GiftID, + "gift_count": input.Command.GiftCount, + "coin_spent": input.Command.CoinSpent, + "rule_version": input.Config.RuleVersion, + "experience_pool": input.ExperiencePool, + "selected_tier_id": input.Candidate.TierID, + "multiplier_ppm": input.Candidate.MultiplierPPM, + "base_reward_coins": input.Candidate.BaseReward, + "room_atmosphere_reward_coins": input.Candidate.RoomReward, + "activity_subsidy_coins": input.Candidate.ActivityReward, + "effective_reward_coins": input.Candidate.effectiveReward(), + "stage_feedback": input.StageFeedback, + "high_multiplier": input.Candidate.HighMultiplier, + "created_at_ms": input.NowMS, }) if _, err := tx.ExecContext(ctx, ` INSERT INTO activity_outbox ( - app_code, outbox_id, event_type, payload, status, retry_count, next_retry_at_ms, created_at_ms, updated_at_ms - ) VALUES (?, ?, 'LuckyGiftDrawn', ?, 'pending', 0, ?, ?, ?)`, + app_code, outbox_id, event_type, payload, status, retry_count, next_retry_at_ms, + locked_by, lock_until_ms, last_error, created_at_ms, updated_at_ms + ) VALUES (?, ?, 'LuckyGiftDrawn', ?, 'pending', 0, ?, '', 0, '', ?, ?)`, input.AppCode, "lucky_"+input.DrawID, payload, input.NowMS, input.NowMS, input.NowMS, ); err != nil { return err @@ -591,6 +609,155 @@ func (r *Repository) applyLuckyDraw(ctx context.Context, tx *sql.Tx, input lucky return nil } +// ClaimPendingLuckyGiftOutbox 抢占幸运礼物抽奖副作用。抽奖事实已在同一事务落库,worker 只补偿钱包和房间 IM。 +func (r *Repository) ClaimPendingLuckyGiftOutbox(ctx context.Context, workerID string, nowMS int64, lockTTL time.Duration, batchSize int) ([]domain.DrawOutbox, error) { + if r == nil || r.db == nil { + return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + workerID = strings.TrimSpace(workerID) + if workerID == "" { + return nil, xerr.New(xerr.InvalidArgument, "worker_id is required") + } + if batchSize <= 0 { + batchSize = 100 + } + if batchSize > 500 { + batchSize = 500 + } + if lockTTL <= 0 { + lockTTL = 30 * time.Second + } + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer func() { _ = tx.Rollback() }() + + rows, err := tx.QueryContext(ctx, ` + SELECT app_code, outbox_id, event_type, COALESCE(CAST(payload AS CHAR), '{}'), retry_count, created_at_ms + FROM activity_outbox + WHERE event_type = 'LuckyGiftDrawn' + AND ( + (status IN ('pending', 'retryable') AND next_retry_at_ms <= ?) + OR (status = 'delivering' AND lock_until_ms <= ?) + ) + ORDER BY created_at_ms ASC, outbox_id ASC + LIMIT ? + FOR UPDATE`, nowMS, nowMS, batchSize) + if err != nil { + return nil, err + } + defer rows.Close() + events := make([]domain.DrawOutbox, 0, batchSize) + for rows.Next() { + var event domain.DrawOutbox + if err := rows.Scan(&event.AppCode, &event.OutboxID, &event.EventType, &event.PayloadJSON, &event.RetryCount, &event.CreatedAtMS); err != nil { + return nil, err + } + events = append(events, event) + } + if err := rows.Err(); err != nil { + return nil, err + } + lockUntilMS := nowMS + lockTTL.Milliseconds() + for _, event := range events { + if _, err := tx.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = 'delivering', locked_by = ?, lock_until_ms = ?, updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ?`, + workerID, lockUntilMS, nowMS, event.AppCode, event.OutboxID, + ); err != nil { + return nil, err + } + } + if err := tx.Commit(); err != nil { + return nil, err + } + return events, nil +} + +func (r *Repository) MarkLuckyGiftOutboxDelivered(ctx context.Context, event domain.DrawOutbox, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + _, err := r.db.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = 'delivered', locked_by = '', lock_until_ms = 0, last_error = '', updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ?`, + nowMS, appcode.Normalize(event.AppCode), event.OutboxID, + ) + return err +} + +func (r *Repository) MarkLuckyGiftOutboxRetryable(ctx context.Context, event domain.DrawOutbox, retryCount int, nextRetryAtMS int64, lastErr string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + _, err := r.db.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = 'retryable', retry_count = ?, next_retry_at_ms = ?, locked_by = '', lock_until_ms = 0, + last_error = ?, updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ?`, + retryCount, nextRetryAtMS, trimLuckyLastError(lastErr), nowMS, appcode.Normalize(event.AppCode), event.OutboxID, + ) + return err +} + +func (r *Repository) MarkLuckyGiftOutboxFailed(ctx context.Context, event domain.DrawOutbox, retryCount int, lastErr string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + _, err := r.db.ExecContext(ctx, ` + UPDATE activity_outbox + SET status = 'failed', retry_count = ?, locked_by = '', lock_until_ms = 0, last_error = ?, updated_at_ms = ? + WHERE app_code = ? AND outbox_id = ?`, + retryCount, trimLuckyLastError(lastErr), nowMS, appcode.Normalize(event.AppCode), event.OutboxID, + ) + return err +} + +func (r *Repository) MarkLuckyGiftDrawGranted(ctx context.Context, appCode string, drawID string, walletTransactionID string, nowMS int64) error { + if r == nil || r.db == nil { + return xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + _, err := r.db.ExecContext(ctx, ` + UPDATE lucky_draw_records + SET reward_status = ?, reward_transaction_id = ?, reward_failure_reason = '', updated_at_ms = ? + WHERE app_code = ? AND draw_id = ?`, + domain.StatusGranted, walletTransactionID, nowMS, appcode.Normalize(appCode), drawID, + ) + return err +} + +func (r *Repository) MarkLuckyGiftDrawFailed(ctx context.Context, appCode string, drawID string, failureReason string, nowMS int64) error { + if r == nil || r.db == nil || strings.TrimSpace(drawID) == "" { + return nil + } + _, err := r.db.ExecContext(ctx, ` + UPDATE lucky_draw_records + SET reward_status = ?, reward_failure_reason = ?, updated_at_ms = ? + WHERE app_code = ? AND draw_id = ? AND reward_status = ?`, + domain.StatusFailed, trimLuckyFailureReason(failureReason), nowMS, appcode.Normalize(appCode), drawID, domain.StatusPending, + ) + return err +} + +func trimLuckyLastError(value string) string { + value = strings.TrimSpace(value) + if len(value) > 512 { + return value[:512] + } + return value +} + +func trimLuckyFailureReason(value string) string { + value = strings.TrimSpace(value) + if len(value) > 255 { + return value[:255] + } + return value +} + func (r *Repository) ListLuckyGiftDraws(ctx context.Context, query domain.DrawQuery) ([]domain.DrawResult, int64, error) { if r == nil || r.db == nil { return nil, 0, xerr.New(xerr.Unavailable, "mysql repository is not configured") diff --git a/services/activity-service/internal/storage/mysql/repository.go b/services/activity-service/internal/storage/mysql/repository.go index 66017dcf..c78b555d 100644 --- a/services/activity-service/internal/storage/mysql/repository.go +++ b/services/activity-service/internal/storage/mysql/repository.go @@ -45,6 +45,15 @@ func (r *Repository) Migrate(ctx context.Context) error { if err := r.ensureLuckyDrawPoolID(ctx); err != nil { return err } + if err := r.ensureLuckyGiftOutboxColumns(ctx); err != nil { + return err + } + if err := r.ensureLuckyDrawRewardSettlementColumns(ctx); err != nil { + return err + } + if err := r.ensureDefaultLuckyGiftRules(ctx); err != nil { + return err + } return nil } @@ -94,6 +103,84 @@ func (r *Repository) ensureLuckyDrawPoolID(ctx context.Context) error { return nil } +func (r *Repository) ensureLuckyGiftOutboxColumns(ctx context.Context) error { + additions := []struct { + name string + sql string + }{ + {"locked_by", `ALTER TABLE activity_outbox ADD COLUMN locked_by VARCHAR(128) NOT NULL DEFAULT '' COMMENT '当前锁定 worker' AFTER next_retry_at_ms`}, + {"lock_until_ms", `ALTER TABLE activity_outbox ADD COLUMN lock_until_ms BIGINT NOT NULL DEFAULT 0 COMMENT '锁过期时间,UTC epoch ms' AFTER locked_by`}, + {"last_error", `ALTER TABLE activity_outbox ADD COLUMN last_error VARCHAR(512) NOT NULL DEFAULT '' COMMENT '最后一次失败原因' AFTER lock_until_ms`}, + } + for _, addition := range additions { + exists, err := r.columnExists(ctx, "activity_outbox", addition.name) + if err != nil { + return err + } + if !exists { + if _, err := r.db.ExecContext(ctx, addition.sql); err != nil { + return err + } + } + } + return nil +} + +func (r *Repository) ensureLuckyDrawRewardSettlementColumns(ctx context.Context) error { + additions := []struct { + name string + sql string + }{ + {"reward_transaction_id", `ALTER TABLE lucky_draw_records ADD COLUMN reward_transaction_id VARCHAR(128) NOT NULL DEFAULT '' COMMENT '返奖钱包交易 ID' AFTER reward_status`}, + {"reward_failure_reason", `ALTER TABLE lucky_draw_records ADD COLUMN reward_failure_reason VARCHAR(255) NOT NULL DEFAULT '' COMMENT '返奖失败原因' AFTER reward_transaction_id`}, + } + for _, addition := range additions { + exists, err := r.columnExists(ctx, "lucky_draw_records", addition.name) + if err != nil { + return err + } + if !exists { + if _, err := r.db.ExecContext(ctx, addition.sql); err != nil { + return err + } + } + } + return nil +} + +func (r *Repository) ensureDefaultLuckyGiftRules(ctx context.Context) error { + // 本地和 Docker 开发库通常保留 MySQL volume;这里补齐缺省 lucky/super_lucky 契约,但不覆盖后台已发布规则。 + const seedNowMS int64 = 1779259000000 + const seedTiersJSON = `[ + {"pool":"novice","tier_id":"novice_none","reward_coins":0,"multiplier_ppm":0,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"novice","tier_id":"novice_1x","reward_coins":100,"multiplier_ppm":1000000,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"novice","tier_id":"novice_2x","reward_coins":200,"multiplier_ppm":2000000,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"intermediate","tier_id":"intermediate_none","reward_coins":0,"multiplier_ppm":0,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"intermediate","tier_id":"intermediate_1x","reward_coins":100,"multiplier_ppm":1000000,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"intermediate","tier_id":"intermediate_2x","reward_coins":200,"multiplier_ppm":2000000,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"advanced","tier_id":"advanced_none","reward_coins":0,"multiplier_ppm":0,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"advanced","tier_id":"advanced_1x","reward_coins":100,"multiplier_ppm":1000000,"weight":0,"high_water_only":false,"enabled":true}, + {"pool":"advanced","tier_id":"advanced_2x","reward_coins":200,"multiplier_ppm":2000000,"weight":0,"high_water_only":false,"enabled":true} + ]` + _, err := r.db.ExecContext(ctx, ` + INSERT IGNORE INTO lucky_gift_rules ( + app_code, gift_id, enabled, rule_version, gift_price, target_rtp_ppm, pool_rate_ppm, + global_window_draws, gift_window_draws, novice_draw_limit, intermediate_draw_limit, + high_multiplier, high_water_pool_multiple, platform_pool_weight_ppm, room_pool_weight_ppm, + gift_pool_weight_ppm, initial_platform_pool, initial_gift_pool, initial_room_pool, + platform_reserve, gift_reserve, room_reserve, max_single_payout, user_hourly_payout_cap, + user_daily_payout_cap, device_daily_payout_cap, room_hourly_payout_cap, anchor_daily_payout_cap, + room_atmosphere_rate_ppm, room_atmosphere_initial, room_atmosphere_reserve, + activity_budget, activity_daily_limit, large_tier_enabled, tiers_json, + updated_by_admin_id, created_at_ms, updated_at_ms + ) VALUES + ('lalu', 'lucky', true, 1, 100, 950000, 950000, 100000, 100000, 2000, 20000, 100, 2, 200000, 300000, 500000, 9500000, 23750000, 5000000, 1000000, 1000000, 100000, 50000, 3420000, 61560000, 102600000, 68400000, 1231200000, 10000, 100000, 10000, 0, 0, true, ?, 0, ?, ?), + ('lalu', 'super_lucky', true, 1, 100, 950000, 950000, 100000, 100000, 2000, 20000, 100, 2, 200000, 300000, 500000, 9500000, 23750000, 5000000, 1000000, 1000000, 100000, 50000, 3420000, 61560000, 102600000, 68400000, 1231200000, 10000, 100000, 10000, 0, 0, true, ?, 0, ?, ?)`, + seedTiersJSON, seedNowMS, seedNowMS, seedTiersJSON, seedNowMS, seedNowMS, + ) + return err +} + func (r *Repository) columnExists(ctx context.Context, table string, column string) (bool, error) { database, err := r.currentDatabase(ctx) if err != nil { diff --git a/services/activity-service/internal/transport/grpc/lucky_gift_server.go b/services/activity-service/internal/transport/grpc/lucky_gift_server.go index d0b85525..f54081a5 100644 --- a/services/activity-service/internal/transport/grpc/lucky_gift_server.go +++ b/services/activity-service/internal/transport/grpc/lucky_gift_server.go @@ -48,15 +48,17 @@ func (s *LuckyGiftServer) ExecuteLuckyGiftDraw(ctx context.Context, req *activit meta := req.GetLuckyGift() ctx = appcode.WithContext(ctx, meta.GetMeta().GetAppCode()) result, err := s.svc.Draw(ctx, domain.DrawCommand{ - CommandID: meta.GetCommandId(), - PoolID: meta.GetPoolId(), - UserID: meta.GetUserId(), - DeviceID: meta.GetDeviceId(), - RoomID: meta.GetRoomId(), - AnchorID: meta.GetAnchorId(), - GiftID: meta.GetGiftId(), - CoinSpent: meta.GetCoinSpent(), - PaidAtMS: meta.GetPaidAtMs(), + CommandID: meta.GetCommandId(), + PoolID: meta.GetPoolId(), + UserID: meta.GetUserId(), + TargetUserID: meta.GetTargetUserId(), + DeviceID: meta.GetDeviceId(), + RoomID: meta.GetRoomId(), + AnchorID: meta.GetAnchorId(), + GiftID: meta.GetGiftId(), + GiftCount: meta.GetGiftCount(), + CoinSpent: meta.GetCoinSpent(), + PaidAtMS: meta.GetPaidAtMs(), }) if err != nil { return nil, xerr.ToGRPCError(err) diff --git a/services/gateway-service/internal/app/app.go b/services/gateway-service/internal/app/app.go index bf88036e..5f03470d 100644 --- a/services/gateway-service/internal/app/app.go +++ b/services/gateway-service/internal/app/app.go @@ -99,6 +99,7 @@ func New(cfg config.Config) (*App, error) { var registrationRewardClient client.RegistrationRewardClient = client.NewGRPCRegistrationRewardClient(activityConn) var firstRechargeRewardClient client.FirstRechargeRewardClient = client.NewGRPCFirstRechargeRewardClient(activityConn) var sevenDayCheckInClient client.SevenDayCheckInClient = client.NewGRPCSevenDayCheckInClient(activityConn) + var luckyGiftClient client.LuckyGiftClient = client.NewGRPCLuckyGiftClient(activityConn) var broadcastClient client.BroadcastClient = client.NewGRPCBroadcastClient(activityConn) var gameClient client.GameClient = client.NewGRPCGameClient(gameConn) appConfigReader, err := openAppConfigReader(cfg.AppConfig) @@ -143,6 +144,7 @@ func New(cfg config.Config) (*App, error) { handler.SetRegistrationRewardClient(registrationRewardClient) handler.SetFirstRechargeRewardClient(firstRechargeRewardClient) handler.SetSevenDayCheckInClient(sevenDayCheckInClient) + handler.SetLuckyGiftClient(luckyGiftClient) handler.SetBroadcastClient(broadcastClient) handler.SetGameClient(gameClient) handler.SetLeaderboardWalletDB(leaderboardWalletDB) diff --git a/services/gateway-service/internal/client/lucky_gift_client.go b/services/gateway-service/internal/client/lucky_gift_client.go new file mode 100644 index 00000000..66b1904a --- /dev/null +++ b/services/gateway-service/internal/client/lucky_gift_client.go @@ -0,0 +1,25 @@ +package client + +import ( + "context" + + "google.golang.org/grpc" + activityv1 "hyapp.local/api/proto/activity/v1" +) + +// LuckyGiftClient abstracts gateway access to activity-service lucky gift eligibility checks. +type LuckyGiftClient interface { + CheckLuckyGift(ctx context.Context, req *activityv1.CheckLuckyGiftRequest) (*activityv1.CheckLuckyGiftResponse, error) +} + +type grpcLuckyGiftClient struct { + client activityv1.LuckyGiftServiceClient +} + +func NewGRPCLuckyGiftClient(conn *grpc.ClientConn) LuckyGiftClient { + return &grpcLuckyGiftClient{client: activityv1.NewLuckyGiftServiceClient(conn)} +} + +func (c *grpcLuckyGiftClient) CheckLuckyGift(ctx context.Context, req *activityv1.CheckLuckyGiftRequest) (*activityv1.CheckLuckyGiftResponse, error) { + return c.client.CheckLuckyGift(ctx, req) +} diff --git a/services/gateway-service/internal/transport/http/activityapi/handler.go b/services/gateway-service/internal/transport/http/activityapi/handler.go index 78978a18..144ab475 100644 --- a/services/gateway-service/internal/transport/http/activityapi/handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/handler.go @@ -20,6 +20,7 @@ type Handler struct { registrationReward client.RegistrationRewardClient firstRechargeReward client.FirstRechargeRewardClient sevenDayCheckIn client.SevenDayCheckInClient + luckyGift client.LuckyGiftClient } type Config struct { @@ -33,6 +34,7 @@ type Config struct { RegistrationReward client.RegistrationRewardClient FirstRechargeReward client.FirstRechargeRewardClient SevenDayCheckIn client.SevenDayCheckInClient + LuckyGift client.LuckyGiftClient } func New(config Config) *Handler { @@ -47,6 +49,7 @@ func New(config Config) *Handler { registrationReward: config.RegistrationReward, firstRechargeReward: config.FirstRechargeReward, sevenDayCheckIn: config.SevenDayCheckIn, + luckyGift: config.LuckyGift, } } @@ -59,6 +62,7 @@ func (h *Handler) TaskHandlers() httproutes.TaskHandlers { GetSevenDayCheckInStatus: h.getSevenDayCheckInStatus, SignSevenDayCheckIn: h.signSevenDayCheckIn, ListUserLeaderboards: h.listUserLeaderboards, + CheckLuckyGift: h.checkLuckyGift, } } diff --git a/services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler.go b/services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler.go new file mode 100644 index 00000000..89fcc260 --- /dev/null +++ b/services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler.go @@ -0,0 +1,71 @@ +package activityapi + +import ( + "encoding/json" + "net/http" + "strings" + + activityv1 "hyapp.local/api/proto/activity/v1" + "hyapp/services/gateway-service/internal/auth" + "hyapp/services/gateway-service/internal/transport/http/httpkit" +) + +type luckyGiftCheckRequest struct { + RoomID string `json:"room_id"` + GiftID string `json:"gift_id"` + PoolID string `json:"pool_id"` +} + +type luckyGiftCheckData struct { + Enabled bool `json:"enabled"` + Reason string `json:"reason"` + RoomID string `json:"room_id"` + GiftID string `json:"gift_id"` + PoolID string `json:"pool_id"` + GiftPrice int64 `json:"gift_price"` + RuleVersion int64 `json:"rule_version"` + TargetRTPPPM int64 `json:"target_rtp_ppm"` + ExperiencePool string `json:"experience_pool"` +} + +// checkLuckyGift 只做送礼前资格检查;扣费、抽奖和返奖都必须继续走 room-service 送礼主链路。 +func (h *Handler) checkLuckyGift(writer http.ResponseWriter, request *http.Request) { + if h.luckyGift == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body luckyGiftCheckRequest + if err := json.NewDecoder(request.Body).Decode(&body); err != nil { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid request body") + return + } + body.RoomID = strings.TrimSpace(body.RoomID) + body.GiftID = strings.TrimSpace(body.GiftID) + body.PoolID = strings.TrimSpace(body.PoolID) + if body.RoomID == "" || body.GiftID == "" || body.PoolID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "room_id, gift_id and pool_id are required") + return + } + resp, err := h.luckyGift.CheckLuckyGift(request.Context(), &activityv1.CheckLuckyGiftRequest{ + Meta: httpkit.ActivityMeta(request), + UserId: auth.UserIDFromContext(request.Context()), + RoomId: body.RoomID, + GiftId: body.GiftID, + PoolId: body.PoolID, + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, luckyGiftCheckData{ + Enabled: resp.GetEnabled(), + Reason: resp.GetReason(), + RoomID: body.RoomID, + GiftID: resp.GetGiftId(), + PoolID: resp.GetPoolId(), + GiftPrice: resp.GetGiftPrice(), + RuleVersion: resp.GetRuleVersion(), + TargetRTPPPM: resp.GetTargetRtpPpm(), + ExperiencePool: resp.GetExperiencePool(), + }) +} diff --git a/services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler_test.go b/services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler_test.go new file mode 100644 index 00000000..0c500b59 --- /dev/null +++ b/services/gateway-service/internal/transport/http/activityapi/lucky_gift_handler_test.go @@ -0,0 +1,64 @@ +package activityapi + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + activityv1 "hyapp.local/api/proto/activity/v1" + "hyapp/pkg/appcode" + "hyapp/services/gateway-service/internal/auth" +) + +func TestCheckLuckyGiftForwardsEligibilityRequest(t *testing.T) { + client := &fakeLuckyGiftClient{resp: &activityv1.CheckLuckyGiftResponse{ + Enabled: true, + Reason: "enabled", + GiftId: "rose", + PoolId: "super_lucky", + GiftPrice: 100, + RuleVersion: 3, + TargetRtpPpm: 600000, + ExperiencePool: "novice", + }} + handler := New(Config{LuckyGift: client}) + request := httptest.NewRequest(http.MethodPost, "/api/v1/activities/lucky-gifts/check", strings.NewReader(`{"room_id":"room-1","gift_id":"rose","pool_id":"super_lucky"}`)) + request = request.WithContext(auth.WithUserID(appcode.WithContext(request.Context(), "lalu"), 42)) + recorder := httptest.NewRecorder() + + handler.checkLuckyGift(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("status mismatch: %d body=%s", recorder.Code, recorder.Body.String()) + } + if client.last == nil || client.last.GetUserId() != 42 || client.last.GetRoomId() != "room-1" || client.last.GetGiftId() != "rose" || client.last.GetPoolId() != "super_lucky" { + t.Fatalf("check request mismatch: %+v", client.last) + } + var envelope struct { + Code string `json:"code"` + Data struct { + Enabled bool `json:"enabled"` + Reason string `json:"reason"` + ExperiencePool string `json:"experience_pool"` + } `json:"data"` + } + if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { + t.Fatalf("decode response: %v", err) + } + if envelope.Code != "OK" || !envelope.Data.Enabled || envelope.Data.ExperiencePool != "novice" { + t.Fatalf("response mismatch: %+v", envelope) + } +} + +type fakeLuckyGiftClient struct { + last *activityv1.CheckLuckyGiftRequest + resp *activityv1.CheckLuckyGiftResponse +} + +func (c *fakeLuckyGiftClient) CheckLuckyGift(_ context.Context, req *activityv1.CheckLuckyGiftRequest) (*activityv1.CheckLuckyGiftResponse, error) { + c.last = req + return c.resp, nil +} diff --git a/services/gateway-service/internal/transport/http/handler.go b/services/gateway-service/internal/transport/http/handler.go index 1807ade7..aa13633f 100644 --- a/services/gateway-service/internal/transport/http/handler.go +++ b/services/gateway-service/internal/transport/http/handler.go @@ -41,6 +41,7 @@ type Handler struct { registrationReward client.RegistrationRewardClient firstRechargeReward client.FirstRechargeRewardClient sevenDayCheckIn client.SevenDayCheckInClient + luckyGift client.LuckyGiftClient broadcastClient client.BroadcastClient gameClient client.GameClient appConfigReader appapi.ConfigReader @@ -204,6 +205,11 @@ func (h *Handler) SetSevenDayCheckInClient(sevenDayCheckIn client.SevenDayCheckI h.sevenDayCheckIn = sevenDayCheckIn } +// SetLuckyGiftClient 注入 activity-service 幸运礼物资格检查 client。 +func (h *Handler) SetLuckyGiftClient(luckyGift client.LuckyGiftClient) { + h.luckyGift = luckyGift +} + // SetBroadcastClient 注入 activity-service 播报群成员关系 client。 func (h *Handler) SetBroadcastClient(broadcastClient client.BroadcastClient) { h.broadcastClient = broadcastClient diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index 6dcfea67..aa33b614 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -138,6 +138,7 @@ type TaskHandlers struct { GetSevenDayCheckInStatus http.HandlerFunc SignSevenDayCheckIn http.HandlerFunc ListUserLeaderboards http.HandlerFunc + CheckLuckyGift http.HandlerFunc } type LevelHandlers struct { @@ -343,6 +344,7 @@ func (r routes) registerTaskRoutes() { r.profile("/activities/seven-day-checkin", http.MethodGet, h.GetSevenDayCheckInStatus) r.profile("/activities/seven-day-checkin/sign", http.MethodPost, h.SignSevenDayCheckIn) r.profile("/activities/user-leaderboards", http.MethodGet, h.ListUserLeaderboards) + r.profile("/activities/lucky-gifts/check", http.MethodPost, h.CheckLuckyGift) r.profile("/tasks/tabs", http.MethodGet, h.ListTaskTabs) r.profile("/tasks/claim", http.MethodPost, h.ClaimTaskReward) } diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index af16cd60..04306a55 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -68,6 +68,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { RegistrationReward: h.registrationReward, FirstRechargeReward: h.firstRechargeReward, SevenDayCheckIn: h.sevenDayCheckIn, + LuckyGift: h.luckyGift, }) gameAPI := gameapi.New(gameapi.Config{ GameClient: h.gameClient, diff --git a/services/room-service/internal/room/service/gift.go b/services/room-service/internal/room/service/gift.go index ca942575..29150e53 100644 --- a/services/room-service/internal/room/service/gift.go +++ b/services/room-service/internal/room/service/gift.go @@ -126,16 +126,18 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r if luckyEnabled { drawResp, err := s.luckyGift.ExecuteLuckyGiftDraw(ctx, &activityv1.ExecuteLuckyGiftDrawRequest{ LuckyGift: &activityv1.LuckyGiftMeta{ - Meta: activityMetaFromRoom(ctx, req.GetMeta()), - CommandId: cmd.ID(), - UserId: cmd.ActorUserID(), - DeviceId: luckyGiftDeviceID(cmd), - RoomId: cmd.RoomID(), - AnchorId: luckyGiftAnchorID(roomMeta), - GiftId: cmd.GiftID, - CoinSpent: billing.GetCoinSpent(), - PaidAtMs: now.UTC().UnixMilli(), - PoolId: cmd.PoolID, + Meta: activityMetaFromRoom(ctx, req.GetMeta()), + CommandId: cmd.ID(), + UserId: cmd.ActorUserID(), + TargetUserId: cmd.TargetUserID, + DeviceId: luckyGiftDeviceID(cmd), + RoomId: cmd.RoomID(), + AnchorId: luckyGiftAnchorID(roomMeta), + GiftId: cmd.GiftID, + GiftCount: cmd.GiftCount, + CoinSpent: billing.GetCoinSpent(), + PaidAtMs: now.UTC().UnixMilli(), + PoolId: cmd.PoolID, }, }) if err != nil { diff --git a/services/wallet-service/internal/domain/ledger/ledger.go b/services/wallet-service/internal/domain/ledger/ledger.go index 33936cd6..2b5ac24b 100644 --- a/services/wallet-service/internal/domain/ledger/ledger.go +++ b/services/wallet-service/internal/domain/ledger/ledger.go @@ -553,6 +553,27 @@ type TaskRewardReceipt struct { GrantedAtMS int64 } +// LuckyGiftRewardCommand 是 activity-service 用抽奖 draw_id 发起的 COIN 入账命令。 +type LuckyGiftRewardCommand struct { + AppCode string + CommandID string + TargetUserID int64 + Amount int64 + DrawID string + RoomID string + GiftID string + PoolID string + Reason string +} + +// LuckyGiftRewardReceipt 是幸运礼物返奖入账后的稳定回执。 +type LuckyGiftRewardReceipt struct { + TransactionID string + Balance AssetBalance + Amount int64 + GrantedAtMS int64 +} + // GameCoinChangeCommand 是 game-service 对钱包发起的游戏专用金币改账命令。 type GameCoinChangeCommand struct { AppCode string diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index ec755566..63f82d53 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -23,6 +23,7 @@ type Repository interface { AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) TransferCoinFromSeller(ctx context.Context, command ledger.CoinSellerTransferCommand) (ledger.CoinSellerTransferReceipt, error) CreditTaskReward(ctx context.Context, command ledger.TaskRewardCommand) (ledger.TaskRewardReceipt, error) + CreditLuckyGiftReward(ctx context.Context, command ledger.LuckyGiftRewardCommand) (ledger.LuckyGiftRewardReceipt, error) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) GetRedPacketConfig(ctx context.Context, appCode string) (ledger.RedPacketConfig, error) UpdateRedPacketConfig(ctx context.Context, config ledger.RedPacketConfig) (ledger.RedPacketConfig, error) @@ -269,6 +270,28 @@ func (s *Service) CreditTaskReward(ctx context.Context, command ledger.TaskRewar return s.repository.CreditTaskReward(ctx, command) } +// CreditLuckyGiftReward 发放幸运礼物中奖金币;抽奖资格和金额已由 activity-service 决策,钱包只做幂等入账。 +func (s *Service) CreditLuckyGiftReward(ctx context.Context, command ledger.LuckyGiftRewardCommand) (ledger.LuckyGiftRewardReceipt, error) { + if command.CommandID == "" || command.TargetUserID <= 0 || command.Amount <= 0 || command.DrawID == "" || command.RoomID == "" || command.GiftID == "" || command.PoolID == "" { + return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.InvalidArgument, "lucky gift reward command is incomplete") + } + command.Reason = strings.TrimSpace(command.Reason) + if command.Reason == "" { + return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required") + } + if s.repository == nil { + return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + command.AppCode = appcode.Normalize(command.AppCode) + command.DrawID = strings.TrimSpace(command.DrawID) + command.RoomID = strings.TrimSpace(command.RoomID) + command.GiftID = strings.TrimSpace(command.GiftID) + command.PoolID = strings.TrimSpace(command.PoolID) + ctx = appcode.WithContext(ctx, command.AppCode) + + return s.repository.CreditLuckyGiftReward(ctx, command) +} + // ApplyGameCoinChange 是游戏平台唯一的 COIN 改账入口,方向由 op_type 控制。 func (s *Service) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) { if command.CommandID == "" || command.UserID <= 0 || command.PlatformCode == "" || command.GameID == "" || command.ProviderOrderID == "" { diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 96b31a5b..297a5ba9 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -519,6 +519,43 @@ func TestCreditTaskRewardIsIdempotent(t *testing.T) { } } +// TestCreditLuckyGiftRewardIsIdempotent 验证幸运礼物返奖只按 draw_id 派一次,并产出余额通知事实。 +func TestCreditLuckyGiftRewardIsIdempotent(t *testing.T) { + repository := mysqltest.NewRepository(t) + svc := walletservice.New(repository) + command := ledger.LuckyGiftRewardCommand{ + CommandID: "lucky_reward:lucky_draw_1", + TargetUserID: 21002, + Amount: 188, + DrawID: "lucky_draw_1", + RoomID: "room-1", + GiftID: "rose", + PoolID: "super_lucky", + Reason: "lucky_gift_reward", + } + + first, err := svc.CreditLuckyGiftReward(context.Background(), command) + if err != nil { + t.Fatalf("CreditLuckyGiftReward failed: %v", err) + } + second, err := svc.CreditLuckyGiftReward(context.Background(), command) + if err != nil { + t.Fatalf("CreditLuckyGiftReward retry failed: %v", err) + } + if first.TransactionID == "" || first.TransactionID != second.TransactionID || second.Balance.AvailableAmount != 188 { + t.Fatalf("lucky reward receipt mismatch: first=%+v second=%+v", first, second) + } + if got := repository.CountRows("wallet_transactions", "command_id = ? AND biz_type = ?", command.CommandID, "lucky_gift_reward"); got != 1 { + t.Fatalf("lucky reward should write one transaction, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ?", first.TransactionID, "WalletBalanceChanged"); got != 1 { + t.Fatalf("lucky reward should write one balance change event, got %d", got) + } + if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ?", first.TransactionID, "WalletLuckyGiftRewardCredited"); got != 1 { + t.Fatalf("lucky reward should write one reward fact event, got %d", got) + } +} + // TestTransferCoinFromSellerMovesDedicatedAssetToPlayerCoin 验证币商专用金币和玩家普通金币不会混账。 func TestTransferCoinFromSellerMovesDedicatedAssetToPlayerCoin(t *testing.T) { repository := mysqltest.NewRepository(t) diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index 9998ae59..1edd591a 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -25,6 +25,7 @@ const ( bizTypeGiftDebit = "gift_debit" bizTypeManualCredit = "manual_credit" bizTypeTaskReward = "task_reward" + bizTypeLuckyGiftReward = "lucky_gift_reward" bizTypeCoinSellerTransfer = "coin_seller_transfer" bizTypeCoinSellerStockPurchase = "coin_seller_stock_purchase" bizTypeCoinSellerCoinCompensation = "coin_seller_coin_compensation" @@ -534,6 +535,80 @@ func (r *Repository) CreditTaskReward(ctx context.Context, command ledger.TaskRe return receiptFromTaskRewardMetadata(transactionID, metadata), nil } +// CreditLuckyGiftReward 在同一事务里完成幸运礼物中奖 COIN 入账、交易分录和钱包 outbox。 +func (r *Repository) CreditLuckyGiftReward(ctx context.Context, command ledger.LuckyGiftRewardCommand) (ledger.LuckyGiftRewardReceipt, error) { + if r == nil || r.db == nil { + return ledger.LuckyGiftRewardReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + ctx = contextWithCommandApp(ctx, command.AppCode) + command.AppCode = appcode.FromContext(ctx) + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + defer func() { _ = tx.Rollback() }() + + requestHash := luckyGiftRewardRequestHash(command) + if txRow, exists, err := r.lookupTransaction(ctx, tx, command.CommandID, requestHash, bizTypeLuckyGiftReward); err != nil || exists { + if err != nil || !exists { + return ledger.LuckyGiftRewardReceipt{}, err + } + return r.receiptForLuckyGiftRewardTransaction(ctx, tx, txRow.TransactionID) + } + + nowMs := time.Now().UnixMilli() + account, err := r.lockAccount(ctx, tx, command.TargetUserID, ledger.AssetCoin, true, nowMs) + if err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + transactionID := transactionID(command.AppCode, command.CommandID) + balanceAfter := account.AvailableAmount + command.Amount + metadata := luckyGiftRewardMetadata{ + AppCode: command.AppCode, + TargetUserID: command.TargetUserID, + AssetType: ledger.AssetCoin, + Amount: command.Amount, + DrawID: command.DrawID, + RoomID: command.RoomID, + GiftID: command.GiftID, + PoolID: command.PoolID, + Reason: command.Reason, + BalanceAfter: balanceAfter, + GrantedAtMS: nowMs, + } + if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizTypeLuckyGiftReward, requestHash, command.DrawID, metadata, nowMs); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + if err := r.applyAccountDelta(ctx, tx, account, command.Amount, 0, nowMs); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + if err := r.insertEntry(ctx, tx, walletEntry{ + TransactionID: transactionID, + UserID: command.TargetUserID, + AssetType: ledger.AssetCoin, + AvailableDelta: command.Amount, + FrozenDelta: 0, + AvailableAfter: balanceAfter, + FrozenAfter: account.FrozenAmount, + RoomID: command.RoomID, + CreatedAtMS: nowMs, + }); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{ + balanceChangedEvent(transactionID, command.CommandID, command.TargetUserID, ledger.AssetCoin, command.Amount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs), + luckyGiftRewardCreditedEvent(transactionID, command.CommandID, metadata, nowMs), + }); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + if err := tx.Commit(); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + + return receiptFromLuckyGiftRewardMetadata(transactionID, metadata), nil +} + // ApplyGameCoinChange 在同一事务内完成游戏 COIN 改账、分录和 outbox。 func (r *Repository) ApplyGameCoinChange(ctx context.Context, command ledger.GameCoinChangeCommand) (ledger.GameCoinChangeReceipt, error) { if r == nil || r.db == nil { @@ -1548,6 +1623,20 @@ type taskRewardMetadata struct { GrantedAtMS int64 `json:"granted_at_ms"` } +type luckyGiftRewardMetadata struct { + AppCode string `json:"app_code"` + TargetUserID int64 `json:"target_user_id"` + AssetType string `json:"asset_type"` + Amount int64 `json:"amount"` + DrawID string `json:"draw_id"` + RoomID string `json:"room_id"` + GiftID string `json:"gift_id"` + PoolID string `json:"pool_id"` + Reason string `json:"reason"` + BalanceAfter int64 `json:"balance_after"` + GrantedAtMS int64 `json:"granted_at_ms"` +} + type gameCoinMetadata struct { AppCode string `json:"app_code"` UserID int64 `json:"user_id"` @@ -1654,6 +1743,38 @@ func receiptFromTaskRewardMetadata(transactionID string, metadata taskRewardMeta } } +func (r *Repository) receiptForLuckyGiftRewardTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.LuckyGiftRewardReceipt, error) { + var metadataJSON string + if err := tx.QueryRowContext(ctx, + `SELECT COALESCE(CAST(metadata_json AS CHAR), '{}') + FROM wallet_transactions + WHERE app_code = ? AND transaction_id = ?`, + appcode.FromContext(ctx), + transactionID, + ).Scan(&metadataJSON); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + var metadata luckyGiftRewardMetadata + if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil { + return ledger.LuckyGiftRewardReceipt{}, err + } + return receiptFromLuckyGiftRewardMetadata(transactionID, metadata), nil +} + +func receiptFromLuckyGiftRewardMetadata(transactionID string, metadata luckyGiftRewardMetadata) ledger.LuckyGiftRewardReceipt { + return ledger.LuckyGiftRewardReceipt{ + TransactionID: transactionID, + Balance: ledger.AssetBalance{ + AppCode: metadata.AppCode, + UserID: metadata.TargetUserID, + AssetType: metadata.AssetType, + AvailableAmount: metadata.BalanceAfter, + }, + Amount: metadata.Amount, + GrantedAtMS: metadata.GrantedAtMS, + } +} + func (r *Repository) receiptForGameTransaction(ctx context.Context, tx *sql.Tx, transactionID string) (ledger.GameCoinChangeReceipt, error) { var metadataJSON string if err := tx.QueryRowContext(ctx, @@ -1836,6 +1957,32 @@ func taskRewardCreditedEvent(transactionID string, commandID string, metadata ta } } +func luckyGiftRewardCreditedEvent(transactionID string, commandID string, metadata luckyGiftRewardMetadata, nowMs int64) walletOutboxEvent { + return walletOutboxEvent{ + EventID: eventID(transactionID, "WalletLuckyGiftRewardCredited", metadata.TargetUserID, ledger.AssetCoin), + EventType: "WalletLuckyGiftRewardCredited", + TransactionID: transactionID, + CommandID: commandID, + UserID: metadata.TargetUserID, + AssetType: ledger.AssetCoin, + AvailableDelta: metadata.Amount, + FrozenDelta: 0, + Payload: map[string]any{ + "transaction_id": transactionID, + "command_id": commandID, + "user_id": metadata.TargetUserID, + "draw_id": metadata.DrawID, + "room_id": metadata.RoomID, + "gift_id": metadata.GiftID, + "pool_id": metadata.PoolID, + "amount": metadata.Amount, + "reason": metadata.Reason, + "created_at_ms": nowMs, + }, + CreatedAtMS: nowMs, + } +} + func coinSellerStockCreditedEvent(transactionID string, commandID string, metadata coinSellerStockMetadata, nowMs int64) walletOutboxEvent { eventType := "WalletCoinSellerCoinCompensated" if metadata.StockType == ledger.StockTypeUSDTPurchase { @@ -1965,6 +2112,19 @@ func taskRewardRequestHash(command ledger.TaskRewardCommand) string { )) } +func luckyGiftRewardRequestHash(command ledger.LuckyGiftRewardCommand) string { + return stableHash(fmt.Sprintf("lucky_gift_reward|%s|%d|%d|%s|%s|%s|%s|%s", + appcode.Normalize(command.AppCode), + command.TargetUserID, + command.Amount, + strings.TrimSpace(command.DrawID), + strings.TrimSpace(command.RoomID), + strings.TrimSpace(command.GiftID), + strings.TrimSpace(command.PoolID), + strings.TrimSpace(command.Reason), + )) +} + func gameBizTypeAndDelta(opType string, coinAmount int64) (string, int64, error) { switch ledger.NormalizeGameOpType(opType) { case ledger.GameOpDebit: diff --git a/services/wallet-service/internal/transport/grpc/server.go b/services/wallet-service/internal/transport/grpc/server.go index f015c1f0..5b382c89 100644 --- a/services/wallet-service/internal/transport/grpc/server.go +++ b/services/wallet-service/internal/transport/grpc/server.go @@ -651,6 +651,38 @@ func (s *Server) CreditTaskReward(ctx context.Context, req *walletv1.CreditTaskR }, nil } +// CreditLuckyGiftReward 处理 activity-service 幸运礼物抽奖 outbox 的中奖金币入账。 +func (s *Server) CreditLuckyGiftReward(ctx context.Context, req *walletv1.CreditLuckyGiftRewardRequest) (*walletv1.CreditLuckyGiftRewardResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + receipt, err := s.svc.CreditLuckyGiftReward(ctx, ledger.LuckyGiftRewardCommand{ + AppCode: req.GetAppCode(), + CommandID: req.GetCommandId(), + TargetUserID: req.GetTargetUserId(), + Amount: req.GetAmount(), + DrawID: req.GetDrawId(), + RoomID: req.GetRoomId(), + GiftID: req.GetGiftId(), + PoolID: req.GetPoolId(), + Reason: req.GetReason(), + }) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + + return &walletv1.CreditLuckyGiftRewardResponse{ + TransactionId: receipt.TransactionID, + Amount: receipt.Amount, + GrantedAtMs: receipt.GrantedAtMS, + Balance: &walletv1.AssetBalance{ + AppCode: receipt.Balance.AppCode, + AssetType: receipt.Balance.AssetType, + AvailableAmount: receipt.Balance.AvailableAmount, + FrozenAmount: receipt.Balance.FrozenAmount, + Version: receipt.Balance.Version, + }, + }, nil +} + // ApplyGameCoinChange 处理 game-service 发起的游戏专用金币改账。 func (s *Server) ApplyGameCoinChange(ctx context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) { ctx = appcode.WithContext(ctx, req.GetAppCode())