幸运礼物

This commit is contained in:
zhx 2026-05-26 03:06:02 +08:00
parent a0ee5962df
commit a68d465272
34 changed files with 1881 additions and 260 deletions

View File

@ -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" +

View File

@ -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 {

View File

@ -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,
},

View File

@ -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);

View File

@ -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,

View File

@ -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 丢失、页面重进或余额不一致时,重新拉取礼物面板和钱包余额纠正状态。

View File

@ -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 <access_token>` |
| `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`,更新金币余额。

View File

@ -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 写 WalletBalanceChangednotice-service 投递私信余额通知
7. lucky gift worker 投递房间群 IM lucky_gift_drawn
8. activity-service 标记 reward_status=granted、outbox=delivered
```
`command_id` 是抽奖幂等键。重复 `ExecuteLuckyGiftDraw` 会返回同一条 `lucky_draw_records`,不会重复消耗 RTP、奖池或预算。

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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<<retryCount) * time.Second
}

View File

@ -2,9 +2,14 @@ package luckygift
import (
"context"
"encoding/json"
"testing"
"time"
"google.golang.org/grpc"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/tencentim"
domain "hyapp/services/activity-service/internal/domain/luckygift"
)
@ -38,9 +43,64 @@ func TestUpsertConfigPreservesPoolScope(t *testing.T) {
}
}
func TestProcessPendingDrawOutboxCreditsWalletAndPublishesRoomIM(t *testing.T) {
payload, _ := json.Marshal(map[string]any{
"event_type": "lucky_gift_drawn",
"event_id": "lucky_gift_drawn:lucky_draw_test",
"app_code": "lalu",
"draw_id": "lucky_draw_test",
"command_id": "cmd-gift",
"pool_id": "super_lucky",
"user_id": 42,
"sender_user_id": 42,
"target_user_id": 99,
"room_id": "room-1",
"gift_id": "rose",
"gift_count": 1,
"coin_spent": 100,
"effective_reward_coins": 300,
"multiplier_ppm": 3000000,
"stage_feedback": true,
"created_at_ms": 1760000000000,
})
repository := &fakeLuckyGiftRepository{
outbox: []domain.DrawOutbox{{
AppCode: "lalu",
OutboxID: "lucky_lucky_draw_test",
EventType: "LuckyGiftDrawn",
PayloadJSON: string(payload),
}},
}
wallet := &fakeLuckyGiftWallet{}
publisher := &fakeLuckyGiftPublisher{}
service := New(repository, WithWallet(wallet), WithRoomPublisher(publisher))
service.SetClock(func() time.Time { return time.UnixMilli(1760000001000).UTC() })
processed, err := service.ProcessPendingDrawOutbox(context.Background(), WorkerOptions{WorkerID: "worker-1"})
if err != nil {
t.Fatalf("ProcessPendingDrawOutbox failed: %v", err)
}
if processed != 1 {
t.Fatalf("processed count mismatch: %d", processed)
}
if wallet.last == nil || wallet.last.GetDrawId() != "lucky_draw_test" || wallet.last.GetTargetUserId() != 42 || wallet.last.GetAmount() != 300 {
t.Fatalf("wallet reward request mismatch: %+v", wallet.last)
}
if publisher.last.GroupID != "room-1" || publisher.last.Desc != "lucky_gift_drawn" || publisher.last.EventID != "lucky_gift_drawn:lucky_draw_test" {
t.Fatalf("room im message mismatch: %+v", publisher.last)
}
if repository.grantedDrawID != "lucky_draw_test" || repository.deliveredOutboxID != "lucky_lucky_draw_test" {
t.Fatalf("repository settlement mismatch: granted=%s delivered=%s", repository.grantedDrawID, repository.deliveredOutboxID)
}
}
type fakeLuckyGiftRepository struct {
getConfigCalls int
upserted domain.Config
outbox []domain.DrawOutbox
grantedDrawID string
deliveredOutboxID string
}
func (r *fakeLuckyGiftRepository) GetLuckyGiftConfig(context.Context, string) (domain.Config, bool, error) {
@ -72,3 +132,47 @@ func (r *fakeLuckyGiftRepository) ListLuckyGiftDraws(context.Context, domain.Dra
func (r *fakeLuckyGiftRepository) GetLuckyGiftDrawSummary(context.Context, domain.DrawQuery) (domain.DrawSummary, error) {
return domain.DrawSummary{}, nil
}
func (r *fakeLuckyGiftRepository) ClaimPendingLuckyGiftOutbox(context.Context, string, int64, time.Duration, int) ([]domain.DrawOutbox, error) {
return r.outbox, nil
}
func (r *fakeLuckyGiftRepository) MarkLuckyGiftOutboxDelivered(_ context.Context, event domain.DrawOutbox, _ int64) error {
r.deliveredOutboxID = event.OutboxID
return nil
}
func (r *fakeLuckyGiftRepository) MarkLuckyGiftOutboxRetryable(context.Context, domain.DrawOutbox, int, int64, string, int64) error {
return nil
}
func (r *fakeLuckyGiftRepository) MarkLuckyGiftOutboxFailed(context.Context, domain.DrawOutbox, int, string, int64) error {
return nil
}
func (r *fakeLuckyGiftRepository) MarkLuckyGiftDrawGranted(_ context.Context, _ string, drawID string, _ string, _ int64) error {
r.grantedDrawID = drawID
return nil
}
func (r *fakeLuckyGiftRepository) MarkLuckyGiftDrawFailed(context.Context, string, string, string, int64) error {
return nil
}
type fakeLuckyGiftWallet struct {
last *walletv1.CreditLuckyGiftRewardRequest
}
func (w *fakeLuckyGiftWallet) CreditLuckyGiftReward(_ context.Context, req *walletv1.CreditLuckyGiftRewardRequest, _ ...grpc.CallOption) (*walletv1.CreditLuckyGiftRewardResponse, error) {
w.last = req
return &walletv1.CreditLuckyGiftRewardResponse{TransactionId: "wallet_tx_lucky", Amount: req.GetAmount()}, nil
}
type fakeLuckyGiftPublisher struct {
last tencentim.CustomGroupMessage
}
func (p *fakeLuckyGiftPublisher) PublishGroupCustomMessage(_ context.Context, message tencentim.CustomGroupMessage) error {
p.last = message
return nil
}

View File

@ -556,33 +556,51 @@ func (r *Repository) applyLuckyDraw(ctx context.Context, tx *sql.Tx, input lucky
coin_spent, rule_version, experience_pool, rtp_window_index, gift_rtp_window_index,
selected_tier_id, base_reward_coins, room_atmosphere_reward_coins, activity_subsidy_coins,
effective_reward_coins, budget_sources_json, stage_feedback, high_multiplier,
candidate_tiers_json, pool_snapshot_json, rtp_snapshot_json, reward_status,
candidate_tiers_json, pool_snapshot_json, rtp_snapshot_json, reward_status, reward_transaction_id, reward_failure_reason,
paid_at_ms, created_at_ms, updated_at_ms
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
input.AppCode, input.DrawID, input.Command.CommandID, input.Command.UserID, input.Command.DeviceID,
input.Command.RoomID, input.Command.AnchorID, input.Config.GiftID, input.Command.GiftID, input.Command.CoinSpent,
input.Config.RuleVersion, input.ExperiencePool, input.GlobalWindow.WindowIndex, input.GiftWindow.WindowIndex,
input.Candidate.TierID, input.Candidate.BaseReward, input.Candidate.RoomReward, input.Candidate.ActivityReward,
input.Candidate.effectiveReward(), input.BudgetSourcesJSON, input.StageFeedback, input.Candidate.HighMultiplier,
string(candidateSnapshot), string(poolSnapshot), string(rtpSnapshot), domain.StatusPending,
string(candidateSnapshot), string(poolSnapshot), string(rtpSnapshot), domain.StatusPending, "", "",
input.Command.PaidAtMS, input.NowMS, input.NowMS,
); err != nil {
return err
}
if input.Candidate.effectiveReward() > 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")

View File

@ -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 {

View File

@ -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)

View File

@ -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)

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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(),
})
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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 == "" {

View File

@ -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)

View File

@ -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:

View File

@ -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())