From 86ae6db83f6662e2553e37c2061741bc8118f862 Mon Sep 17 00:00:00 2001 From: zhx Date: Thu, 4 Jun 2026 10:06:49 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=B8=E5=85=B3=E8=A3=85=E6=89=AE=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/proto/activity/v1/activity.pb.go | 1264 ++++++++++++----- api/proto/activity/v1/activity.proto | 50 + api/proto/activity/v1/activity_grpc.pb.go | 84 +- api/proto/room/v1/room.pb.go | 285 ++-- api/proto/room/v1/room.proto | 5 + docs/用户装扮Flutter对接文档.md | 16 +- docs/语音房客户端接口流程.md | 7 +- .../mysql/backfill_level_badge_metadata.sql | 97 ++ server/admin/cmd/server/main.go | 2 +- .../internal/modules/levelconfig/handler.go | 74 +- server/admin/internal/modules/resource/dto.go | 4 + .../modules/resource/operator_test.go | 72 +- .../internal/modules/resource/request.go | 186 ++- .../configs/config.docker.yaml | 1 + .../configs/config.tencent.example.yaml | 1 + services/activity-service/configs/config.yaml | 1 + .../mysql/initdb/001_activity_service.sql | 30 + services/activity-service/internal/app/app.go | 10 +- .../internal/config/config.go | 3 + .../internal/config/config_test.go | 3 + .../internal/domain/growth/growth.go | 39 + .../internal/service/growth/service.go | 116 +- .../internal/service/growth/service_test.go | 79 +- .../storage/mysql/growth_repository.go | 170 ++- .../internal/transport/grpc/growth_server.go | 63 + .../internal/client/growth_level_client.go | 5 + .../http/activityapi/level_handler.go | 2 + .../internal/transport/http/response_test.go | 212 ++- .../transport/http/roomapi/handler.go | 3 + .../transport/http/roomapi/room_handler.go | 262 +++- .../transport/http/roomapi/room_view.go | 27 +- .../internal/transport/http/router.go | 2 + .../http/userapi/appearance_handler.go | 200 ++- .../transport/http/userapi/handler.go | 3 + .../internal/room/service/gift.go | 140 +- .../internal/room/service/lucky_gift_test.go | 126 +- .../internal/room/service/service.go | 2 + services/user-service/internal/app/app.go | 6 +- .../integration/activityclient/client.go | 30 +- .../internal/service/auth/service.go | 24 + .../internal/service/auth/third_party.go | 22 + .../wallet-service/configs/config.docker.yaml | 20 +- .../configs/config.tencent.example.yaml | 20 +- services/wallet-service/configs/config.yaml | 18 + .../mysql/initdb/001_wallet_service.sql | 21 + services/wallet-service/internal/app/app.go | 172 ++- .../wallet-service/internal/config/config.go | 107 +- .../internal/config/config_test.go | 38 + .../internal/service/wallet/service_test.go | 61 + .../internal/storage/mysql/repository.go | 124 +- tools/grant-registration-level-badges/main.go | 158 +++ 51 files changed, 3645 insertions(+), 822 deletions(-) create mode 100644 scripts/mysql/backfill_level_badge_metadata.sql create mode 100644 services/wallet-service/internal/config/config_test.go create mode 100644 tools/grant-registration-level-badges/main.go diff --git a/api/proto/activity/v1/activity.pb.go b/api/proto/activity/v1/activity.pb.go index 38c67b6a..e4162e8c 100644 --- a/api/proto/activity/v1/activity.pb.go +++ b/api/proto/activity/v1/activity.pb.go @@ -7194,6 +7194,7 @@ type LevelTrackOverview struct { DisplayBadgeResourceId int64 `protobuf:"varint,10,opt,name=display_badge_resource_id,json=displayBadgeResourceId,proto3" json:"display_badge_resource_id,omitempty"` RewardPendingCount int64 `protobuf:"varint,11,opt,name=reward_pending_count,json=rewardPendingCount,proto3" json:"reward_pending_count,omitempty"` SortOrder int32 `protobuf:"varint,12,opt,name=sort_order,json=sortOrder,proto3" json:"sort_order,omitempty"` + DisplayBadgeSourceLevel int32 `protobuf:"varint,13,opt,name=display_badge_source_level,json=displayBadgeSourceLevel,proto3" json:"display_badge_source_level,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -7312,6 +7313,13 @@ func (x *LevelTrackOverview) GetSortOrder() int32 { return 0 } +func (x *LevelTrackOverview) GetDisplayBadgeSourceLevel() int32 { + if x != nil { + return x.DisplayBadgeSourceLevel + } + return 0 +} + type GetMyLevelOverviewRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -7544,6 +7552,272 @@ func (x *GetLevelTrackResponse) GetServerTimeMs() int64 { return 0 } +// LevelDisplayTrackProfile 是资料卡和房间展示使用的单轨道等级投影。 +type LevelDisplayTrackProfile struct { + state protoimpl.MessageState `protogen:"open.v1"` + Track string `protobuf:"bytes,1,opt,name=track,proto3" json:"track,omitempty"` + Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + TierId int64 `protobuf:"varint,3,opt,name=tier_id,json=tierId,proto3" json:"tier_id,omitempty"` + AvatarFrameResourceId int64 `protobuf:"varint,4,opt,name=avatar_frame_resource_id,json=avatarFrameResourceId,proto3" json:"avatar_frame_resource_id,omitempty"` + BadgeResourceId int64 `protobuf:"varint,5,opt,name=badge_resource_id,json=badgeResourceId,proto3" json:"badge_resource_id,omitempty"` + BadgeSourceLevel int32 `protobuf:"varint,6,opt,name=badge_source_level,json=badgeSourceLevel,proto3" json:"badge_source_level,omitempty"` + UpdatedAtMs int64 `protobuf:"varint,7,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LevelDisplayTrackProfile) Reset() { + *x = LevelDisplayTrackProfile{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LevelDisplayTrackProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LevelDisplayTrackProfile) ProtoMessage() {} + +func (x *LevelDisplayTrackProfile) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + 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 LevelDisplayTrackProfile.ProtoReflect.Descriptor instead. +func (*LevelDisplayTrackProfile) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{98} +} + +func (x *LevelDisplayTrackProfile) GetTrack() string { + if x != nil { + return x.Track + } + return "" +} + +func (x *LevelDisplayTrackProfile) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *LevelDisplayTrackProfile) GetTierId() int64 { + if x != nil { + return x.TierId + } + return 0 +} + +func (x *LevelDisplayTrackProfile) GetAvatarFrameResourceId() int64 { + if x != nil { + return x.AvatarFrameResourceId + } + return 0 +} + +func (x *LevelDisplayTrackProfile) GetBadgeResourceId() int64 { + if x != nil { + return x.BadgeResourceId + } + return 0 +} + +func (x *LevelDisplayTrackProfile) GetBadgeSourceLevel() int32 { + if x != nil { + return x.BadgeSourceLevel + } + return 0 +} + +func (x *LevelDisplayTrackProfile) GetUpdatedAtMs() int64 { + if x != nil { + return x.UpdatedAtMs + } + return 0 +} + +// UserLevelDisplayProfile 固定返回财富、游戏、魅力三条轨道,避免客户端按数组猜槽位。 +type UserLevelDisplayProfile struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Wealth *LevelDisplayTrackProfile `protobuf:"bytes,2,opt,name=wealth,proto3" json:"wealth,omitempty"` + Game *LevelDisplayTrackProfile `protobuf:"bytes,3,opt,name=game,proto3" json:"game,omitempty"` + Charm *LevelDisplayTrackProfile `protobuf:"bytes,4,opt,name=charm,proto3" json:"charm,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UserLevelDisplayProfile) Reset() { + *x = UserLevelDisplayProfile{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UserLevelDisplayProfile) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UserLevelDisplayProfile) ProtoMessage() {} + +func (x *UserLevelDisplayProfile) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + 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 UserLevelDisplayProfile.ProtoReflect.Descriptor instead. +func (*UserLevelDisplayProfile) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{99} +} + +func (x *UserLevelDisplayProfile) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *UserLevelDisplayProfile) GetWealth() *LevelDisplayTrackProfile { + if x != nil { + return x.Wealth + } + return nil +} + +func (x *UserLevelDisplayProfile) GetGame() *LevelDisplayTrackProfile { + if x != nil { + return x.Game + } + return nil +} + +func (x *UserLevelDisplayProfile) GetCharm() *LevelDisplayTrackProfile { + if x != nil { + return x.Charm + } + return nil +} + +type BatchGetUserLevelDisplayProfilesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserIds []int64 `protobuf:"varint,2,rep,packed,name=user_ids,json=userIds,proto3" json:"user_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetUserLevelDisplayProfilesRequest) Reset() { + *x = BatchGetUserLevelDisplayProfilesRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserLevelDisplayProfilesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserLevelDisplayProfilesRequest) ProtoMessage() {} + +func (x *BatchGetUserLevelDisplayProfilesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + 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 BatchGetUserLevelDisplayProfilesRequest.ProtoReflect.Descriptor instead. +func (*BatchGetUserLevelDisplayProfilesRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{100} +} + +func (x *BatchGetUserLevelDisplayProfilesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *BatchGetUserLevelDisplayProfilesRequest) GetUserIds() []int64 { + if x != nil { + return x.UserIds + } + return nil +} + +type BatchGetUserLevelDisplayProfilesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Profiles []*UserLevelDisplayProfile `protobuf:"bytes,1,rep,name=profiles,proto3" json:"profiles,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *BatchGetUserLevelDisplayProfilesResponse) Reset() { + *x = BatchGetUserLevelDisplayProfilesResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *BatchGetUserLevelDisplayProfilesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BatchGetUserLevelDisplayProfilesResponse) ProtoMessage() {} + +func (x *BatchGetUserLevelDisplayProfilesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + 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 BatchGetUserLevelDisplayProfilesResponse.ProtoReflect.Descriptor instead. +func (*BatchGetUserLevelDisplayProfilesResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{101} +} + +func (x *BatchGetUserLevelDisplayProfilesResponse) GetProfiles() []*UserLevelDisplayProfile { + if x != nil { + return x.Profiles + } + return nil +} + +func (x *BatchGetUserLevelDisplayProfilesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + // LevelRewardJob 是等级或等级段奖励的发放状态。 type LevelRewardJob struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -7566,7 +7840,7 @@ type LevelRewardJob struct { func (x *LevelRewardJob) Reset() { *x = LevelRewardJob{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + mi := &file_proto_activity_v1_activity_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7578,7 +7852,7 @@ func (x *LevelRewardJob) String() string { func (*LevelRewardJob) ProtoMessage() {} func (x *LevelRewardJob) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[98] + mi := &file_proto_activity_v1_activity_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7591,7 +7865,7 @@ func (x *LevelRewardJob) ProtoReflect() protoreflect.Message { // Deprecated: Use LevelRewardJob.ProtoReflect.Descriptor instead. func (*LevelRewardJob) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{98} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{102} } func (x *LevelRewardJob) GetRewardJobId() string { @@ -7699,7 +7973,7 @@ type ListLevelRewardsRequest struct { func (x *ListLevelRewardsRequest) Reset() { *x = ListLevelRewardsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + mi := &file_proto_activity_v1_activity_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7711,7 +7985,7 @@ func (x *ListLevelRewardsRequest) String() string { func (*ListLevelRewardsRequest) ProtoMessage() {} func (x *ListLevelRewardsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[99] + mi := &file_proto_activity_v1_activity_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7724,7 +7998,7 @@ func (x *ListLevelRewardsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelRewardsRequest.ProtoReflect.Descriptor instead. func (*ListLevelRewardsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{99} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{103} } func (x *ListLevelRewardsRequest) GetMeta() *RequestMeta { @@ -7779,7 +8053,7 @@ type ListLevelRewardsResponse struct { func (x *ListLevelRewardsResponse) Reset() { *x = ListLevelRewardsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + mi := &file_proto_activity_v1_activity_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7791,7 +8065,7 @@ func (x *ListLevelRewardsResponse) String() string { func (*ListLevelRewardsResponse) ProtoMessage() {} func (x *ListLevelRewardsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[100] + mi := &file_proto_activity_v1_activity_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7804,7 +8078,7 @@ func (x *ListLevelRewardsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelRewardsResponse.ProtoReflect.Descriptor instead. func (*ListLevelRewardsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{100} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{104} } func (x *ListLevelRewardsResponse) GetRewards() []*LevelRewardJob { @@ -7841,7 +8115,7 @@ type ConsumeLevelEventRequest struct { func (x *ConsumeLevelEventRequest) Reset() { *x = ConsumeLevelEventRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + mi := &file_proto_activity_v1_activity_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7853,7 +8127,7 @@ func (x *ConsumeLevelEventRequest) String() string { func (*ConsumeLevelEventRequest) ProtoMessage() {} func (x *ConsumeLevelEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[101] + mi := &file_proto_activity_v1_activity_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7866,7 +8140,7 @@ func (x *ConsumeLevelEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeLevelEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeLevelEventRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{101} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{105} } func (x *ConsumeLevelEventRequest) GetMeta() *RequestMeta { @@ -7960,7 +8234,7 @@ type ConsumeLevelEventResponse struct { func (x *ConsumeLevelEventResponse) Reset() { *x = ConsumeLevelEventResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[102] + mi := &file_proto_activity_v1_activity_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7972,7 +8246,7 @@ func (x *ConsumeLevelEventResponse) String() string { func (*ConsumeLevelEventResponse) ProtoMessage() {} func (x *ConsumeLevelEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[102] + mi := &file_proto_activity_v1_activity_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7985,7 +8259,7 @@ func (x *ConsumeLevelEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeLevelEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeLevelEventResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{102} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{106} } func (x *ConsumeLevelEventResponse) GetEventId() string { @@ -8030,6 +8304,186 @@ func (x *ConsumeLevelEventResponse) GetRewardJobCount() int64 { return 0 } +type RegistrationLevelBadgeGrant struct { + state protoimpl.MessageState `protogen:"open.v1"` + Track string `protobuf:"bytes,1,opt,name=track,proto3" json:"track,omitempty"` + Level int32 `protobuf:"varint,2,opt,name=level,proto3" json:"level,omitempty"` + ResourceId int64 `protobuf:"varint,3,opt,name=resource_id,json=resourceId,proto3" json:"resource_id,omitempty"` + GrantId string `protobuf:"bytes,4,opt,name=grant_id,json=grantId,proto3" json:"grant_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RegistrationLevelBadgeGrant) Reset() { + *x = RegistrationLevelBadgeGrant{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RegistrationLevelBadgeGrant) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegistrationLevelBadgeGrant) ProtoMessage() {} + +func (x *RegistrationLevelBadgeGrant) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_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 RegistrationLevelBadgeGrant.ProtoReflect.Descriptor instead. +func (*RegistrationLevelBadgeGrant) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{107} +} + +func (x *RegistrationLevelBadgeGrant) GetTrack() string { + if x != nil { + return x.Track + } + return "" +} + +func (x *RegistrationLevelBadgeGrant) GetLevel() int32 { + if x != nil { + return x.Level + } + return 0 +} + +func (x *RegistrationLevelBadgeGrant) GetResourceId() int64 { + if x != nil { + return x.ResourceId + } + return 0 +} + +func (x *RegistrationLevelBadgeGrant) GetGrantId() string { + if x != nil { + return x.GrantId + } + return "" +} + +type IssueRegistrationLevelBadgesRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` + UserId int64 `protobuf:"varint,2,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + CommandId string `protobuf:"bytes,3,opt,name=command_id,json=commandId,proto3" json:"command_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueRegistrationLevelBadgesRequest) Reset() { + *x = IssueRegistrationLevelBadgesRequest{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueRegistrationLevelBadgesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueRegistrationLevelBadgesRequest) ProtoMessage() {} + +func (x *IssueRegistrationLevelBadgesRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + 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 IssueRegistrationLevelBadgesRequest.ProtoReflect.Descriptor instead. +func (*IssueRegistrationLevelBadgesRequest) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{108} +} + +func (x *IssueRegistrationLevelBadgesRequest) GetMeta() *RequestMeta { + if x != nil { + return x.Meta + } + return nil +} + +func (x *IssueRegistrationLevelBadgesRequest) GetUserId() int64 { + if x != nil { + return x.UserId + } + return 0 +} + +func (x *IssueRegistrationLevelBadgesRequest) GetCommandId() string { + if x != nil { + return x.CommandId + } + return "" +} + +type IssueRegistrationLevelBadgesResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Grants []*RegistrationLevelBadgeGrant `protobuf:"bytes,1,rep,name=grants,proto3" json:"grants,omitempty"` + ServerTimeMs int64 `protobuf:"varint,2,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *IssueRegistrationLevelBadgesResponse) Reset() { + *x = IssueRegistrationLevelBadgesResponse{} + mi := &file_proto_activity_v1_activity_proto_msgTypes[109] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *IssueRegistrationLevelBadgesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*IssueRegistrationLevelBadgesResponse) ProtoMessage() {} + +func (x *IssueRegistrationLevelBadgesResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_activity_v1_activity_proto_msgTypes[109] + 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 IssueRegistrationLevelBadgesResponse.ProtoReflect.Descriptor instead. +func (*IssueRegistrationLevelBadgesResponse) Descriptor() ([]byte, []int) { + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{109} +} + +func (x *IssueRegistrationLevelBadgesResponse) GetGrants() []*RegistrationLevelBadgeGrant { + if x != nil { + return x.Grants + } + return nil +} + +func (x *IssueRegistrationLevelBadgesResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + type UpsertLevelTrackRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Meta *RequestMeta `protobuf:"bytes,1,opt,name=meta,proto3" json:"meta,omitempty"` @@ -8044,7 +8498,7 @@ type UpsertLevelTrackRequest struct { func (x *UpsertLevelTrackRequest) Reset() { *x = UpsertLevelTrackRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[103] + mi := &file_proto_activity_v1_activity_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8056,7 +8510,7 @@ func (x *UpsertLevelTrackRequest) String() string { func (*UpsertLevelTrackRequest) ProtoMessage() {} func (x *UpsertLevelTrackRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[103] + mi := &file_proto_activity_v1_activity_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8069,7 +8523,7 @@ func (x *UpsertLevelTrackRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTrackRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelTrackRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{103} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{110} } func (x *UpsertLevelTrackRequest) GetMeta() *RequestMeta { @@ -8124,7 +8578,7 @@ type UpsertLevelTrackResponse struct { func (x *UpsertLevelTrackResponse) Reset() { *x = UpsertLevelTrackResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[104] + mi := &file_proto_activity_v1_activity_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8136,7 +8590,7 @@ func (x *UpsertLevelTrackResponse) String() string { func (*UpsertLevelTrackResponse) ProtoMessage() {} func (x *UpsertLevelTrackResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[104] + mi := &file_proto_activity_v1_activity_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8149,7 +8603,7 @@ func (x *UpsertLevelTrackResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTrackResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelTrackResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{104} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{111} } func (x *UpsertLevelTrackResponse) GetTrack() *LevelTrack { @@ -8184,7 +8638,7 @@ type UpsertLevelRuleRequest struct { func (x *UpsertLevelRuleRequest) Reset() { *x = UpsertLevelRuleRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[105] + mi := &file_proto_activity_v1_activity_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8196,7 +8650,7 @@ func (x *UpsertLevelRuleRequest) String() string { func (*UpsertLevelRuleRequest) ProtoMessage() {} func (x *UpsertLevelRuleRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[105] + mi := &file_proto_activity_v1_activity_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8209,7 +8663,7 @@ func (x *UpsertLevelRuleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelRuleRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelRuleRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{105} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{112} } func (x *UpsertLevelRuleRequest) GetMeta() *RequestMeta { @@ -8292,7 +8746,7 @@ type UpsertLevelRuleResponse struct { func (x *UpsertLevelRuleResponse) Reset() { *x = UpsertLevelRuleResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[106] + mi := &file_proto_activity_v1_activity_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8304,7 +8758,7 @@ func (x *UpsertLevelRuleResponse) String() string { func (*UpsertLevelRuleResponse) ProtoMessage() {} func (x *UpsertLevelRuleResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[106] + mi := &file_proto_activity_v1_activity_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8317,7 +8771,7 @@ func (x *UpsertLevelRuleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelRuleResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelRuleResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{106} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{113} } func (x *UpsertLevelRuleResponse) GetRule() *LevelRule { @@ -8354,7 +8808,7 @@ type UpsertLevelTierRequest struct { func (x *UpsertLevelTierRequest) Reset() { *x = UpsertLevelTierRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + mi := &file_proto_activity_v1_activity_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8366,7 +8820,7 @@ func (x *UpsertLevelTierRequest) String() string { func (*UpsertLevelTierRequest) ProtoMessage() {} func (x *UpsertLevelTierRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[107] + mi := &file_proto_activity_v1_activity_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8379,7 +8833,7 @@ func (x *UpsertLevelTierRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTierRequest.ProtoReflect.Descriptor instead. func (*UpsertLevelTierRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{107} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{114} } func (x *UpsertLevelTierRequest) GetMeta() *RequestMeta { @@ -8476,7 +8930,7 @@ type UpsertLevelTierResponse struct { func (x *UpsertLevelTierResponse) Reset() { *x = UpsertLevelTierResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + mi := &file_proto_activity_v1_activity_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8488,7 +8942,7 @@ func (x *UpsertLevelTierResponse) String() string { func (*UpsertLevelTierResponse) ProtoMessage() {} func (x *UpsertLevelTierResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[108] + mi := &file_proto_activity_v1_activity_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8501,7 +8955,7 @@ func (x *UpsertLevelTierResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLevelTierResponse.ProtoReflect.Descriptor instead. func (*UpsertLevelTierResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{108} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{115} } func (x *UpsertLevelTierResponse) GetTier() *LevelTier { @@ -8530,7 +8984,7 @@ type ListLevelConfigRequest struct { func (x *ListLevelConfigRequest) Reset() { *x = ListLevelConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[109] + mi := &file_proto_activity_v1_activity_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8542,7 +8996,7 @@ func (x *ListLevelConfigRequest) String() string { func (*ListLevelConfigRequest) ProtoMessage() {} func (x *ListLevelConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[109] + mi := &file_proto_activity_v1_activity_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8555,7 +9009,7 @@ func (x *ListLevelConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelConfigRequest.ProtoReflect.Descriptor instead. func (*ListLevelConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{109} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{116} } func (x *ListLevelConfigRequest) GetMeta() *RequestMeta { @@ -8591,7 +9045,7 @@ type ListLevelConfigResponse struct { func (x *ListLevelConfigResponse) Reset() { *x = ListLevelConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[110] + mi := &file_proto_activity_v1_activity_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8603,7 +9057,7 @@ func (x *ListLevelConfigResponse) String() string { func (*ListLevelConfigResponse) ProtoMessage() {} func (x *ListLevelConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[110] + mi := &file_proto_activity_v1_activity_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8616,7 +9070,7 @@ func (x *ListLevelConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLevelConfigResponse.ProtoReflect.Descriptor instead. func (*ListLevelConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{110} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{117} } func (x *ListLevelConfigResponse) GetTracks() []*LevelTrack { @@ -8662,7 +9116,7 @@ type AchievementCondition struct { func (x *AchievementCondition) Reset() { *x = AchievementCondition{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[111] + mi := &file_proto_activity_v1_activity_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8674,7 +9128,7 @@ func (x *AchievementCondition) String() string { func (*AchievementCondition) ProtoMessage() {} func (x *AchievementCondition) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[111] + mi := &file_proto_activity_v1_activity_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8687,7 +9141,7 @@ func (x *AchievementCondition) ProtoReflect() protoreflect.Message { // Deprecated: Use AchievementCondition.ProtoReflect.Descriptor instead. func (*AchievementCondition) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{111} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{118} } func (x *AchievementCondition) GetConditionId() string { @@ -8768,7 +9222,7 @@ type AchievementDefinition struct { func (x *AchievementDefinition) Reset() { *x = AchievementDefinition{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[112] + mi := &file_proto_activity_v1_activity_proto_msgTypes[119] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8780,7 +9234,7 @@ func (x *AchievementDefinition) String() string { func (*AchievementDefinition) ProtoMessage() {} func (x *AchievementDefinition) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[112] + mi := &file_proto_activity_v1_activity_proto_msgTypes[119] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8793,7 +9247,7 @@ func (x *AchievementDefinition) ProtoReflect() protoreflect.Message { // Deprecated: Use AchievementDefinition.ProtoReflect.Descriptor instead. func (*AchievementDefinition) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{112} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{119} } func (x *AchievementDefinition) GetAchievementId() string { @@ -8958,7 +9412,7 @@ type UserAchievement struct { func (x *UserAchievement) Reset() { *x = UserAchievement{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[113] + mi := &file_proto_activity_v1_activity_proto_msgTypes[120] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8970,7 +9424,7 @@ func (x *UserAchievement) String() string { func (*UserAchievement) ProtoMessage() {} func (x *UserAchievement) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[113] + mi := &file_proto_activity_v1_activity_proto_msgTypes[120] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8983,7 +9437,7 @@ func (x *UserAchievement) ProtoReflect() protoreflect.Message { // Deprecated: Use UserAchievement.ProtoReflect.Descriptor instead. func (*UserAchievement) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{113} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{120} } func (x *UserAchievement) GetDefinition() *AchievementDefinition { @@ -9049,7 +9503,7 @@ type ListAchievementsRequest struct { func (x *ListAchievementsRequest) Reset() { *x = ListAchievementsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[114] + mi := &file_proto_activity_v1_activity_proto_msgTypes[121] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9061,7 +9515,7 @@ func (x *ListAchievementsRequest) String() string { func (*ListAchievementsRequest) ProtoMessage() {} func (x *ListAchievementsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[114] + mi := &file_proto_activity_v1_activity_proto_msgTypes[121] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9074,7 +9528,7 @@ func (x *ListAchievementsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAchievementsRequest.ProtoReflect.Descriptor instead. func (*ListAchievementsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{114} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{121} } func (x *ListAchievementsRequest) GetMeta() *RequestMeta { @@ -9130,7 +9584,7 @@ type ListAchievementsResponse struct { func (x *ListAchievementsResponse) Reset() { *x = ListAchievementsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[115] + mi := &file_proto_activity_v1_activity_proto_msgTypes[122] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9142,7 +9596,7 @@ func (x *ListAchievementsResponse) String() string { func (*ListAchievementsResponse) ProtoMessage() {} func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[115] + mi := &file_proto_activity_v1_activity_proto_msgTypes[122] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9155,7 +9609,7 @@ func (x *ListAchievementsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListAchievementsResponse.ProtoReflect.Descriptor instead. func (*ListAchievementsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{115} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{122} } func (x *ListAchievementsResponse) GetAchievements() []*UserAchievement { @@ -9196,7 +9650,7 @@ type ConsumeAchievementEventRequest struct { func (x *ConsumeAchievementEventRequest) Reset() { *x = ConsumeAchievementEventRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[116] + mi := &file_proto_activity_v1_activity_proto_msgTypes[123] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9208,7 +9662,7 @@ func (x *ConsumeAchievementEventRequest) String() string { func (*ConsumeAchievementEventRequest) ProtoMessage() {} func (x *ConsumeAchievementEventRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[116] + mi := &file_proto_activity_v1_activity_proto_msgTypes[123] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9221,7 +9675,7 @@ func (x *ConsumeAchievementEventRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeAchievementEventRequest.ProtoReflect.Descriptor instead. func (*ConsumeAchievementEventRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{116} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{123} } func (x *ConsumeAchievementEventRequest) GetMeta() *RequestMeta { @@ -9300,7 +9754,7 @@ type ConsumeAchievementEventResponse struct { func (x *ConsumeAchievementEventResponse) Reset() { *x = ConsumeAchievementEventResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[117] + mi := &file_proto_activity_v1_activity_proto_msgTypes[124] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9312,7 +9766,7 @@ func (x *ConsumeAchievementEventResponse) String() string { func (*ConsumeAchievementEventResponse) ProtoMessage() {} func (x *ConsumeAchievementEventResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[117] + mi := &file_proto_activity_v1_activity_proto_msgTypes[124] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9325,7 +9779,7 @@ func (x *ConsumeAchievementEventResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ConsumeAchievementEventResponse.ProtoReflect.Descriptor instead. func (*ConsumeAchievementEventResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{117} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{124} } func (x *ConsumeAchievementEventResponse) GetEventId() string { @@ -9380,7 +9834,7 @@ type BadgeDisplayItem struct { func (x *BadgeDisplayItem) Reset() { *x = BadgeDisplayItem{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[118] + mi := &file_proto_activity_v1_activity_proto_msgTypes[125] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9392,7 +9846,7 @@ func (x *BadgeDisplayItem) String() string { func (*BadgeDisplayItem) ProtoMessage() {} func (x *BadgeDisplayItem) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[118] + mi := &file_proto_activity_v1_activity_proto_msgTypes[125] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9405,7 +9859,7 @@ func (x *BadgeDisplayItem) ProtoReflect() protoreflect.Message { // Deprecated: Use BadgeDisplayItem.ProtoReflect.Descriptor instead. func (*BadgeDisplayItem) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{118} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{125} } func (x *BadgeDisplayItem) GetSlot() string { @@ -9481,7 +9935,7 @@ type ListMyBadgesRequest struct { func (x *ListMyBadgesRequest) Reset() { *x = ListMyBadgesRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[119] + mi := &file_proto_activity_v1_activity_proto_msgTypes[126] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9493,7 +9947,7 @@ func (x *ListMyBadgesRequest) String() string { func (*ListMyBadgesRequest) ProtoMessage() {} func (x *ListMyBadgesRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[119] + mi := &file_proto_activity_v1_activity_proto_msgTypes[126] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9506,7 +9960,7 @@ func (x *ListMyBadgesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMyBadgesRequest.ProtoReflect.Descriptor instead. func (*ListMyBadgesRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{119} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{126} } func (x *ListMyBadgesRequest) GetMeta() *RequestMeta { @@ -9535,7 +9989,7 @@ type ListMyBadgesResponse struct { func (x *ListMyBadgesResponse) Reset() { *x = ListMyBadgesResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[120] + mi := &file_proto_activity_v1_activity_proto_msgTypes[127] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9547,7 +10001,7 @@ func (x *ListMyBadgesResponse) String() string { func (*ListMyBadgesResponse) ProtoMessage() {} func (x *ListMyBadgesResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[120] + mi := &file_proto_activity_v1_activity_proto_msgTypes[127] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9560,7 +10014,7 @@ func (x *ListMyBadgesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListMyBadgesResponse.ProtoReflect.Descriptor instead. func (*ListMyBadgesResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{120} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{127} } func (x *ListMyBadgesResponse) GetStripBadges() []*BadgeDisplayItem { @@ -9604,7 +10058,7 @@ type SetBadgeDisplayRequest struct { func (x *SetBadgeDisplayRequest) Reset() { *x = SetBadgeDisplayRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[121] + mi := &file_proto_activity_v1_activity_proto_msgTypes[128] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9616,7 +10070,7 @@ func (x *SetBadgeDisplayRequest) String() string { func (*SetBadgeDisplayRequest) ProtoMessage() {} func (x *SetBadgeDisplayRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[121] + mi := &file_proto_activity_v1_activity_proto_msgTypes[128] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9629,7 +10083,7 @@ func (x *SetBadgeDisplayRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBadgeDisplayRequest.ProtoReflect.Descriptor instead. func (*SetBadgeDisplayRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{121} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{128} } func (x *SetBadgeDisplayRequest) GetMeta() *RequestMeta { @@ -9676,7 +10130,7 @@ type SetBadgeDisplayResponse struct { func (x *SetBadgeDisplayResponse) Reset() { *x = SetBadgeDisplayResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[122] + mi := &file_proto_activity_v1_activity_proto_msgTypes[129] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9688,7 +10142,7 @@ func (x *SetBadgeDisplayResponse) String() string { func (*SetBadgeDisplayResponse) ProtoMessage() {} func (x *SetBadgeDisplayResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[122] + mi := &file_proto_activity_v1_activity_proto_msgTypes[129] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9701,7 +10155,7 @@ func (x *SetBadgeDisplayResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetBadgeDisplayResponse.ProtoReflect.Descriptor instead. func (*SetBadgeDisplayResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{122} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{129} } func (x *SetBadgeDisplayResponse) GetProfile() *ListMyBadgesResponse { @@ -9737,7 +10191,7 @@ type UpsertAchievementDefinitionRequest struct { func (x *UpsertAchievementDefinitionRequest) Reset() { *x = UpsertAchievementDefinitionRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[123] + mi := &file_proto_activity_v1_activity_proto_msgTypes[130] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9749,7 +10203,7 @@ func (x *UpsertAchievementDefinitionRequest) String() string { func (*UpsertAchievementDefinitionRequest) ProtoMessage() {} func (x *UpsertAchievementDefinitionRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[123] + mi := &file_proto_activity_v1_activity_proto_msgTypes[130] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9762,7 +10216,7 @@ func (x *UpsertAchievementDefinitionRequest) ProtoReflect() protoreflect.Message // Deprecated: Use UpsertAchievementDefinitionRequest.ProtoReflect.Descriptor instead. func (*UpsertAchievementDefinitionRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{123} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{130} } func (x *UpsertAchievementDefinitionRequest) GetMeta() *RequestMeta { @@ -9901,7 +10355,7 @@ type UpsertAchievementDefinitionResponse struct { func (x *UpsertAchievementDefinitionResponse) Reset() { *x = UpsertAchievementDefinitionResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[124] + mi := &file_proto_activity_v1_activity_proto_msgTypes[131] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9913,7 +10367,7 @@ func (x *UpsertAchievementDefinitionResponse) String() string { func (*UpsertAchievementDefinitionResponse) ProtoMessage() {} func (x *UpsertAchievementDefinitionResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[124] + mi := &file_proto_activity_v1_activity_proto_msgTypes[131] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9926,7 +10380,7 @@ func (x *UpsertAchievementDefinitionResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use UpsertAchievementDefinitionResponse.ProtoReflect.Descriptor instead. func (*UpsertAchievementDefinitionResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{124} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{131} } func (x *UpsertAchievementDefinitionResponse) GetAchievement() *AchievementDefinition { @@ -9965,7 +10419,7 @@ type LuckyGiftMeta struct { func (x *LuckyGiftMeta) Reset() { *x = LuckyGiftMeta{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[125] + mi := &file_proto_activity_v1_activity_proto_msgTypes[132] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9977,7 +10431,7 @@ func (x *LuckyGiftMeta) String() string { func (*LuckyGiftMeta) ProtoMessage() {} func (x *LuckyGiftMeta) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[125] + mi := &file_proto_activity_v1_activity_proto_msgTypes[132] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9990,7 +10444,7 @@ func (x *LuckyGiftMeta) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftMeta.ProtoReflect.Descriptor instead. func (*LuckyGiftMeta) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{125} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{132} } func (x *LuckyGiftMeta) GetMeta() *RequestMeta { @@ -10099,7 +10553,7 @@ type LuckyGiftTier struct { func (x *LuckyGiftTier) Reset() { *x = LuckyGiftTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[126] + mi := &file_proto_activity_v1_activity_proto_msgTypes[133] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10111,7 +10565,7 @@ func (x *LuckyGiftTier) String() string { func (*LuckyGiftTier) ProtoMessage() {} func (x *LuckyGiftTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[126] + mi := &file_proto_activity_v1_activity_proto_msgTypes[133] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10124,7 +10578,7 @@ func (x *LuckyGiftTier) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftTier.ProtoReflect.Descriptor instead. func (*LuckyGiftTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{126} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{133} } func (x *LuckyGiftTier) GetPool() string { @@ -10224,7 +10678,7 @@ type LuckyGiftConfig struct { func (x *LuckyGiftConfig) Reset() { *x = LuckyGiftConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[127] + mi := &file_proto_activity_v1_activity_proto_msgTypes[134] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10236,7 +10690,7 @@ func (x *LuckyGiftConfig) String() string { func (*LuckyGiftConfig) ProtoMessage() {} func (x *LuckyGiftConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[127] + mi := &file_proto_activity_v1_activity_proto_msgTypes[134] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10249,7 +10703,7 @@ func (x *LuckyGiftConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftConfig.ProtoReflect.Descriptor instead. func (*LuckyGiftConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{127} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{134} } func (x *LuckyGiftConfig) GetAppCode() string { @@ -10548,7 +11002,7 @@ type LuckyGiftRuleTier struct { func (x *LuckyGiftRuleTier) Reset() { *x = LuckyGiftRuleTier{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[128] + mi := &file_proto_activity_v1_activity_proto_msgTypes[135] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10560,7 +11014,7 @@ func (x *LuckyGiftRuleTier) String() string { func (*LuckyGiftRuleTier) ProtoMessage() {} func (x *LuckyGiftRuleTier) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[128] + mi := &file_proto_activity_v1_activity_proto_msgTypes[135] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10573,7 +11027,7 @@ func (x *LuckyGiftRuleTier) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftRuleTier.ProtoReflect.Descriptor instead. func (*LuckyGiftRuleTier) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{128} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{135} } func (x *LuckyGiftRuleTier) GetStage() string { @@ -10642,7 +11096,7 @@ type LuckyGiftRuleStage struct { func (x *LuckyGiftRuleStage) Reset() { *x = LuckyGiftRuleStage{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[129] + mi := &file_proto_activity_v1_activity_proto_msgTypes[136] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10654,7 +11108,7 @@ func (x *LuckyGiftRuleStage) String() string { func (*LuckyGiftRuleStage) ProtoMessage() {} func (x *LuckyGiftRuleStage) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[129] + mi := &file_proto_activity_v1_activity_proto_msgTypes[136] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10667,7 +11121,7 @@ func (x *LuckyGiftRuleStage) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftRuleStage.ProtoReflect.Descriptor instead. func (*LuckyGiftRuleStage) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{129} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{136} } func (x *LuckyGiftRuleStage) GetStage() string { @@ -10713,7 +11167,7 @@ type LuckyGiftRuleConfig struct { func (x *LuckyGiftRuleConfig) Reset() { *x = LuckyGiftRuleConfig{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[130] + mi := &file_proto_activity_v1_activity_proto_msgTypes[137] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10725,7 +11179,7 @@ func (x *LuckyGiftRuleConfig) String() string { func (*LuckyGiftRuleConfig) ProtoMessage() {} func (x *LuckyGiftRuleConfig) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[130] + mi := &file_proto_activity_v1_activity_proto_msgTypes[137] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10738,7 +11192,7 @@ func (x *LuckyGiftRuleConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftRuleConfig.ProtoReflect.Descriptor instead. func (*LuckyGiftRuleConfig) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{130} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{137} } func (x *LuckyGiftRuleConfig) GetAppCode() string { @@ -10901,7 +11355,7 @@ type CheckLuckyGiftRequest struct { func (x *CheckLuckyGiftRequest) Reset() { *x = CheckLuckyGiftRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[131] + mi := &file_proto_activity_v1_activity_proto_msgTypes[138] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10913,7 +11367,7 @@ func (x *CheckLuckyGiftRequest) String() string { func (*CheckLuckyGiftRequest) ProtoMessage() {} func (x *CheckLuckyGiftRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[131] + mi := &file_proto_activity_v1_activity_proto_msgTypes[138] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -10926,7 +11380,7 @@ func (x *CheckLuckyGiftRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckLuckyGiftRequest.ProtoReflect.Descriptor instead. func (*CheckLuckyGiftRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{131} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{138} } func (x *CheckLuckyGiftRequest) GetMeta() *RequestMeta { @@ -10980,7 +11434,7 @@ type CheckLuckyGiftResponse struct { func (x *CheckLuckyGiftResponse) Reset() { *x = CheckLuckyGiftResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[132] + mi := &file_proto_activity_v1_activity_proto_msgTypes[139] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -10992,7 +11446,7 @@ func (x *CheckLuckyGiftResponse) String() string { func (*CheckLuckyGiftResponse) ProtoMessage() {} func (x *CheckLuckyGiftResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[132] + mi := &file_proto_activity_v1_activity_proto_msgTypes[139] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11005,7 +11459,7 @@ func (x *CheckLuckyGiftResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckLuckyGiftResponse.ProtoReflect.Descriptor instead. func (*CheckLuckyGiftResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{132} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{139} } func (x *CheckLuckyGiftResponse) GetEnabled() bool { @@ -11095,7 +11549,7 @@ type LuckyGiftDrawResult struct { func (x *LuckyGiftDrawResult) Reset() { *x = LuckyGiftDrawResult{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[133] + mi := &file_proto_activity_v1_activity_proto_msgTypes[140] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11107,7 +11561,7 @@ func (x *LuckyGiftDrawResult) String() string { func (*LuckyGiftDrawResult) ProtoMessage() {} func (x *LuckyGiftDrawResult) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[133] + mi := &file_proto_activity_v1_activity_proto_msgTypes[140] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11120,7 +11574,7 @@ func (x *LuckyGiftDrawResult) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftDrawResult.ProtoReflect.Descriptor instead. func (*LuckyGiftDrawResult) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{133} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{140} } func (x *LuckyGiftDrawResult) GetDrawId() string { @@ -11293,7 +11747,7 @@ type ExecuteLuckyGiftDrawRequest struct { func (x *ExecuteLuckyGiftDrawRequest) Reset() { *x = ExecuteLuckyGiftDrawRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[134] + mi := &file_proto_activity_v1_activity_proto_msgTypes[141] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11305,7 +11759,7 @@ func (x *ExecuteLuckyGiftDrawRequest) String() string { func (*ExecuteLuckyGiftDrawRequest) ProtoMessage() {} func (x *ExecuteLuckyGiftDrawRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[134] + mi := &file_proto_activity_v1_activity_proto_msgTypes[141] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11318,7 +11772,7 @@ func (x *ExecuteLuckyGiftDrawRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteLuckyGiftDrawRequest.ProtoReflect.Descriptor instead. func (*ExecuteLuckyGiftDrawRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{134} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{141} } func (x *ExecuteLuckyGiftDrawRequest) GetLuckyGift() *LuckyGiftMeta { @@ -11337,7 +11791,7 @@ type ExecuteLuckyGiftDrawResponse struct { func (x *ExecuteLuckyGiftDrawResponse) Reset() { *x = ExecuteLuckyGiftDrawResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[135] + mi := &file_proto_activity_v1_activity_proto_msgTypes[142] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11349,7 +11803,7 @@ func (x *ExecuteLuckyGiftDrawResponse) String() string { func (*ExecuteLuckyGiftDrawResponse) ProtoMessage() {} func (x *ExecuteLuckyGiftDrawResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[135] + mi := &file_proto_activity_v1_activity_proto_msgTypes[142] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11362,7 +11816,7 @@ func (x *ExecuteLuckyGiftDrawResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ExecuteLuckyGiftDrawResponse.ProtoReflect.Descriptor instead. func (*ExecuteLuckyGiftDrawResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{135} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{142} } func (x *ExecuteLuckyGiftDrawResponse) GetResult() *LuckyGiftDrawResult { @@ -11383,7 +11837,7 @@ type GetLuckyGiftConfigRequest struct { func (x *GetLuckyGiftConfigRequest) Reset() { *x = GetLuckyGiftConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[136] + mi := &file_proto_activity_v1_activity_proto_msgTypes[143] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11395,7 +11849,7 @@ func (x *GetLuckyGiftConfigRequest) String() string { func (*GetLuckyGiftConfigRequest) ProtoMessage() {} func (x *GetLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[136] + mi := &file_proto_activity_v1_activity_proto_msgTypes[143] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11408,7 +11862,7 @@ func (x *GetLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftConfigRequest.ProtoReflect.Descriptor instead. func (*GetLuckyGiftConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{136} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{143} } func (x *GetLuckyGiftConfigRequest) GetMeta() *RequestMeta { @@ -11441,7 +11895,7 @@ type GetLuckyGiftConfigResponse struct { func (x *GetLuckyGiftConfigResponse) Reset() { *x = GetLuckyGiftConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[137] + mi := &file_proto_activity_v1_activity_proto_msgTypes[144] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11453,7 +11907,7 @@ func (x *GetLuckyGiftConfigResponse) String() string { func (*GetLuckyGiftConfigResponse) ProtoMessage() {} func (x *GetLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[137] + mi := &file_proto_activity_v1_activity_proto_msgTypes[144] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11466,7 +11920,7 @@ func (x *GetLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftConfigResponse.ProtoReflect.Descriptor instead. func (*GetLuckyGiftConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{137} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{144} } func (x *GetLuckyGiftConfigResponse) GetConfig() *LuckyGiftRuleConfig { @@ -11487,7 +11941,7 @@ type UpsertLuckyGiftConfigRequest struct { func (x *UpsertLuckyGiftConfigRequest) Reset() { *x = UpsertLuckyGiftConfigRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[138] + mi := &file_proto_activity_v1_activity_proto_msgTypes[145] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11499,7 +11953,7 @@ func (x *UpsertLuckyGiftConfigRequest) String() string { func (*UpsertLuckyGiftConfigRequest) ProtoMessage() {} func (x *UpsertLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[138] + mi := &file_proto_activity_v1_activity_proto_msgTypes[145] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11512,7 +11966,7 @@ func (x *UpsertLuckyGiftConfigRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLuckyGiftConfigRequest.ProtoReflect.Descriptor instead. func (*UpsertLuckyGiftConfigRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{138} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{145} } func (x *UpsertLuckyGiftConfigRequest) GetMeta() *RequestMeta { @@ -11545,7 +11999,7 @@ type UpsertLuckyGiftConfigResponse struct { func (x *UpsertLuckyGiftConfigResponse) Reset() { *x = UpsertLuckyGiftConfigResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[139] + mi := &file_proto_activity_v1_activity_proto_msgTypes[146] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11557,7 +12011,7 @@ func (x *UpsertLuckyGiftConfigResponse) String() string { func (*UpsertLuckyGiftConfigResponse) ProtoMessage() {} func (x *UpsertLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[139] + mi := &file_proto_activity_v1_activity_proto_msgTypes[146] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11570,7 +12024,7 @@ func (x *UpsertLuckyGiftConfigResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UpsertLuckyGiftConfigResponse.ProtoReflect.Descriptor instead. func (*UpsertLuckyGiftConfigResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{139} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{146} } func (x *UpsertLuckyGiftConfigResponse) GetConfig() *LuckyGiftRuleConfig { @@ -11589,7 +12043,7 @@ type ListLuckyGiftConfigsRequest struct { func (x *ListLuckyGiftConfigsRequest) Reset() { *x = ListLuckyGiftConfigsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[140] + mi := &file_proto_activity_v1_activity_proto_msgTypes[147] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11601,7 +12055,7 @@ func (x *ListLuckyGiftConfigsRequest) String() string { func (*ListLuckyGiftConfigsRequest) ProtoMessage() {} func (x *ListLuckyGiftConfigsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[140] + mi := &file_proto_activity_v1_activity_proto_msgTypes[147] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11614,7 +12068,7 @@ func (x *ListLuckyGiftConfigsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftConfigsRequest.ProtoReflect.Descriptor instead. func (*ListLuckyGiftConfigsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{140} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{147} } func (x *ListLuckyGiftConfigsRequest) GetMeta() *RequestMeta { @@ -11633,7 +12087,7 @@ type ListLuckyGiftConfigsResponse struct { func (x *ListLuckyGiftConfigsResponse) Reset() { *x = ListLuckyGiftConfigsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[141] + mi := &file_proto_activity_v1_activity_proto_msgTypes[148] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11645,7 +12099,7 @@ func (x *ListLuckyGiftConfigsResponse) String() string { func (*ListLuckyGiftConfigsResponse) ProtoMessage() {} func (x *ListLuckyGiftConfigsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[141] + mi := &file_proto_activity_v1_activity_proto_msgTypes[148] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11658,7 +12112,7 @@ func (x *ListLuckyGiftConfigsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftConfigsResponse.ProtoReflect.Descriptor instead. func (*ListLuckyGiftConfigsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{141} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{148} } func (x *ListLuckyGiftConfigsResponse) GetConfigs() []*LuckyGiftRuleConfig { @@ -11684,7 +12138,7 @@ type ListLuckyGiftDrawsRequest struct { func (x *ListLuckyGiftDrawsRequest) Reset() { *x = ListLuckyGiftDrawsRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[142] + mi := &file_proto_activity_v1_activity_proto_msgTypes[149] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11696,7 +12150,7 @@ func (x *ListLuckyGiftDrawsRequest) String() string { func (*ListLuckyGiftDrawsRequest) ProtoMessage() {} func (x *ListLuckyGiftDrawsRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[142] + mi := &file_proto_activity_v1_activity_proto_msgTypes[149] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11709,7 +12163,7 @@ func (x *ListLuckyGiftDrawsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftDrawsRequest.ProtoReflect.Descriptor instead. func (*ListLuckyGiftDrawsRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{142} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{149} } func (x *ListLuckyGiftDrawsRequest) GetMeta() *RequestMeta { @@ -11778,7 +12232,7 @@ type ListLuckyGiftDrawsResponse struct { func (x *ListLuckyGiftDrawsResponse) Reset() { *x = ListLuckyGiftDrawsResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[143] + mi := &file_proto_activity_v1_activity_proto_msgTypes[150] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11790,7 +12244,7 @@ func (x *ListLuckyGiftDrawsResponse) String() string { func (*ListLuckyGiftDrawsResponse) ProtoMessage() {} func (x *ListLuckyGiftDrawsResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[143] + mi := &file_proto_activity_v1_activity_proto_msgTypes[150] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11803,7 +12257,7 @@ func (x *ListLuckyGiftDrawsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListLuckyGiftDrawsResponse.ProtoReflect.Descriptor instead. func (*ListLuckyGiftDrawsResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{143} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{150} } func (x *ListLuckyGiftDrawsResponse) GetDraws() []*LuckyGiftDrawResult { @@ -11841,7 +12295,7 @@ type LuckyGiftDrawSummary struct { func (x *LuckyGiftDrawSummary) Reset() { *x = LuckyGiftDrawSummary{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[144] + mi := &file_proto_activity_v1_activity_proto_msgTypes[151] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11853,7 +12307,7 @@ func (x *LuckyGiftDrawSummary) String() string { func (*LuckyGiftDrawSummary) ProtoMessage() {} func (x *LuckyGiftDrawSummary) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[144] + mi := &file_proto_activity_v1_activity_proto_msgTypes[151] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11866,7 +12320,7 @@ func (x *LuckyGiftDrawSummary) ProtoReflect() protoreflect.Message { // Deprecated: Use LuckyGiftDrawSummary.ProtoReflect.Descriptor instead. func (*LuckyGiftDrawSummary) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{144} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{151} } func (x *LuckyGiftDrawSummary) GetPoolId() string { @@ -11974,7 +12428,7 @@ type GetLuckyGiftDrawSummaryRequest struct { func (x *GetLuckyGiftDrawSummaryRequest) Reset() { *x = GetLuckyGiftDrawSummaryRequest{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[145] + mi := &file_proto_activity_v1_activity_proto_msgTypes[152] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -11986,7 +12440,7 @@ func (x *GetLuckyGiftDrawSummaryRequest) String() string { func (*GetLuckyGiftDrawSummaryRequest) ProtoMessage() {} func (x *GetLuckyGiftDrawSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[145] + mi := &file_proto_activity_v1_activity_proto_msgTypes[152] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -11999,7 +12453,7 @@ func (x *GetLuckyGiftDrawSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftDrawSummaryRequest.ProtoReflect.Descriptor instead. func (*GetLuckyGiftDrawSummaryRequest) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{145} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{152} } func (x *GetLuckyGiftDrawSummaryRequest) GetMeta() *RequestMeta { @@ -12053,7 +12507,7 @@ type GetLuckyGiftDrawSummaryResponse struct { func (x *GetLuckyGiftDrawSummaryResponse) Reset() { *x = GetLuckyGiftDrawSummaryResponse{} - mi := &file_proto_activity_v1_activity_proto_msgTypes[146] + mi := &file_proto_activity_v1_activity_proto_msgTypes[153] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -12065,7 +12519,7 @@ func (x *GetLuckyGiftDrawSummaryResponse) String() string { func (*GetLuckyGiftDrawSummaryResponse) ProtoMessage() {} func (x *GetLuckyGiftDrawSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_proto_activity_v1_activity_proto_msgTypes[146] + mi := &file_proto_activity_v1_activity_proto_msgTypes[153] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -12078,7 +12532,7 @@ func (x *GetLuckyGiftDrawSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLuckyGiftDrawSummaryResponse.ProtoReflect.Descriptor instead. func (*GetLuckyGiftDrawSummaryResponse) Descriptor() ([]byte, []int) { - return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{146} + return file_proto_activity_v1_activity_proto_rawDescGZIP(), []int{153} } func (x *GetLuckyGiftDrawSummaryResponse) GetSummary() *LuckyGiftDrawSummary { @@ -12732,7 +13186,7 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\x13created_by_admin_id\x18\v \x01(\x03R\x10createdByAdminId\x12-\n" + "\x13updated_by_admin_id\x18\f \x01(\x03R\x10updatedByAdminId\x12\"\n" + "\rcreated_at_ms\x18\r \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\x0e \x01(\x03R\vupdatedAtMs\"\xfd\x03\n" + + "\rupdated_at_ms\x18\x0e \x01(\x03R\vupdatedAtMs\"\xba\x04\n" + "\x12LevelTrackOverview\x12\x14\n" + "\x05track\x18\x01 \x01(\tR\x05track\x12\x12\n" + "\x04name\x18\x02 \x01(\tR\x04name\x12\x14\n" + @@ -12749,7 +13203,8 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + " \x01(\x03R\x16displayBadgeResourceId\x120\n" + "\x14reward_pending_count\x18\v \x01(\x03R\x12rewardPendingCount\x12\x1d\n" + "\n" + - "sort_order\x18\f \x01(\x05R\tsortOrder\"h\n" + + "sort_order\x18\f \x01(\x05R\tsortOrder\x12;\n" + + "\x1adisplay_badge_source_level\x18\r \x01(\x05R\x17displayBadgeSourceLevel\"h\n" + "\x19GetMyLevelOverviewRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\"\x81\x01\n" + @@ -12764,7 +13219,26 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\boverview\x18\x01 \x01(\v2%.hyapp.activity.v1.LevelTrackOverviewR\boverview\x122\n" + "\x05rules\x18\x02 \x03(\v2\x1c.hyapp.activity.v1.LevelRuleR\x05rules\x122\n" + "\x05tiers\x18\x03 \x03(\v2\x1c.hyapp.activity.v1.LevelTierR\x05tiers\x12$\n" + - "\x0eserver_time_ms\x18\x04 \x01(\x03R\fserverTimeMs\"\xe7\x03\n" + + "\x0eserver_time_ms\x18\x04 \x01(\x03R\fserverTimeMs\"\x96\x02\n" + + "\x18LevelDisplayTrackProfile\x12\x14\n" + + "\x05track\x18\x01 \x01(\tR\x05track\x12\x14\n" + + "\x05level\x18\x02 \x01(\x05R\x05level\x12\x17\n" + + "\atier_id\x18\x03 \x01(\x03R\x06tierId\x127\n" + + "\x18avatar_frame_resource_id\x18\x04 \x01(\x03R\x15avatarFrameResourceId\x12*\n" + + "\x11badge_resource_id\x18\x05 \x01(\x03R\x0fbadgeResourceId\x12,\n" + + "\x12badge_source_level\x18\x06 \x01(\x05R\x10badgeSourceLevel\x12\"\n" + + "\rupdated_at_ms\x18\a \x01(\x03R\vupdatedAtMs\"\xfb\x01\n" + + "\x17UserLevelDisplayProfile\x12\x17\n" + + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12C\n" + + "\x06wealth\x18\x02 \x01(\v2+.hyapp.activity.v1.LevelDisplayTrackProfileR\x06wealth\x12?\n" + + "\x04game\x18\x03 \x01(\v2+.hyapp.activity.v1.LevelDisplayTrackProfileR\x04game\x12A\n" + + "\x05charm\x18\x04 \x01(\v2+.hyapp.activity.v1.LevelDisplayTrackProfileR\x05charm\"x\n" + + "'BatchGetUserLevelDisplayProfilesRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x19\n" + + "\buser_ids\x18\x02 \x03(\x03R\auserIds\"\x98\x01\n" + + "(BatchGetUserLevelDisplayProfilesResponse\x12F\n" + + "\bprofiles\x18\x01 \x03(\v2*.hyapp.activity.v1.UserLevelDisplayProfileR\bprofiles\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xe7\x03\n" + "\x0eLevelRewardJob\x12\"\n" + "\rreward_job_id\x18\x01 \x01(\tR\vrewardJobId\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x14\n" + @@ -12811,7 +13285,21 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\rlevel_changed\x18\x03 \x01(\bR\flevelChanged\x12%\n" + "\x0eprevious_level\x18\x04 \x01(\x05R\rpreviousLevel\x12\x1b\n" + "\tnew_level\x18\x05 \x01(\x05R\bnewLevel\x12(\n" + - "\x10reward_job_count\x18\x06 \x01(\x03R\x0erewardJobCount\"\xde\x01\n" + + "\x10reward_job_count\x18\x06 \x01(\x03R\x0erewardJobCount\"\x85\x01\n" + + "\x1bRegistrationLevelBadgeGrant\x12\x14\n" + + "\x05track\x18\x01 \x01(\tR\x05track\x12\x14\n" + + "\x05level\x18\x02 \x01(\x05R\x05level\x12\x1f\n" + + "\vresource_id\x18\x03 \x01(\x03R\n" + + "resourceId\x12\x19\n" + + "\bgrant_id\x18\x04 \x01(\tR\agrantId\"\x91\x01\n" + + "#IssueRegistrationLevelBadgesRequest\x122\n" + + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x17\n" + + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x1d\n" + + "\n" + + "command_id\x18\x03 \x01(\tR\tcommandId\"\x94\x01\n" + + "$IssueRegistrationLevelBadgesResponse\x12F\n" + + "\x06grants\x18\x01 \x03(\v2..hyapp.activity.v1.RegistrationLevelBadgeGrantR\x06grants\x12$\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xde\x01\n" + "\x17UpsertLevelTrackRequest\x122\n" + "\x04meta\x18\x01 \x01(\v2\x1e.hyapp.activity.v1.RequestMetaR\x04meta\x12\x14\n" + "\x05track\x18\x02 \x01(\tR\x05track\x12\x12\n" + @@ -13220,12 +13708,14 @@ const file_proto_activity_v1_activity_proto_rawDesc = "" + "\vTaskService\x12b\n" + "\rListUserTasks\x12'.hyapp.activity.v1.ListUserTasksRequest\x1a(.hyapp.activity.v1.ListUserTasksResponse\x12h\n" + "\x0fClaimTaskReward\x12).hyapp.activity.v1.ClaimTaskRewardRequest\x1a*.hyapp.activity.v1.ClaimTaskRewardResponse\x12k\n" + - "\x10ConsumeTaskEvent\x12*.hyapp.activity.v1.ConsumeTaskEventRequest\x1a+.hyapp.activity.v1.ConsumeTaskEventResponse2\xc8\x03\n" + + "\x10ConsumeTaskEvent\x12*.hyapp.activity.v1.ConsumeTaskEventRequest\x1a+.hyapp.activity.v1.ConsumeTaskEventResponse2\xf8\x05\n" + "\x12GrowthLevelService\x12q\n" + "\x12GetMyLevelOverview\x12,.hyapp.activity.v1.GetMyLevelOverviewRequest\x1a-.hyapp.activity.v1.GetMyLevelOverviewResponse\x12b\n" + - "\rGetLevelTrack\x12'.hyapp.activity.v1.GetLevelTrackRequest\x1a(.hyapp.activity.v1.GetLevelTrackResponse\x12k\n" + + "\rGetLevelTrack\x12'.hyapp.activity.v1.GetLevelTrackRequest\x1a(.hyapp.activity.v1.GetLevelTrackResponse\x12\x9b\x01\n" + + " BatchGetUserLevelDisplayProfiles\x12:.hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest\x1a;.hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse\x12k\n" + "\x10ListLevelRewards\x12*.hyapp.activity.v1.ListLevelRewardsRequest\x1a+.hyapp.activity.v1.ListLevelRewardsResponse\x12n\n" + - "\x11ConsumeLevelEvent\x12+.hyapp.activity.v1.ConsumeLevelEventRequest\x1a,.hyapp.activity.v1.ConsumeLevelEventResponse2\xcf\x03\n" + + "\x11ConsumeLevelEvent\x12+.hyapp.activity.v1.ConsumeLevelEventRequest\x1a,.hyapp.activity.v1.ConsumeLevelEventResponse\x12\x8f\x01\n" + + "\x1cIssueRegistrationLevelBadges\x126.hyapp.activity.v1.IssueRegistrationLevelBadgesRequest\x1a7.hyapp.activity.v1.IssueRegistrationLevelBadgesResponse2\xcf\x03\n" + "\x12AchievementService\x12k\n" + "\x10ListAchievements\x12*.hyapp.activity.v1.ListAchievementsRequest\x1a+.hyapp.activity.v1.ListAchievementsResponse\x12\x80\x01\n" + "\x17ConsumeAchievementEvent\x121.hyapp.activity.v1.ConsumeAchievementEventRequest\x1a2.hyapp.activity.v1.ConsumeAchievementEventResponse\x12_\n" + @@ -13294,7 +13784,7 @@ func file_proto_activity_v1_activity_proto_rawDescGZIP() []byte { return file_proto_activity_v1_activity_proto_rawDescData } -var file_proto_activity_v1_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 147) +var file_proto_activity_v1_activity_proto_msgTypes = make([]protoimpl.MessageInfo, 154) var file_proto_activity_v1_activity_proto_goTypes = []any{ (*RequestMeta)(nil), // 0: hyapp.activity.v1.RequestMeta (*PingActivityRequest)(nil), // 1: hyapp.activity.v1.PingActivityRequest @@ -13394,56 +13884,63 @@ var file_proto_activity_v1_activity_proto_goTypes = []any{ (*GetMyLevelOverviewResponse)(nil), // 95: hyapp.activity.v1.GetMyLevelOverviewResponse (*GetLevelTrackRequest)(nil), // 96: hyapp.activity.v1.GetLevelTrackRequest (*GetLevelTrackResponse)(nil), // 97: hyapp.activity.v1.GetLevelTrackResponse - (*LevelRewardJob)(nil), // 98: hyapp.activity.v1.LevelRewardJob - (*ListLevelRewardsRequest)(nil), // 99: hyapp.activity.v1.ListLevelRewardsRequest - (*ListLevelRewardsResponse)(nil), // 100: hyapp.activity.v1.ListLevelRewardsResponse - (*ConsumeLevelEventRequest)(nil), // 101: hyapp.activity.v1.ConsumeLevelEventRequest - (*ConsumeLevelEventResponse)(nil), // 102: hyapp.activity.v1.ConsumeLevelEventResponse - (*UpsertLevelTrackRequest)(nil), // 103: hyapp.activity.v1.UpsertLevelTrackRequest - (*UpsertLevelTrackResponse)(nil), // 104: hyapp.activity.v1.UpsertLevelTrackResponse - (*UpsertLevelRuleRequest)(nil), // 105: hyapp.activity.v1.UpsertLevelRuleRequest - (*UpsertLevelRuleResponse)(nil), // 106: hyapp.activity.v1.UpsertLevelRuleResponse - (*UpsertLevelTierRequest)(nil), // 107: hyapp.activity.v1.UpsertLevelTierRequest - (*UpsertLevelTierResponse)(nil), // 108: hyapp.activity.v1.UpsertLevelTierResponse - (*ListLevelConfigRequest)(nil), // 109: hyapp.activity.v1.ListLevelConfigRequest - (*ListLevelConfigResponse)(nil), // 110: hyapp.activity.v1.ListLevelConfigResponse - (*AchievementCondition)(nil), // 111: hyapp.activity.v1.AchievementCondition - (*AchievementDefinition)(nil), // 112: hyapp.activity.v1.AchievementDefinition - (*UserAchievement)(nil), // 113: hyapp.activity.v1.UserAchievement - (*ListAchievementsRequest)(nil), // 114: hyapp.activity.v1.ListAchievementsRequest - (*ListAchievementsResponse)(nil), // 115: hyapp.activity.v1.ListAchievementsResponse - (*ConsumeAchievementEventRequest)(nil), // 116: hyapp.activity.v1.ConsumeAchievementEventRequest - (*ConsumeAchievementEventResponse)(nil), // 117: hyapp.activity.v1.ConsumeAchievementEventResponse - (*BadgeDisplayItem)(nil), // 118: hyapp.activity.v1.BadgeDisplayItem - (*ListMyBadgesRequest)(nil), // 119: hyapp.activity.v1.ListMyBadgesRequest - (*ListMyBadgesResponse)(nil), // 120: hyapp.activity.v1.ListMyBadgesResponse - (*SetBadgeDisplayRequest)(nil), // 121: hyapp.activity.v1.SetBadgeDisplayRequest - (*SetBadgeDisplayResponse)(nil), // 122: hyapp.activity.v1.SetBadgeDisplayResponse - (*UpsertAchievementDefinitionRequest)(nil), // 123: hyapp.activity.v1.UpsertAchievementDefinitionRequest - (*UpsertAchievementDefinitionResponse)(nil), // 124: hyapp.activity.v1.UpsertAchievementDefinitionResponse - (*LuckyGiftMeta)(nil), // 125: hyapp.activity.v1.LuckyGiftMeta - (*LuckyGiftTier)(nil), // 126: hyapp.activity.v1.LuckyGiftTier - (*LuckyGiftConfig)(nil), // 127: hyapp.activity.v1.LuckyGiftConfig - (*LuckyGiftRuleTier)(nil), // 128: hyapp.activity.v1.LuckyGiftRuleTier - (*LuckyGiftRuleStage)(nil), // 129: hyapp.activity.v1.LuckyGiftRuleStage - (*LuckyGiftRuleConfig)(nil), // 130: hyapp.activity.v1.LuckyGiftRuleConfig - (*CheckLuckyGiftRequest)(nil), // 131: hyapp.activity.v1.CheckLuckyGiftRequest - (*CheckLuckyGiftResponse)(nil), // 132: hyapp.activity.v1.CheckLuckyGiftResponse - (*LuckyGiftDrawResult)(nil), // 133: hyapp.activity.v1.LuckyGiftDrawResult - (*ExecuteLuckyGiftDrawRequest)(nil), // 134: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest - (*ExecuteLuckyGiftDrawResponse)(nil), // 135: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse - (*GetLuckyGiftConfigRequest)(nil), // 136: hyapp.activity.v1.GetLuckyGiftConfigRequest - (*GetLuckyGiftConfigResponse)(nil), // 137: hyapp.activity.v1.GetLuckyGiftConfigResponse - (*UpsertLuckyGiftConfigRequest)(nil), // 138: hyapp.activity.v1.UpsertLuckyGiftConfigRequest - (*UpsertLuckyGiftConfigResponse)(nil), // 139: hyapp.activity.v1.UpsertLuckyGiftConfigResponse - (*ListLuckyGiftConfigsRequest)(nil), // 140: hyapp.activity.v1.ListLuckyGiftConfigsRequest - (*ListLuckyGiftConfigsResponse)(nil), // 141: hyapp.activity.v1.ListLuckyGiftConfigsResponse - (*ListLuckyGiftDrawsRequest)(nil), // 142: hyapp.activity.v1.ListLuckyGiftDrawsRequest - (*ListLuckyGiftDrawsResponse)(nil), // 143: hyapp.activity.v1.ListLuckyGiftDrawsResponse - (*LuckyGiftDrawSummary)(nil), // 144: hyapp.activity.v1.LuckyGiftDrawSummary - (*GetLuckyGiftDrawSummaryRequest)(nil), // 145: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest - (*GetLuckyGiftDrawSummaryResponse)(nil), // 146: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse - (*v1.EventEnvelope)(nil), // 147: hyapp.events.room.v1.EventEnvelope + (*LevelDisplayTrackProfile)(nil), // 98: hyapp.activity.v1.LevelDisplayTrackProfile + (*UserLevelDisplayProfile)(nil), // 99: hyapp.activity.v1.UserLevelDisplayProfile + (*BatchGetUserLevelDisplayProfilesRequest)(nil), // 100: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest + (*BatchGetUserLevelDisplayProfilesResponse)(nil), // 101: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse + (*LevelRewardJob)(nil), // 102: hyapp.activity.v1.LevelRewardJob + (*ListLevelRewardsRequest)(nil), // 103: hyapp.activity.v1.ListLevelRewardsRequest + (*ListLevelRewardsResponse)(nil), // 104: hyapp.activity.v1.ListLevelRewardsResponse + (*ConsumeLevelEventRequest)(nil), // 105: hyapp.activity.v1.ConsumeLevelEventRequest + (*ConsumeLevelEventResponse)(nil), // 106: hyapp.activity.v1.ConsumeLevelEventResponse + (*RegistrationLevelBadgeGrant)(nil), // 107: hyapp.activity.v1.RegistrationLevelBadgeGrant + (*IssueRegistrationLevelBadgesRequest)(nil), // 108: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + (*IssueRegistrationLevelBadgesResponse)(nil), // 109: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + (*UpsertLevelTrackRequest)(nil), // 110: hyapp.activity.v1.UpsertLevelTrackRequest + (*UpsertLevelTrackResponse)(nil), // 111: hyapp.activity.v1.UpsertLevelTrackResponse + (*UpsertLevelRuleRequest)(nil), // 112: hyapp.activity.v1.UpsertLevelRuleRequest + (*UpsertLevelRuleResponse)(nil), // 113: hyapp.activity.v1.UpsertLevelRuleResponse + (*UpsertLevelTierRequest)(nil), // 114: hyapp.activity.v1.UpsertLevelTierRequest + (*UpsertLevelTierResponse)(nil), // 115: hyapp.activity.v1.UpsertLevelTierResponse + (*ListLevelConfigRequest)(nil), // 116: hyapp.activity.v1.ListLevelConfigRequest + (*ListLevelConfigResponse)(nil), // 117: hyapp.activity.v1.ListLevelConfigResponse + (*AchievementCondition)(nil), // 118: hyapp.activity.v1.AchievementCondition + (*AchievementDefinition)(nil), // 119: hyapp.activity.v1.AchievementDefinition + (*UserAchievement)(nil), // 120: hyapp.activity.v1.UserAchievement + (*ListAchievementsRequest)(nil), // 121: hyapp.activity.v1.ListAchievementsRequest + (*ListAchievementsResponse)(nil), // 122: hyapp.activity.v1.ListAchievementsResponse + (*ConsumeAchievementEventRequest)(nil), // 123: hyapp.activity.v1.ConsumeAchievementEventRequest + (*ConsumeAchievementEventResponse)(nil), // 124: hyapp.activity.v1.ConsumeAchievementEventResponse + (*BadgeDisplayItem)(nil), // 125: hyapp.activity.v1.BadgeDisplayItem + (*ListMyBadgesRequest)(nil), // 126: hyapp.activity.v1.ListMyBadgesRequest + (*ListMyBadgesResponse)(nil), // 127: hyapp.activity.v1.ListMyBadgesResponse + (*SetBadgeDisplayRequest)(nil), // 128: hyapp.activity.v1.SetBadgeDisplayRequest + (*SetBadgeDisplayResponse)(nil), // 129: hyapp.activity.v1.SetBadgeDisplayResponse + (*UpsertAchievementDefinitionRequest)(nil), // 130: hyapp.activity.v1.UpsertAchievementDefinitionRequest + (*UpsertAchievementDefinitionResponse)(nil), // 131: hyapp.activity.v1.UpsertAchievementDefinitionResponse + (*LuckyGiftMeta)(nil), // 132: hyapp.activity.v1.LuckyGiftMeta + (*LuckyGiftTier)(nil), // 133: hyapp.activity.v1.LuckyGiftTier + (*LuckyGiftConfig)(nil), // 134: hyapp.activity.v1.LuckyGiftConfig + (*LuckyGiftRuleTier)(nil), // 135: hyapp.activity.v1.LuckyGiftRuleTier + (*LuckyGiftRuleStage)(nil), // 136: hyapp.activity.v1.LuckyGiftRuleStage + (*LuckyGiftRuleConfig)(nil), // 137: hyapp.activity.v1.LuckyGiftRuleConfig + (*CheckLuckyGiftRequest)(nil), // 138: hyapp.activity.v1.CheckLuckyGiftRequest + (*CheckLuckyGiftResponse)(nil), // 139: hyapp.activity.v1.CheckLuckyGiftResponse + (*LuckyGiftDrawResult)(nil), // 140: hyapp.activity.v1.LuckyGiftDrawResult + (*ExecuteLuckyGiftDrawRequest)(nil), // 141: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest + (*ExecuteLuckyGiftDrawResponse)(nil), // 142: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse + (*GetLuckyGiftConfigRequest)(nil), // 143: hyapp.activity.v1.GetLuckyGiftConfigRequest + (*GetLuckyGiftConfigResponse)(nil), // 144: hyapp.activity.v1.GetLuckyGiftConfigResponse + (*UpsertLuckyGiftConfigRequest)(nil), // 145: hyapp.activity.v1.UpsertLuckyGiftConfigRequest + (*UpsertLuckyGiftConfigResponse)(nil), // 146: hyapp.activity.v1.UpsertLuckyGiftConfigResponse + (*ListLuckyGiftConfigsRequest)(nil), // 147: hyapp.activity.v1.ListLuckyGiftConfigsRequest + (*ListLuckyGiftConfigsResponse)(nil), // 148: hyapp.activity.v1.ListLuckyGiftConfigsResponse + (*ListLuckyGiftDrawsRequest)(nil), // 149: hyapp.activity.v1.ListLuckyGiftDrawsRequest + (*ListLuckyGiftDrawsResponse)(nil), // 150: hyapp.activity.v1.ListLuckyGiftDrawsResponse + (*LuckyGiftDrawSummary)(nil), // 151: hyapp.activity.v1.LuckyGiftDrawSummary + (*GetLuckyGiftDrawSummaryRequest)(nil), // 152: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest + (*GetLuckyGiftDrawSummaryResponse)(nil), // 153: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse + (*v1.EventEnvelope)(nil), // 154: hyapp.events.room.v1.EventEnvelope } var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 0, // 0: hyapp.activity.v1.PingActivityRequest.meta:type_name -> hyapp.activity.v1.RequestMeta @@ -13468,7 +13965,7 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 0, // 19: hyapp.activity.v1.PublishGlobalBroadcastRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 20: hyapp.activity.v1.RemoveRegionBroadcastMemberRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 0, // 21: hyapp.activity.v1.ConsumeRoomEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 147, // 22: hyapp.activity.v1.ConsumeRoomEventRequest.envelope:type_name -> hyapp.events.room.v1.EventEnvelope + 154, // 22: hyapp.activity.v1.ConsumeRoomEventRequest.envelope:type_name -> hyapp.events.room.v1.EventEnvelope 0, // 23: hyapp.activity.v1.ListTaskDefinitionsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta 42, // 24: hyapp.activity.v1.ListTaskDefinitionsResponse.tasks:type_name -> hyapp.activity.v1.TaskDefinition 0, // 25: hyapp.activity.v1.UpsertTaskDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta @@ -13517,177 +14014,188 @@ var file_proto_activity_v1_activity_proto_depIdxs = []int32{ 93, // 68: hyapp.activity.v1.GetLevelTrackResponse.overview:type_name -> hyapp.activity.v1.LevelTrackOverview 91, // 69: hyapp.activity.v1.GetLevelTrackResponse.rules:type_name -> hyapp.activity.v1.LevelRule 92, // 70: hyapp.activity.v1.GetLevelTrackResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 0, // 71: hyapp.activity.v1.ListLevelRewardsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 98, // 72: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob - 0, // 73: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 74: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 90, // 75: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack - 0, // 76: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 91, // 77: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule - 0, // 78: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 92, // 79: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier - 0, // 80: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 90, // 81: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack - 91, // 82: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule - 92, // 83: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier - 111, // 84: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 112, // 85: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 86: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 113, // 87: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement - 0, // 88: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 0, // 89: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 118, // 90: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 118, // 91: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 118, // 92: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem - 0, // 93: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 118, // 94: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem - 120, // 95: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse - 0, // 96: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 111, // 97: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition - 112, // 98: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition - 0, // 99: hyapp.activity.v1.LuckyGiftMeta.meta:type_name -> hyapp.activity.v1.RequestMeta - 126, // 100: hyapp.activity.v1.LuckyGiftConfig.tiers:type_name -> hyapp.activity.v1.LuckyGiftTier - 128, // 101: hyapp.activity.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.activity.v1.LuckyGiftRuleTier - 129, // 102: hyapp.activity.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.activity.v1.LuckyGiftRuleStage - 0, // 103: hyapp.activity.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 125, // 104: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.activity.v1.LuckyGiftMeta - 133, // 105: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.activity.v1.LuckyGiftDrawResult - 0, // 106: hyapp.activity.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 130, // 107: hyapp.activity.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 108: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 130, // 109: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 130, // 110: hyapp.activity.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 111: hyapp.activity.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 130, // 112: hyapp.activity.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig - 0, // 113: hyapp.activity.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 133, // 114: hyapp.activity.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.activity.v1.LuckyGiftDrawResult - 0, // 115: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta - 144, // 116: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.LuckyGiftDrawSummary - 1, // 117: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest - 3, // 118: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest - 6, // 119: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest - 9, // 120: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest - 11, // 121: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest - 13, // 122: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest - 15, // 123: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest - 17, // 124: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest - 19, // 125: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest - 21, // 126: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 127: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 21, // 128: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 25, // 129: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest - 27, // 130: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest - 29, // 131: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest - 94, // 132: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest - 96, // 133: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest - 99, // 134: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest - 101, // 135: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest - 114, // 136: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest - 116, // 137: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest - 119, // 138: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest - 121, // 139: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest - 131, // 140: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.activity.v1.CheckLuckyGiftRequest - 134, // 141: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawRequest - 32, // 142: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest - 34, // 143: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest - 35, // 144: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest - 37, // 145: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest - 21, // 146: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest - 40, // 147: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest - 43, // 148: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest - 45, // 149: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest - 47, // 150: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest - 52, // 151: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest - 54, // 152: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest - 56, // 153: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest - 58, // 154: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest - 60, // 155: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest - 66, // 156: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest - 68, // 157: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest - 70, // 158: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest - 72, // 159: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest - 74, // 160: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest - 79, // 161: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest - 81, // 162: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest - 83, // 163: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest - 85, // 164: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest - 88, // 165: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest - 109, // 166: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest - 103, // 167: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest - 105, // 168: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest - 107, // 169: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest - 114, // 170: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest - 123, // 171: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest - 136, // 172: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.activity.v1.GetLuckyGiftConfigRequest - 138, // 173: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.activity.v1.UpsertLuckyGiftConfigRequest - 140, // 174: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.activity.v1.ListLuckyGiftConfigsRequest - 142, // 175: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.activity.v1.ListLuckyGiftDrawsRequest - 145, // 176: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest - 2, // 177: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse - 4, // 178: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse - 7, // 179: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse - 10, // 180: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse - 12, // 181: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse - 14, // 182: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse - 16, // 183: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse - 18, // 184: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse - 20, // 185: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse - 22, // 186: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 187: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 22, // 188: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse - 26, // 189: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse - 28, // 190: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse - 30, // 191: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse - 95, // 192: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse - 97, // 193: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse - 100, // 194: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse - 102, // 195: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse - 115, // 196: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse - 117, // 197: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse - 120, // 198: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse - 122, // 199: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse - 132, // 200: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.activity.v1.CheckLuckyGiftResponse - 135, // 201: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawResponse - 33, // 202: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse - 36, // 203: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 36, // 204: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse - 38, // 205: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse - 39, // 206: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse - 41, // 207: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse - 44, // 208: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse - 46, // 209: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse - 48, // 210: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse - 53, // 211: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse - 55, // 212: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse - 57, // 213: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse - 59, // 214: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse - 61, // 215: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse - 67, // 216: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse - 69, // 217: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse - 71, // 218: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse - 73, // 219: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse - 75, // 220: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse - 80, // 221: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse - 82, // 222: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse - 84, // 223: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse - 86, // 224: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse - 89, // 225: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse - 110, // 226: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse - 104, // 227: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse - 106, // 228: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse - 108, // 229: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse - 115, // 230: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse - 124, // 231: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse - 137, // 232: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.activity.v1.GetLuckyGiftConfigResponse - 139, // 233: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.activity.v1.UpsertLuckyGiftConfigResponse - 141, // 234: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.activity.v1.ListLuckyGiftConfigsResponse - 143, // 235: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.activity.v1.ListLuckyGiftDrawsResponse - 146, // 236: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse - 177, // [177:237] is the sub-list for method output_type - 117, // [117:177] is the sub-list for method input_type - 117, // [117:117] is the sub-list for extension type_name - 117, // [117:117] is the sub-list for extension extendee - 0, // [0:117] is the sub-list for field type_name + 98, // 71: hyapp.activity.v1.UserLevelDisplayProfile.wealth:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 98, // 72: hyapp.activity.v1.UserLevelDisplayProfile.game:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 98, // 73: hyapp.activity.v1.UserLevelDisplayProfile.charm:type_name -> hyapp.activity.v1.LevelDisplayTrackProfile + 0, // 74: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 99, // 75: hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse.profiles:type_name -> hyapp.activity.v1.UserLevelDisplayProfile + 0, // 76: hyapp.activity.v1.ListLevelRewardsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 102, // 77: hyapp.activity.v1.ListLevelRewardsResponse.rewards:type_name -> hyapp.activity.v1.LevelRewardJob + 0, // 78: hyapp.activity.v1.ConsumeLevelEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 79: hyapp.activity.v1.IssueRegistrationLevelBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 107, // 80: hyapp.activity.v1.IssueRegistrationLevelBadgesResponse.grants:type_name -> hyapp.activity.v1.RegistrationLevelBadgeGrant + 0, // 81: hyapp.activity.v1.UpsertLevelTrackRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 90, // 82: hyapp.activity.v1.UpsertLevelTrackResponse.track:type_name -> hyapp.activity.v1.LevelTrack + 0, // 83: hyapp.activity.v1.UpsertLevelRuleRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 91, // 84: hyapp.activity.v1.UpsertLevelRuleResponse.rule:type_name -> hyapp.activity.v1.LevelRule + 0, // 85: hyapp.activity.v1.UpsertLevelTierRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 92, // 86: hyapp.activity.v1.UpsertLevelTierResponse.tier:type_name -> hyapp.activity.v1.LevelTier + 0, // 87: hyapp.activity.v1.ListLevelConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 90, // 88: hyapp.activity.v1.ListLevelConfigResponse.tracks:type_name -> hyapp.activity.v1.LevelTrack + 91, // 89: hyapp.activity.v1.ListLevelConfigResponse.rules:type_name -> hyapp.activity.v1.LevelRule + 92, // 90: hyapp.activity.v1.ListLevelConfigResponse.tiers:type_name -> hyapp.activity.v1.LevelTier + 118, // 91: hyapp.activity.v1.AchievementDefinition.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 119, // 92: hyapp.activity.v1.UserAchievement.definition:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 93: hyapp.activity.v1.ListAchievementsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 120, // 94: hyapp.activity.v1.ListAchievementsResponse.achievements:type_name -> hyapp.activity.v1.UserAchievement + 0, // 95: hyapp.activity.v1.ConsumeAchievementEventRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 0, // 96: hyapp.activity.v1.ListMyBadgesRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 125, // 97: hyapp.activity.v1.ListMyBadgesResponse.strip_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 125, // 98: hyapp.activity.v1.ListMyBadgesResponse.profile_tile_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 125, // 99: hyapp.activity.v1.ListMyBadgesResponse.honor_badges:type_name -> hyapp.activity.v1.BadgeDisplayItem + 0, // 100: hyapp.activity.v1.SetBadgeDisplayRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 125, // 101: hyapp.activity.v1.SetBadgeDisplayRequest.items:type_name -> hyapp.activity.v1.BadgeDisplayItem + 127, // 102: hyapp.activity.v1.SetBadgeDisplayResponse.profile:type_name -> hyapp.activity.v1.ListMyBadgesResponse + 0, // 103: hyapp.activity.v1.UpsertAchievementDefinitionRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 118, // 104: hyapp.activity.v1.UpsertAchievementDefinitionRequest.conditions:type_name -> hyapp.activity.v1.AchievementCondition + 119, // 105: hyapp.activity.v1.UpsertAchievementDefinitionResponse.achievement:type_name -> hyapp.activity.v1.AchievementDefinition + 0, // 106: hyapp.activity.v1.LuckyGiftMeta.meta:type_name -> hyapp.activity.v1.RequestMeta + 133, // 107: hyapp.activity.v1.LuckyGiftConfig.tiers:type_name -> hyapp.activity.v1.LuckyGiftTier + 135, // 108: hyapp.activity.v1.LuckyGiftRuleStage.tiers:type_name -> hyapp.activity.v1.LuckyGiftRuleTier + 136, // 109: hyapp.activity.v1.LuckyGiftRuleConfig.stages:type_name -> hyapp.activity.v1.LuckyGiftRuleStage + 0, // 110: hyapp.activity.v1.CheckLuckyGiftRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 132, // 111: hyapp.activity.v1.ExecuteLuckyGiftDrawRequest.lucky_gift:type_name -> hyapp.activity.v1.LuckyGiftMeta + 140, // 112: hyapp.activity.v1.ExecuteLuckyGiftDrawResponse.result:type_name -> hyapp.activity.v1.LuckyGiftDrawResult + 0, // 113: hyapp.activity.v1.GetLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 137, // 114: hyapp.activity.v1.GetLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 115: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 137, // 116: hyapp.activity.v1.UpsertLuckyGiftConfigRequest.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 137, // 117: hyapp.activity.v1.UpsertLuckyGiftConfigResponse.config:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 118: hyapp.activity.v1.ListLuckyGiftConfigsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 137, // 119: hyapp.activity.v1.ListLuckyGiftConfigsResponse.configs:type_name -> hyapp.activity.v1.LuckyGiftRuleConfig + 0, // 120: hyapp.activity.v1.ListLuckyGiftDrawsRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 140, // 121: hyapp.activity.v1.ListLuckyGiftDrawsResponse.draws:type_name -> hyapp.activity.v1.LuckyGiftDrawResult + 0, // 122: hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest.meta:type_name -> hyapp.activity.v1.RequestMeta + 151, // 123: hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse.summary:type_name -> hyapp.activity.v1.LuckyGiftDrawSummary + 1, // 124: hyapp.activity.v1.ActivityService.PingActivity:input_type -> hyapp.activity.v1.PingActivityRequest + 3, // 125: hyapp.activity.v1.ActivityService.GetActivityStatus:input_type -> hyapp.activity.v1.GetActivityStatusRequest + 6, // 126: hyapp.activity.v1.MessageInboxService.ListMessageTabs:input_type -> hyapp.activity.v1.ListMessageTabsRequest + 9, // 127: hyapp.activity.v1.MessageInboxService.ListInboxMessages:input_type -> hyapp.activity.v1.ListInboxMessagesRequest + 11, // 128: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:input_type -> hyapp.activity.v1.MarkInboxMessageReadRequest + 13, // 129: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:input_type -> hyapp.activity.v1.MarkInboxSectionReadRequest + 15, // 130: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:input_type -> hyapp.activity.v1.DeleteInboxMessageRequest + 17, // 131: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:input_type -> hyapp.activity.v1.CreateInboxMessageRequest + 19, // 132: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:input_type -> hyapp.activity.v1.CreateFanoutJobRequest + 21, // 133: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 134: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 21, // 135: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 25, // 136: hyapp.activity.v1.TaskService.ListUserTasks:input_type -> hyapp.activity.v1.ListUserTasksRequest + 27, // 137: hyapp.activity.v1.TaskService.ClaimTaskReward:input_type -> hyapp.activity.v1.ClaimTaskRewardRequest + 29, // 138: hyapp.activity.v1.TaskService.ConsumeTaskEvent:input_type -> hyapp.activity.v1.ConsumeTaskEventRequest + 94, // 139: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:input_type -> hyapp.activity.v1.GetMyLevelOverviewRequest + 96, // 140: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:input_type -> hyapp.activity.v1.GetLevelTrackRequest + 100, // 141: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:input_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesRequest + 103, // 142: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:input_type -> hyapp.activity.v1.ListLevelRewardsRequest + 105, // 143: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:input_type -> hyapp.activity.v1.ConsumeLevelEventRequest + 108, // 144: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:input_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesRequest + 121, // 145: hyapp.activity.v1.AchievementService.ListAchievements:input_type -> hyapp.activity.v1.ListAchievementsRequest + 123, // 146: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:input_type -> hyapp.activity.v1.ConsumeAchievementEventRequest + 126, // 147: hyapp.activity.v1.AchievementService.ListMyBadges:input_type -> hyapp.activity.v1.ListMyBadgesRequest + 128, // 148: hyapp.activity.v1.AchievementService.SetBadgeDisplay:input_type -> hyapp.activity.v1.SetBadgeDisplayRequest + 138, // 149: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:input_type -> hyapp.activity.v1.CheckLuckyGiftRequest + 141, // 150: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:input_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawRequest + 32, // 151: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:input_type -> hyapp.activity.v1.EnsureBroadcastGroupsRequest + 34, // 152: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:input_type -> hyapp.activity.v1.PublishRegionBroadcastRequest + 35, // 153: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:input_type -> hyapp.activity.v1.PublishGlobalBroadcastRequest + 37, // 154: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:input_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberRequest + 21, // 155: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:input_type -> hyapp.activity.v1.CronBatchRequest + 40, // 156: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:input_type -> hyapp.activity.v1.ConsumeRoomEventRequest + 43, // 157: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:input_type -> hyapp.activity.v1.ListTaskDefinitionsRequest + 45, // 158: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:input_type -> hyapp.activity.v1.UpsertTaskDefinitionRequest + 47, // 159: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:input_type -> hyapp.activity.v1.SetTaskDefinitionStatusRequest + 52, // 160: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:input_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityRequest + 54, // 161: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:input_type -> hyapp.activity.v1.IssueRegistrationRewardRequest + 56, // 162: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:input_type -> hyapp.activity.v1.GetRegistrationRewardConfigRequest + 58, // 163: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:input_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigRequest + 60, // 164: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:input_type -> hyapp.activity.v1.ListRegistrationRewardClaimsRequest + 66, // 165: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:input_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusRequest + 68, // 166: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:input_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardRequest + 70, // 167: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigRequest + 72, // 168: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:input_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigRequest + 74, // 169: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:input_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsRequest + 79, // 170: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:input_type -> hyapp.activity.v1.GetSevenDayCheckInStatusRequest + 81, // 171: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:input_type -> hyapp.activity.v1.SignSevenDayCheckInRequest + 83, // 172: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:input_type -> hyapp.activity.v1.GetSevenDayCheckInConfigRequest + 85, // 173: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:input_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigRequest + 88, // 174: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:input_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsRequest + 116, // 175: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:input_type -> hyapp.activity.v1.ListLevelConfigRequest + 110, // 176: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:input_type -> hyapp.activity.v1.UpsertLevelTrackRequest + 112, // 177: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:input_type -> hyapp.activity.v1.UpsertLevelRuleRequest + 114, // 178: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:input_type -> hyapp.activity.v1.UpsertLevelTierRequest + 121, // 179: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:input_type -> hyapp.activity.v1.ListAchievementsRequest + 130, // 180: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:input_type -> hyapp.activity.v1.UpsertAchievementDefinitionRequest + 143, // 181: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:input_type -> hyapp.activity.v1.GetLuckyGiftConfigRequest + 145, // 182: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:input_type -> hyapp.activity.v1.UpsertLuckyGiftConfigRequest + 147, // 183: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:input_type -> hyapp.activity.v1.ListLuckyGiftConfigsRequest + 149, // 184: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:input_type -> hyapp.activity.v1.ListLuckyGiftDrawsRequest + 152, // 185: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:input_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryRequest + 2, // 186: hyapp.activity.v1.ActivityService.PingActivity:output_type -> hyapp.activity.v1.PingActivityResponse + 4, // 187: hyapp.activity.v1.ActivityService.GetActivityStatus:output_type -> hyapp.activity.v1.GetActivityStatusResponse + 7, // 188: hyapp.activity.v1.MessageInboxService.ListMessageTabs:output_type -> hyapp.activity.v1.ListMessageTabsResponse + 10, // 189: hyapp.activity.v1.MessageInboxService.ListInboxMessages:output_type -> hyapp.activity.v1.ListInboxMessagesResponse + 12, // 190: hyapp.activity.v1.MessageInboxService.MarkInboxMessageRead:output_type -> hyapp.activity.v1.MarkInboxMessageReadResponse + 14, // 191: hyapp.activity.v1.MessageInboxService.MarkInboxSectionRead:output_type -> hyapp.activity.v1.MarkInboxSectionReadResponse + 16, // 192: hyapp.activity.v1.MessageInboxService.DeleteInboxMessage:output_type -> hyapp.activity.v1.DeleteInboxMessageResponse + 18, // 193: hyapp.activity.v1.MessageInboxService.CreateInboxMessage:output_type -> hyapp.activity.v1.CreateInboxMessageResponse + 20, // 194: hyapp.activity.v1.MessageInboxService.CreateFanoutJob:output_type -> hyapp.activity.v1.CreateFanoutJobResponse + 22, // 195: hyapp.activity.v1.ActivityCronService.ProcessMessageFanoutBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 196: hyapp.activity.v1.ActivityCronService.ProcessLevelRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 22, // 197: hyapp.activity.v1.ActivityCronService.ProcessAchievementRewardBatch:output_type -> hyapp.activity.v1.CronBatchResponse + 26, // 198: hyapp.activity.v1.TaskService.ListUserTasks:output_type -> hyapp.activity.v1.ListUserTasksResponse + 28, // 199: hyapp.activity.v1.TaskService.ClaimTaskReward:output_type -> hyapp.activity.v1.ClaimTaskRewardResponse + 30, // 200: hyapp.activity.v1.TaskService.ConsumeTaskEvent:output_type -> hyapp.activity.v1.ConsumeTaskEventResponse + 95, // 201: hyapp.activity.v1.GrowthLevelService.GetMyLevelOverview:output_type -> hyapp.activity.v1.GetMyLevelOverviewResponse + 97, // 202: hyapp.activity.v1.GrowthLevelService.GetLevelTrack:output_type -> hyapp.activity.v1.GetLevelTrackResponse + 101, // 203: hyapp.activity.v1.GrowthLevelService.BatchGetUserLevelDisplayProfiles:output_type -> hyapp.activity.v1.BatchGetUserLevelDisplayProfilesResponse + 104, // 204: hyapp.activity.v1.GrowthLevelService.ListLevelRewards:output_type -> hyapp.activity.v1.ListLevelRewardsResponse + 106, // 205: hyapp.activity.v1.GrowthLevelService.ConsumeLevelEvent:output_type -> hyapp.activity.v1.ConsumeLevelEventResponse + 109, // 206: hyapp.activity.v1.GrowthLevelService.IssueRegistrationLevelBadges:output_type -> hyapp.activity.v1.IssueRegistrationLevelBadgesResponse + 122, // 207: hyapp.activity.v1.AchievementService.ListAchievements:output_type -> hyapp.activity.v1.ListAchievementsResponse + 124, // 208: hyapp.activity.v1.AchievementService.ConsumeAchievementEvent:output_type -> hyapp.activity.v1.ConsumeAchievementEventResponse + 127, // 209: hyapp.activity.v1.AchievementService.ListMyBadges:output_type -> hyapp.activity.v1.ListMyBadgesResponse + 129, // 210: hyapp.activity.v1.AchievementService.SetBadgeDisplay:output_type -> hyapp.activity.v1.SetBadgeDisplayResponse + 139, // 211: hyapp.activity.v1.LuckyGiftService.CheckLuckyGift:output_type -> hyapp.activity.v1.CheckLuckyGiftResponse + 142, // 212: hyapp.activity.v1.LuckyGiftService.ExecuteLuckyGiftDraw:output_type -> hyapp.activity.v1.ExecuteLuckyGiftDrawResponse + 33, // 213: hyapp.activity.v1.BroadcastService.EnsureBroadcastGroups:output_type -> hyapp.activity.v1.EnsureBroadcastGroupsResponse + 36, // 214: hyapp.activity.v1.BroadcastService.PublishRegionBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 36, // 215: hyapp.activity.v1.BroadcastService.PublishGlobalBroadcast:output_type -> hyapp.activity.v1.PublishBroadcastResponse + 38, // 216: hyapp.activity.v1.BroadcastService.RemoveRegionBroadcastMember:output_type -> hyapp.activity.v1.RemoveRegionBroadcastMemberResponse + 39, // 217: hyapp.activity.v1.BroadcastService.ProcessBroadcastOutboxBatch:output_type -> hyapp.activity.v1.ProcessBroadcastOutboxBatchResponse + 41, // 218: hyapp.activity.v1.RoomEventConsumerService.ConsumeRoomEvent:output_type -> hyapp.activity.v1.ConsumeRoomEventResponse + 44, // 219: hyapp.activity.v1.AdminTaskService.ListTaskDefinitions:output_type -> hyapp.activity.v1.ListTaskDefinitionsResponse + 46, // 220: hyapp.activity.v1.AdminTaskService.UpsertTaskDefinition:output_type -> hyapp.activity.v1.UpsertTaskDefinitionResponse + 48, // 221: hyapp.activity.v1.AdminTaskService.SetTaskDefinitionStatus:output_type -> hyapp.activity.v1.SetTaskDefinitionStatusResponse + 53, // 222: hyapp.activity.v1.RegistrationRewardService.GetRegistrationRewardEligibility:output_type -> hyapp.activity.v1.GetRegistrationRewardEligibilityResponse + 55, // 223: hyapp.activity.v1.RegistrationRewardService.IssueRegistrationReward:output_type -> hyapp.activity.v1.IssueRegistrationRewardResponse + 57, // 224: hyapp.activity.v1.AdminRegistrationRewardService.GetRegistrationRewardConfig:output_type -> hyapp.activity.v1.GetRegistrationRewardConfigResponse + 59, // 225: hyapp.activity.v1.AdminRegistrationRewardService.UpdateRegistrationRewardConfig:output_type -> hyapp.activity.v1.UpdateRegistrationRewardConfigResponse + 61, // 226: hyapp.activity.v1.AdminRegistrationRewardService.ListRegistrationRewardClaims:output_type -> hyapp.activity.v1.ListRegistrationRewardClaimsResponse + 67, // 227: hyapp.activity.v1.FirstRechargeRewardService.GetFirstRechargeRewardStatus:output_type -> hyapp.activity.v1.GetFirstRechargeRewardStatusResponse + 69, // 228: hyapp.activity.v1.FirstRechargeRewardService.ConsumeFirstRechargeReward:output_type -> hyapp.activity.v1.ConsumeFirstRechargeRewardResponse + 71, // 229: hyapp.activity.v1.AdminFirstRechargeRewardService.GetFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.GetFirstRechargeRewardConfigResponse + 73, // 230: hyapp.activity.v1.AdminFirstRechargeRewardService.UpdateFirstRechargeRewardConfig:output_type -> hyapp.activity.v1.UpdateFirstRechargeRewardConfigResponse + 75, // 231: hyapp.activity.v1.AdminFirstRechargeRewardService.ListFirstRechargeRewardClaims:output_type -> hyapp.activity.v1.ListFirstRechargeRewardClaimsResponse + 80, // 232: hyapp.activity.v1.SevenDayCheckInService.GetSevenDayCheckInStatus:output_type -> hyapp.activity.v1.GetSevenDayCheckInStatusResponse + 82, // 233: hyapp.activity.v1.SevenDayCheckInService.SignSevenDayCheckIn:output_type -> hyapp.activity.v1.SignSevenDayCheckInResponse + 84, // 234: hyapp.activity.v1.AdminSevenDayCheckInService.GetSevenDayCheckInConfig:output_type -> hyapp.activity.v1.GetSevenDayCheckInConfigResponse + 86, // 235: hyapp.activity.v1.AdminSevenDayCheckInService.UpdateSevenDayCheckInConfig:output_type -> hyapp.activity.v1.UpdateSevenDayCheckInConfigResponse + 89, // 236: hyapp.activity.v1.AdminSevenDayCheckInService.ListSevenDayCheckInClaims:output_type -> hyapp.activity.v1.ListSevenDayCheckInClaimsResponse + 117, // 237: hyapp.activity.v1.AdminGrowthLevelService.ListLevelConfig:output_type -> hyapp.activity.v1.ListLevelConfigResponse + 111, // 238: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTrack:output_type -> hyapp.activity.v1.UpsertLevelTrackResponse + 113, // 239: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelRule:output_type -> hyapp.activity.v1.UpsertLevelRuleResponse + 115, // 240: hyapp.activity.v1.AdminGrowthLevelService.UpsertLevelTier:output_type -> hyapp.activity.v1.UpsertLevelTierResponse + 122, // 241: hyapp.activity.v1.AdminAchievementService.ListAchievementDefinitions:output_type -> hyapp.activity.v1.ListAchievementsResponse + 131, // 242: hyapp.activity.v1.AdminAchievementService.UpsertAchievementDefinition:output_type -> hyapp.activity.v1.UpsertAchievementDefinitionResponse + 144, // 243: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftConfig:output_type -> hyapp.activity.v1.GetLuckyGiftConfigResponse + 146, // 244: hyapp.activity.v1.AdminLuckyGiftService.UpsertLuckyGiftConfig:output_type -> hyapp.activity.v1.UpsertLuckyGiftConfigResponse + 148, // 245: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftConfigs:output_type -> hyapp.activity.v1.ListLuckyGiftConfigsResponse + 150, // 246: hyapp.activity.v1.AdminLuckyGiftService.ListLuckyGiftDraws:output_type -> hyapp.activity.v1.ListLuckyGiftDrawsResponse + 153, // 247: hyapp.activity.v1.AdminLuckyGiftService.GetLuckyGiftDrawSummary:output_type -> hyapp.activity.v1.GetLuckyGiftDrawSummaryResponse + 186, // [186:248] is the sub-list for method output_type + 124, // [124:186] is the sub-list for method input_type + 124, // [124:124] is the sub-list for extension type_name + 124, // [124:124] is the sub-list for extension extendee + 0, // [0:124] is the sub-list for field type_name } func init() { file_proto_activity_v1_activity_proto_init() } @@ -13701,7 +14209,7 @@ func file_proto_activity_v1_activity_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_activity_v1_activity_proto_rawDesc), len(file_proto_activity_v1_activity_proto_rawDesc)), NumEnums: 0, - NumMessages: 147, + NumMessages: 154, NumExtensions: 0, NumServices: 19, }, diff --git a/api/proto/activity/v1/activity.proto b/api/proto/activity/v1/activity.proto index 57f07001..50d6e5c3 100644 --- a/api/proto/activity/v1/activity.proto +++ b/api/proto/activity/v1/activity.proto @@ -825,6 +825,7 @@ message LevelTrackOverview { int64 display_badge_resource_id = 10; int64 reward_pending_count = 11; int32 sort_order = 12; + int32 display_badge_source_level = 13; } message GetMyLevelOverviewRequest { @@ -850,6 +851,35 @@ message GetLevelTrackResponse { int64 server_time_ms = 4; } +// LevelDisplayTrackProfile 是资料卡和房间展示使用的单轨道等级投影。 +message LevelDisplayTrackProfile { + string track = 1; + int32 level = 2; + int64 tier_id = 3; + int64 avatar_frame_resource_id = 4; + int64 badge_resource_id = 5; + int32 badge_source_level = 6; + int64 updated_at_ms = 7; +} + +// UserLevelDisplayProfile 固定返回财富、游戏、魅力三条轨道,避免客户端按数组猜槽位。 +message UserLevelDisplayProfile { + int64 user_id = 1; + LevelDisplayTrackProfile wealth = 2; + LevelDisplayTrackProfile game = 3; + LevelDisplayTrackProfile charm = 4; +} + +message BatchGetUserLevelDisplayProfilesRequest { + RequestMeta meta = 1; + repeated int64 user_ids = 2; +} + +message BatchGetUserLevelDisplayProfilesResponse { + repeated UserLevelDisplayProfile profiles = 1; + int64 server_time_ms = 2; +} + // LevelRewardJob 是等级或等级段奖励的发放状态。 message LevelRewardJob { string reward_job_id = 1; @@ -905,6 +935,24 @@ message ConsumeLevelEventResponse { int64 reward_job_count = 6; } +message RegistrationLevelBadgeGrant { + string track = 1; + int32 level = 2; + int64 resource_id = 3; + string grant_id = 4; +} + +message IssueRegistrationLevelBadgesRequest { + RequestMeta meta = 1; + int64 user_id = 2; + string command_id = 3; +} + +message IssueRegistrationLevelBadgesResponse { + repeated RegistrationLevelBadgeGrant grants = 1; + int64 server_time_ms = 2; +} + message UpsertLevelTrackRequest { RequestMeta meta = 1; string track = 2; @@ -1383,8 +1431,10 @@ service TaskService { service GrowthLevelService { rpc GetMyLevelOverview(GetMyLevelOverviewRequest) returns (GetMyLevelOverviewResponse); rpc GetLevelTrack(GetLevelTrackRequest) returns (GetLevelTrackResponse); + rpc BatchGetUserLevelDisplayProfiles(BatchGetUserLevelDisplayProfilesRequest) returns (BatchGetUserLevelDisplayProfilesResponse); rpc ListLevelRewards(ListLevelRewardsRequest) returns (ListLevelRewardsResponse); rpc ConsumeLevelEvent(ConsumeLevelEventRequest) returns (ConsumeLevelEventResponse); + rpc IssueRegistrationLevelBadges(IssueRegistrationLevelBadgesRequest) returns (IssueRegistrationLevelBadgesResponse); } // AchievementService owns App achievement reads, badge display and internal achievement event consumption. diff --git a/api/proto/activity/v1/activity_grpc.pb.go b/api/proto/activity/v1/activity_grpc.pb.go index 2d2f118f..d19e7524 100644 --- a/api/proto/activity/v1/activity_grpc.pb.go +++ b/api/proto/activity/v1/activity_grpc.pb.go @@ -861,10 +861,12 @@ var TaskService_ServiceDesc = grpc.ServiceDesc{ } const ( - GrowthLevelService_GetMyLevelOverview_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/GetMyLevelOverview" - GrowthLevelService_GetLevelTrack_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/GetLevelTrack" - GrowthLevelService_ListLevelRewards_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/ListLevelRewards" - GrowthLevelService_ConsumeLevelEvent_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/ConsumeLevelEvent" + GrowthLevelService_GetMyLevelOverview_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/GetMyLevelOverview" + GrowthLevelService_GetLevelTrack_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/GetLevelTrack" + GrowthLevelService_BatchGetUserLevelDisplayProfiles_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/BatchGetUserLevelDisplayProfiles" + GrowthLevelService_ListLevelRewards_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/ListLevelRewards" + GrowthLevelService_ConsumeLevelEvent_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/ConsumeLevelEvent" + GrowthLevelService_IssueRegistrationLevelBadges_FullMethodName = "/hyapp.activity.v1.GrowthLevelService/IssueRegistrationLevelBadges" ) // GrowthLevelServiceClient is the client API for GrowthLevelService service. @@ -875,8 +877,10 @@ const ( type GrowthLevelServiceClient interface { GetMyLevelOverview(ctx context.Context, in *GetMyLevelOverviewRequest, opts ...grpc.CallOption) (*GetMyLevelOverviewResponse, error) GetLevelTrack(ctx context.Context, in *GetLevelTrackRequest, opts ...grpc.CallOption) (*GetLevelTrackResponse, error) + BatchGetUserLevelDisplayProfiles(ctx context.Context, in *BatchGetUserLevelDisplayProfilesRequest, opts ...grpc.CallOption) (*BatchGetUserLevelDisplayProfilesResponse, error) ListLevelRewards(ctx context.Context, in *ListLevelRewardsRequest, opts ...grpc.CallOption) (*ListLevelRewardsResponse, error) ConsumeLevelEvent(ctx context.Context, in *ConsumeLevelEventRequest, opts ...grpc.CallOption) (*ConsumeLevelEventResponse, error) + IssueRegistrationLevelBadges(ctx context.Context, in *IssueRegistrationLevelBadgesRequest, opts ...grpc.CallOption) (*IssueRegistrationLevelBadgesResponse, error) } type growthLevelServiceClient struct { @@ -907,6 +911,16 @@ func (c *growthLevelServiceClient) GetLevelTrack(ctx context.Context, in *GetLev return out, nil } +func (c *growthLevelServiceClient) BatchGetUserLevelDisplayProfiles(ctx context.Context, in *BatchGetUserLevelDisplayProfilesRequest, opts ...grpc.CallOption) (*BatchGetUserLevelDisplayProfilesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(BatchGetUserLevelDisplayProfilesResponse) + err := c.cc.Invoke(ctx, GrowthLevelService_BatchGetUserLevelDisplayProfiles_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *growthLevelServiceClient) ListLevelRewards(ctx context.Context, in *ListLevelRewardsRequest, opts ...grpc.CallOption) (*ListLevelRewardsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListLevelRewardsResponse) @@ -927,6 +941,16 @@ func (c *growthLevelServiceClient) ConsumeLevelEvent(ctx context.Context, in *Co return out, nil } +func (c *growthLevelServiceClient) IssueRegistrationLevelBadges(ctx context.Context, in *IssueRegistrationLevelBadgesRequest, opts ...grpc.CallOption) (*IssueRegistrationLevelBadgesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(IssueRegistrationLevelBadgesResponse) + err := c.cc.Invoke(ctx, GrowthLevelService_IssueRegistrationLevelBadges_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // GrowthLevelServiceServer is the server API for GrowthLevelService service. // All implementations must embed UnimplementedGrowthLevelServiceServer // for forward compatibility. @@ -935,8 +959,10 @@ func (c *growthLevelServiceClient) ConsumeLevelEvent(ctx context.Context, in *Co type GrowthLevelServiceServer interface { GetMyLevelOverview(context.Context, *GetMyLevelOverviewRequest) (*GetMyLevelOverviewResponse, error) GetLevelTrack(context.Context, *GetLevelTrackRequest) (*GetLevelTrackResponse, error) + BatchGetUserLevelDisplayProfiles(context.Context, *BatchGetUserLevelDisplayProfilesRequest) (*BatchGetUserLevelDisplayProfilesResponse, error) ListLevelRewards(context.Context, *ListLevelRewardsRequest) (*ListLevelRewardsResponse, error) ConsumeLevelEvent(context.Context, *ConsumeLevelEventRequest) (*ConsumeLevelEventResponse, error) + IssueRegistrationLevelBadges(context.Context, *IssueRegistrationLevelBadgesRequest) (*IssueRegistrationLevelBadgesResponse, error) mustEmbedUnimplementedGrowthLevelServiceServer() } @@ -953,12 +979,18 @@ func (UnimplementedGrowthLevelServiceServer) GetMyLevelOverview(context.Context, func (UnimplementedGrowthLevelServiceServer) GetLevelTrack(context.Context, *GetLevelTrackRequest) (*GetLevelTrackResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetLevelTrack not implemented") } +func (UnimplementedGrowthLevelServiceServer) BatchGetUserLevelDisplayProfiles(context.Context, *BatchGetUserLevelDisplayProfilesRequest) (*BatchGetUserLevelDisplayProfilesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method BatchGetUserLevelDisplayProfiles not implemented") +} func (UnimplementedGrowthLevelServiceServer) ListLevelRewards(context.Context, *ListLevelRewardsRequest) (*ListLevelRewardsResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListLevelRewards not implemented") } func (UnimplementedGrowthLevelServiceServer) ConsumeLevelEvent(context.Context, *ConsumeLevelEventRequest) (*ConsumeLevelEventResponse, error) { return nil, status.Error(codes.Unimplemented, "method ConsumeLevelEvent not implemented") } +func (UnimplementedGrowthLevelServiceServer) IssueRegistrationLevelBadges(context.Context, *IssueRegistrationLevelBadgesRequest) (*IssueRegistrationLevelBadgesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method IssueRegistrationLevelBadges not implemented") +} func (UnimplementedGrowthLevelServiceServer) mustEmbedUnimplementedGrowthLevelServiceServer() {} func (UnimplementedGrowthLevelServiceServer) testEmbeddedByValue() {} @@ -1016,6 +1048,24 @@ func _GrowthLevelService_GetLevelTrack_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _GrowthLevelService_BatchGetUserLevelDisplayProfiles_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BatchGetUserLevelDisplayProfilesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrowthLevelServiceServer).BatchGetUserLevelDisplayProfiles(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GrowthLevelService_BatchGetUserLevelDisplayProfiles_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrowthLevelServiceServer).BatchGetUserLevelDisplayProfiles(ctx, req.(*BatchGetUserLevelDisplayProfilesRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _GrowthLevelService_ListLevelRewards_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListLevelRewardsRequest) if err := dec(in); err != nil { @@ -1052,6 +1102,24 @@ func _GrowthLevelService_ConsumeLevelEvent_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _GrowthLevelService_IssueRegistrationLevelBadges_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(IssueRegistrationLevelBadgesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(GrowthLevelServiceServer).IssueRegistrationLevelBadges(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: GrowthLevelService_IssueRegistrationLevelBadges_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(GrowthLevelServiceServer).IssueRegistrationLevelBadges(ctx, req.(*IssueRegistrationLevelBadgesRequest)) + } + return interceptor(ctx, in, info, handler) +} + // GrowthLevelService_ServiceDesc is the grpc.ServiceDesc for GrowthLevelService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -1067,6 +1135,10 @@ var GrowthLevelService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetLevelTrack", Handler: _GrowthLevelService_GetLevelTrack_Handler, }, + { + MethodName: "BatchGetUserLevelDisplayProfiles", + Handler: _GrowthLevelService_BatchGetUserLevelDisplayProfiles_Handler, + }, { MethodName: "ListLevelRewards", Handler: _GrowthLevelService_ListLevelRewards_Handler, @@ -1075,6 +1147,10 @@ var GrowthLevelService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ConsumeLevelEvent", Handler: _GrowthLevelService_ConsumeLevelEvent_Handler, }, + { + MethodName: "IssueRegistrationLevelBadges", + Handler: _GrowthLevelService_IssueRegistrationLevelBadges_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/activity/v1/activity.proto", diff --git a/api/proto/room/v1/room.pb.go b/api/proto/room/v1/room.pb.go index d3ac2af1..47853acd 100644 --- a/api/proto/room/v1/room.pb.go +++ b/api/proto/room/v1/room.pb.go @@ -564,8 +564,10 @@ type LuckyGiftDrawResult struct { CreatedAtMs int64 `protobuf:"varint,17,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` WalletTransactionId string `protobuf:"bytes,18,opt,name=wallet_transaction_id,json=walletTransactionId,proto3" json:"wallet_transaction_id,omitempty"` CoinBalanceAfter int64 `protobuf:"varint,19,opt,name=coin_balance_after,json=coinBalanceAfter,proto3" json:"coin_balance_after,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // target_user_id 是本次幸运礼物抽奖对应的收礼用户;多目标送礼用它关联结果。 + TargetUserId int64 `protobuf:"varint,20,opt,name=target_user_id,json=targetUserId,proto3" json:"target_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *LuckyGiftDrawResult) Reset() { @@ -731,6 +733,13 @@ func (x *LuckyGiftDrawResult) GetCoinBalanceAfter() int64 { return 0 } +func (x *LuckyGiftDrawResult) GetTargetUserId() int64 { + if x != nil { + return x.TargetUserId + } + return 0 +} + // RoomTreasureRewardItem 是后台配置给客户端展示的宝箱奖励候选项。 type RoomTreasureRewardItem struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -6770,9 +6779,12 @@ type SendGiftResponse struct { GiftRank []*RankItem `protobuf:"bytes,4,rep,name=gift_rank,json=giftRank,proto3" json:"gift_rank,omitempty"` Room *RoomSnapshot `protobuf:"bytes,5,opt,name=room,proto3" json:"room,omitempty"` Treasure *RoomTreasureState `protobuf:"bytes,6,opt,name=treasure,proto3" json:"treasure,omitempty"` - LuckyGift *LuckyGiftDrawResult `protobuf:"bytes,7,opt,name=lucky_gift,json=luckyGift,proto3" json:"lucky_gift,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // lucky_gift 是本次送礼的聚合幸运礼物结果;多目标时倍率和返奖金额按所有目标累加。 + LuckyGift *LuckyGiftDrawResult `protobuf:"bytes,7,opt,name=lucky_gift,json=luckyGift,proto3" json:"lucky_gift,omitempty"` + // lucky_gifts 是每个收礼目标的独立抽奖结果;多目标客户端用 target_user_id 对齐明细表现。 + LuckyGifts []*LuckyGiftDrawResult `protobuf:"bytes,8,rep,name=lucky_gifts,json=luckyGifts,proto3" json:"lucky_gifts,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *SendGiftResponse) Reset() { @@ -6854,6 +6866,13 @@ func (x *SendGiftResponse) GetLuckyGift() *LuckyGiftDrawResult { return nil } +func (x *SendGiftResponse) GetLuckyGifts() []*LuckyGiftDrawResult { + if x != nil { + return x.LuckyGifts + } + return nil +} + // CheckSpeakPermissionRequest 让腾讯云 IM 发言回调或 gateway 在公屏前同步问房间业务态。 type CheckSpeakPermissionRequest struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -9022,7 +9041,7 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\x05score\x18\x02 \x01(\x03R\x05score\x12\x1d\n" + "\n" + "gift_value\x18\x03 \x01(\x03R\tgiftValue\x12\"\n" + - "\rupdated_at_ms\x18\x04 \x01(\x03R\vupdatedAtMs\"\x8a\x06\n" + + "\rupdated_at_ms\x18\x04 \x01(\x03R\vupdatedAtMs\"\xb0\x06\n" + "\x13LuckyGiftDrawResult\x12\x18\n" + "\aenabled\x18\x01 \x01(\bR\aenabled\x12\x17\n" + "\adraw_id\x18\x02 \x01(\tR\x06drawId\x12\x1d\n" + @@ -9044,7 +9063,8 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\x0fhigh_multiplier\x18\x10 \x01(\bR\x0ehighMultiplier\x12\"\n" + "\rcreated_at_ms\x18\x11 \x01(\x03R\vcreatedAtMs\x122\n" + "\x15wallet_transaction_id\x18\x12 \x01(\tR\x13walletTransactionId\x12,\n" + - "\x12coin_balance_after\x18\x13 \x01(\x03R\x10coinBalanceAfter\"\xc0\x01\n" + + "\x12coin_balance_after\x18\x13 \x01(\x03R\x10coinBalanceAfter\x12$\n" + + "\x0etarget_user_id\x18\x14 \x01(\x03R\ftargetUserId\"\xc0\x01\n" + "\x16RoomTreasureRewardItem\x12$\n" + "\x0ereward_item_id\x18\x01 \x01(\tR\frewardItemId\x12*\n" + "\x11resource_group_id\x18\x02 \x01(\x03R\x0fresourceGroupId\x12\x16\n" + @@ -9543,7 +9563,7 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\x15target_host_region_id\x18\t \x01(\x03R\x12targetHostRegionId\x12<\n" + "\x1btarget_agency_owner_user_id\x18\n" + " \x01(\x03R\x17targetAgencyOwnerUserId\x12(\n" + - "\x10sender_region_id\x18\v \x01(\x03R\x0esenderRegionId\"\xfb\x02\n" + + "\x10sender_region_id\x18\v \x01(\x03R\x0esenderRegionId\"\xc0\x03\n" + "\x10SendGiftResponse\x124\n" + "\x06result\x18\x01 \x01(\v2\x1c.hyapp.room.v1.CommandResultR\x06result\x12,\n" + "\x12billing_receipt_id\x18\x02 \x01(\tR\x10billingReceiptId\x12\x1b\n" + @@ -9552,7 +9572,9 @@ const file_proto_room_v1_room_proto_rawDesc = "" + "\x04room\x18\x05 \x01(\v2\x1b.hyapp.room.v1.RoomSnapshotR\x04room\x12<\n" + "\btreasure\x18\x06 \x01(\v2 .hyapp.room.v1.RoomTreasureStateR\btreasure\x12A\n" + "\n" + - "lucky_gift\x18\a \x01(\v2\".hyapp.room.v1.LuckyGiftDrawResultR\tluckyGift\"j\n" + + "lucky_gift\x18\a \x01(\v2\".hyapp.room.v1.LuckyGiftDrawResultR\tluckyGift\x12C\n" + + "\vlucky_gifts\x18\b \x03(\v2\".hyapp.room.v1.LuckyGiftDrawResultR\n" + + "luckyGifts\"j\n" + "\x1bCheckSpeakPermissionRequest\x12\x17\n" + "\aroom_id\x18\x01 \x01(\tR\x06roomId\x12\x17\n" + "\auser_id\x18\x02 \x01(\x03R\x06userId\x12\x19\n" + @@ -10028,128 +10050,129 @@ var file_proto_room_v1_room_proto_depIdxs = []int32{ 32, // 114: hyapp.room.v1.SendGiftResponse.room:type_name -> hyapp.room.v1.RoomSnapshot 10, // 115: hyapp.room.v1.SendGiftResponse.treasure:type_name -> hyapp.room.v1.RoomTreasureState 6, // 116: hyapp.room.v1.SendGiftResponse.lucky_gift:type_name -> hyapp.room.v1.LuckyGiftDrawResult - 0, // 117: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 118: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 99, // 119: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser - 0, // 120: hyapp.room.v1.ListRoomGiftLeaderboardRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 100, // 121: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem - 100, // 122: hyapp.room.v1.RoomGiftLeaderboardItem.room:type_name -> hyapp.room.v1.RoomListItem - 102, // 123: hyapp.room.v1.ListRoomGiftLeaderboardResponse.items:type_name -> hyapp.room.v1.RoomGiftLeaderboardItem - 0, // 124: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 100, // 125: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem - 0, // 126: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 127: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 32, // 128: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot - 0, // 129: hyapp.room.v1.GetRoomTreasureRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 11, // 130: hyapp.room.v1.GetRoomTreasureResponse.treasure:type_name -> hyapp.room.v1.RoomTreasureInfo - 0, // 131: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 2, // 132: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser - 3, // 133: hyapp.room.v1.ListRoomOnlineUsersResponse.items:type_name -> hyapp.room.v1.RoomOnlineUser - 0, // 134: hyapp.room.v1.ListRoomBannedUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 114, // 135: hyapp.room.v1.ListRoomBannedUsersResponse.items:type_name -> hyapp.room.v1.RoomBannedUser - 0, // 136: hyapp.room.v1.FollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 0, // 137: hyapp.room.v1.UnfollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta - 40, // 138: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest - 42, // 139: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest - 34, // 140: hyapp.room.v1.RoomCommandService.SaveRoomBackground:input_type -> hyapp.room.v1.SaveRoomBackgroundRequest - 38, // 141: hyapp.room.v1.RoomCommandService.SetRoomBackground:input_type -> hyapp.room.v1.SetRoomBackgroundRequest - 45, // 142: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest - 47, // 143: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest - 49, // 144: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest - 51, // 145: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest - 58, // 146: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:input_type -> hyapp.room.v1.AdminUpdateRoomRequest - 60, // 147: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:input_type -> hyapp.room.v1.AdminDeleteRoomRequest - 16, // 148: hyapp.room.v1.RoomCommandService.AdminUpdateRoomTreasureConfig:input_type -> hyapp.room.v1.AdminUpdateRoomTreasureConfigRequest - 21, // 149: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:input_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigRequest - 27, // 150: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:input_type -> hyapp.room.v1.AdminCreateRoomPinRequest - 29, // 151: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:input_type -> hyapp.room.v1.AdminCancelRoomPinRequest - 62, // 152: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest - 64, // 153: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest - 66, // 154: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest - 68, // 155: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest - 70, // 156: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest - 72, // 157: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest - 74, // 158: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest - 76, // 159: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest - 78, // 160: hyapp.room.v1.RoomCommandService.SetRoomPassword:input_type -> hyapp.room.v1.SetRoomPasswordRequest - 80, // 161: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest - 82, // 162: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest - 84, // 163: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest - 86, // 164: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest - 88, // 165: hyapp.room.v1.RoomCommandService.SystemEvictUser:input_type -> hyapp.room.v1.SystemEvictUserRequest - 90, // 166: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest - 117, // 167: hyapp.room.v1.RoomCommandService.FollowRoom:input_type -> hyapp.room.v1.FollowRoomRequest - 119, // 168: hyapp.room.v1.RoomCommandService.UnfollowRoom:input_type -> hyapp.room.v1.UnfollowRoomRequest - 92, // 169: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest - 94, // 170: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest - 54, // 171: hyapp.room.v1.RoomQueryService.AdminListRooms:input_type -> hyapp.room.v1.AdminListRoomsRequest - 56, // 172: hyapp.room.v1.RoomQueryService.AdminGetRoom:input_type -> hyapp.room.v1.AdminGetRoomRequest - 14, // 173: hyapp.room.v1.RoomQueryService.AdminGetRoomTreasureConfig:input_type -> hyapp.room.v1.AdminGetRoomTreasureConfigRequest - 19, // 174: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:input_type -> hyapp.room.v1.AdminGetRoomSeatConfigRequest - 25, // 175: hyapp.room.v1.RoomQueryService.AdminListRoomPins:input_type -> hyapp.room.v1.AdminListRoomPinsRequest - 96, // 176: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest - 97, // 177: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest - 98, // 178: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:input_type -> hyapp.room.v1.ListRoomGiftLeaderboardRequest - 104, // 179: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest - 106, // 180: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest - 108, // 181: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest - 36, // 182: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:input_type -> hyapp.room.v1.ListRoomBackgroundsRequest - 110, // 183: hyapp.room.v1.RoomQueryService.GetRoomTreasure:input_type -> hyapp.room.v1.GetRoomTreasureRequest - 112, // 184: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest - 115, // 185: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:input_type -> hyapp.room.v1.ListRoomBannedUsersRequest - 41, // 186: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse - 43, // 187: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse - 35, // 188: hyapp.room.v1.RoomCommandService.SaveRoomBackground:output_type -> hyapp.room.v1.SaveRoomBackgroundResponse - 39, // 189: hyapp.room.v1.RoomCommandService.SetRoomBackground:output_type -> hyapp.room.v1.SetRoomBackgroundResponse - 46, // 190: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse - 48, // 191: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse - 50, // 192: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse - 52, // 193: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse - 59, // 194: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:output_type -> hyapp.room.v1.AdminUpdateRoomResponse - 61, // 195: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:output_type -> hyapp.room.v1.AdminDeleteRoomResponse - 17, // 196: hyapp.room.v1.RoomCommandService.AdminUpdateRoomTreasureConfig:output_type -> hyapp.room.v1.AdminUpdateRoomTreasureConfigResponse - 22, // 197: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:output_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigResponse - 28, // 198: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:output_type -> hyapp.room.v1.AdminCreateRoomPinResponse - 30, // 199: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:output_type -> hyapp.room.v1.AdminCancelRoomPinResponse - 63, // 200: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse - 65, // 201: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse - 67, // 202: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse - 69, // 203: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse - 71, // 204: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse - 73, // 205: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse - 75, // 206: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse - 77, // 207: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse - 79, // 208: hyapp.room.v1.RoomCommandService.SetRoomPassword:output_type -> hyapp.room.v1.SetRoomPasswordResponse - 81, // 209: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse - 83, // 210: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse - 85, // 211: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse - 87, // 212: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse - 89, // 213: hyapp.room.v1.RoomCommandService.SystemEvictUser:output_type -> hyapp.room.v1.SystemEvictUserResponse - 91, // 214: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse - 118, // 215: hyapp.room.v1.RoomCommandService.FollowRoom:output_type -> hyapp.room.v1.FollowRoomResponse - 120, // 216: hyapp.room.v1.RoomCommandService.UnfollowRoom:output_type -> hyapp.room.v1.UnfollowRoomResponse - 93, // 217: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse - 95, // 218: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse - 55, // 219: hyapp.room.v1.RoomQueryService.AdminListRooms:output_type -> hyapp.room.v1.AdminListRoomsResponse - 57, // 220: hyapp.room.v1.RoomQueryService.AdminGetRoom:output_type -> hyapp.room.v1.AdminGetRoomResponse - 15, // 221: hyapp.room.v1.RoomQueryService.AdminGetRoomTreasureConfig:output_type -> hyapp.room.v1.AdminGetRoomTreasureConfigResponse - 20, // 222: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:output_type -> hyapp.room.v1.AdminGetRoomSeatConfigResponse - 26, // 223: hyapp.room.v1.RoomQueryService.AdminListRoomPins:output_type -> hyapp.room.v1.AdminListRoomPinsResponse - 101, // 224: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse - 101, // 225: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse - 103, // 226: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:output_type -> hyapp.room.v1.ListRoomGiftLeaderboardResponse - 105, // 227: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse - 107, // 228: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse - 109, // 229: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse - 37, // 230: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:output_type -> hyapp.room.v1.ListRoomBackgroundsResponse - 111, // 231: hyapp.room.v1.RoomQueryService.GetRoomTreasure:output_type -> hyapp.room.v1.GetRoomTreasureResponse - 113, // 232: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse - 116, // 233: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:output_type -> hyapp.room.v1.ListRoomBannedUsersResponse - 186, // [186:234] is the sub-list for method output_type - 138, // [138:186] is the sub-list for method input_type - 138, // [138:138] is the sub-list for extension type_name - 138, // [138:138] is the sub-list for extension extendee - 0, // [0:138] is the sub-list for field type_name + 6, // 117: hyapp.room.v1.SendGiftResponse.lucky_gifts:type_name -> hyapp.room.v1.LuckyGiftDrawResult + 0, // 118: hyapp.room.v1.ListRoomsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 119: hyapp.room.v1.ListRoomFeedsRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 99, // 120: hyapp.room.v1.ListRoomFeedsRequest.related_users:type_name -> hyapp.room.v1.RoomFeedRelatedUser + 0, // 121: hyapp.room.v1.ListRoomGiftLeaderboardRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 100, // 122: hyapp.room.v1.ListRoomsResponse.rooms:type_name -> hyapp.room.v1.RoomListItem + 100, // 123: hyapp.room.v1.RoomGiftLeaderboardItem.room:type_name -> hyapp.room.v1.RoomListItem + 102, // 124: hyapp.room.v1.ListRoomGiftLeaderboardResponse.items:type_name -> hyapp.room.v1.RoomGiftLeaderboardItem + 0, // 125: hyapp.room.v1.GetMyRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 100, // 126: hyapp.room.v1.GetMyRoomResponse.room:type_name -> hyapp.room.v1.RoomListItem + 0, // 127: hyapp.room.v1.GetCurrentRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 128: hyapp.room.v1.GetRoomSnapshotRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 32, // 129: hyapp.room.v1.GetRoomSnapshotResponse.room:type_name -> hyapp.room.v1.RoomSnapshot + 0, // 130: hyapp.room.v1.GetRoomTreasureRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 11, // 131: hyapp.room.v1.GetRoomTreasureResponse.treasure:type_name -> hyapp.room.v1.RoomTreasureInfo + 0, // 132: hyapp.room.v1.ListRoomOnlineUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 2, // 133: hyapp.room.v1.ListRoomOnlineUsersResponse.users:type_name -> hyapp.room.v1.RoomUser + 3, // 134: hyapp.room.v1.ListRoomOnlineUsersResponse.items:type_name -> hyapp.room.v1.RoomOnlineUser + 0, // 135: hyapp.room.v1.ListRoomBannedUsersRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 114, // 136: hyapp.room.v1.ListRoomBannedUsersResponse.items:type_name -> hyapp.room.v1.RoomBannedUser + 0, // 137: hyapp.room.v1.FollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 0, // 138: hyapp.room.v1.UnfollowRoomRequest.meta:type_name -> hyapp.room.v1.RequestMeta + 40, // 139: hyapp.room.v1.RoomCommandService.CreateRoom:input_type -> hyapp.room.v1.CreateRoomRequest + 42, // 140: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:input_type -> hyapp.room.v1.UpdateRoomProfileRequest + 34, // 141: hyapp.room.v1.RoomCommandService.SaveRoomBackground:input_type -> hyapp.room.v1.SaveRoomBackgroundRequest + 38, // 142: hyapp.room.v1.RoomCommandService.SetRoomBackground:input_type -> hyapp.room.v1.SetRoomBackgroundRequest + 45, // 143: hyapp.room.v1.RoomCommandService.JoinRoom:input_type -> hyapp.room.v1.JoinRoomRequest + 47, // 144: hyapp.room.v1.RoomCommandService.RoomHeartbeat:input_type -> hyapp.room.v1.RoomHeartbeatRequest + 49, // 145: hyapp.room.v1.RoomCommandService.LeaveRoom:input_type -> hyapp.room.v1.LeaveRoomRequest + 51, // 146: hyapp.room.v1.RoomCommandService.CloseRoom:input_type -> hyapp.room.v1.CloseRoomRequest + 58, // 147: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:input_type -> hyapp.room.v1.AdminUpdateRoomRequest + 60, // 148: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:input_type -> hyapp.room.v1.AdminDeleteRoomRequest + 16, // 149: hyapp.room.v1.RoomCommandService.AdminUpdateRoomTreasureConfig:input_type -> hyapp.room.v1.AdminUpdateRoomTreasureConfigRequest + 21, // 150: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:input_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigRequest + 27, // 151: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:input_type -> hyapp.room.v1.AdminCreateRoomPinRequest + 29, // 152: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:input_type -> hyapp.room.v1.AdminCancelRoomPinRequest + 62, // 153: hyapp.room.v1.RoomCommandService.MicUp:input_type -> hyapp.room.v1.MicUpRequest + 64, // 154: hyapp.room.v1.RoomCommandService.MicDown:input_type -> hyapp.room.v1.MicDownRequest + 66, // 155: hyapp.room.v1.RoomCommandService.ChangeMicSeat:input_type -> hyapp.room.v1.ChangeMicSeatRequest + 68, // 156: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:input_type -> hyapp.room.v1.ConfirmMicPublishingRequest + 70, // 157: hyapp.room.v1.RoomCommandService.SetMicMute:input_type -> hyapp.room.v1.SetMicMuteRequest + 72, // 158: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:input_type -> hyapp.room.v1.ApplyRTCEventRequest + 74, // 159: hyapp.room.v1.RoomCommandService.SetMicSeatLock:input_type -> hyapp.room.v1.SetMicSeatLockRequest + 76, // 160: hyapp.room.v1.RoomCommandService.SetChatEnabled:input_type -> hyapp.room.v1.SetChatEnabledRequest + 78, // 161: hyapp.room.v1.RoomCommandService.SetRoomPassword:input_type -> hyapp.room.v1.SetRoomPasswordRequest + 80, // 162: hyapp.room.v1.RoomCommandService.SetRoomAdmin:input_type -> hyapp.room.v1.SetRoomAdminRequest + 82, // 163: hyapp.room.v1.RoomCommandService.MuteUser:input_type -> hyapp.room.v1.MuteUserRequest + 84, // 164: hyapp.room.v1.RoomCommandService.KickUser:input_type -> hyapp.room.v1.KickUserRequest + 86, // 165: hyapp.room.v1.RoomCommandService.UnbanUser:input_type -> hyapp.room.v1.UnbanUserRequest + 88, // 166: hyapp.room.v1.RoomCommandService.SystemEvictUser:input_type -> hyapp.room.v1.SystemEvictUserRequest + 90, // 167: hyapp.room.v1.RoomCommandService.SendGift:input_type -> hyapp.room.v1.SendGiftRequest + 117, // 168: hyapp.room.v1.RoomCommandService.FollowRoom:input_type -> hyapp.room.v1.FollowRoomRequest + 119, // 169: hyapp.room.v1.RoomCommandService.UnfollowRoom:input_type -> hyapp.room.v1.UnfollowRoomRequest + 92, // 170: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:input_type -> hyapp.room.v1.CheckSpeakPermissionRequest + 94, // 171: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:input_type -> hyapp.room.v1.VerifyRoomPresenceRequest + 54, // 172: hyapp.room.v1.RoomQueryService.AdminListRooms:input_type -> hyapp.room.v1.AdminListRoomsRequest + 56, // 173: hyapp.room.v1.RoomQueryService.AdminGetRoom:input_type -> hyapp.room.v1.AdminGetRoomRequest + 14, // 174: hyapp.room.v1.RoomQueryService.AdminGetRoomTreasureConfig:input_type -> hyapp.room.v1.AdminGetRoomTreasureConfigRequest + 19, // 175: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:input_type -> hyapp.room.v1.AdminGetRoomSeatConfigRequest + 25, // 176: hyapp.room.v1.RoomQueryService.AdminListRoomPins:input_type -> hyapp.room.v1.AdminListRoomPinsRequest + 96, // 177: hyapp.room.v1.RoomQueryService.ListRooms:input_type -> hyapp.room.v1.ListRoomsRequest + 97, // 178: hyapp.room.v1.RoomQueryService.ListRoomFeeds:input_type -> hyapp.room.v1.ListRoomFeedsRequest + 98, // 179: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:input_type -> hyapp.room.v1.ListRoomGiftLeaderboardRequest + 104, // 180: hyapp.room.v1.RoomQueryService.GetMyRoom:input_type -> hyapp.room.v1.GetMyRoomRequest + 106, // 181: hyapp.room.v1.RoomQueryService.GetCurrentRoom:input_type -> hyapp.room.v1.GetCurrentRoomRequest + 108, // 182: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:input_type -> hyapp.room.v1.GetRoomSnapshotRequest + 36, // 183: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:input_type -> hyapp.room.v1.ListRoomBackgroundsRequest + 110, // 184: hyapp.room.v1.RoomQueryService.GetRoomTreasure:input_type -> hyapp.room.v1.GetRoomTreasureRequest + 112, // 185: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:input_type -> hyapp.room.v1.ListRoomOnlineUsersRequest + 115, // 186: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:input_type -> hyapp.room.v1.ListRoomBannedUsersRequest + 41, // 187: hyapp.room.v1.RoomCommandService.CreateRoom:output_type -> hyapp.room.v1.CreateRoomResponse + 43, // 188: hyapp.room.v1.RoomCommandService.UpdateRoomProfile:output_type -> hyapp.room.v1.UpdateRoomProfileResponse + 35, // 189: hyapp.room.v1.RoomCommandService.SaveRoomBackground:output_type -> hyapp.room.v1.SaveRoomBackgroundResponse + 39, // 190: hyapp.room.v1.RoomCommandService.SetRoomBackground:output_type -> hyapp.room.v1.SetRoomBackgroundResponse + 46, // 191: hyapp.room.v1.RoomCommandService.JoinRoom:output_type -> hyapp.room.v1.JoinRoomResponse + 48, // 192: hyapp.room.v1.RoomCommandService.RoomHeartbeat:output_type -> hyapp.room.v1.RoomHeartbeatResponse + 50, // 193: hyapp.room.v1.RoomCommandService.LeaveRoom:output_type -> hyapp.room.v1.LeaveRoomResponse + 52, // 194: hyapp.room.v1.RoomCommandService.CloseRoom:output_type -> hyapp.room.v1.CloseRoomResponse + 59, // 195: hyapp.room.v1.RoomCommandService.AdminUpdateRoom:output_type -> hyapp.room.v1.AdminUpdateRoomResponse + 61, // 196: hyapp.room.v1.RoomCommandService.AdminDeleteRoom:output_type -> hyapp.room.v1.AdminDeleteRoomResponse + 17, // 197: hyapp.room.v1.RoomCommandService.AdminUpdateRoomTreasureConfig:output_type -> hyapp.room.v1.AdminUpdateRoomTreasureConfigResponse + 22, // 198: hyapp.room.v1.RoomCommandService.AdminUpdateRoomSeatConfig:output_type -> hyapp.room.v1.AdminUpdateRoomSeatConfigResponse + 28, // 199: hyapp.room.v1.RoomCommandService.AdminCreateRoomPin:output_type -> hyapp.room.v1.AdminCreateRoomPinResponse + 30, // 200: hyapp.room.v1.RoomCommandService.AdminCancelRoomPin:output_type -> hyapp.room.v1.AdminCancelRoomPinResponse + 63, // 201: hyapp.room.v1.RoomCommandService.MicUp:output_type -> hyapp.room.v1.MicUpResponse + 65, // 202: hyapp.room.v1.RoomCommandService.MicDown:output_type -> hyapp.room.v1.MicDownResponse + 67, // 203: hyapp.room.v1.RoomCommandService.ChangeMicSeat:output_type -> hyapp.room.v1.ChangeMicSeatResponse + 69, // 204: hyapp.room.v1.RoomCommandService.ConfirmMicPublishing:output_type -> hyapp.room.v1.ConfirmMicPublishingResponse + 71, // 205: hyapp.room.v1.RoomCommandService.SetMicMute:output_type -> hyapp.room.v1.SetMicMuteResponse + 73, // 206: hyapp.room.v1.RoomCommandService.ApplyRTCEvent:output_type -> hyapp.room.v1.ApplyRTCEventResponse + 75, // 207: hyapp.room.v1.RoomCommandService.SetMicSeatLock:output_type -> hyapp.room.v1.SetMicSeatLockResponse + 77, // 208: hyapp.room.v1.RoomCommandService.SetChatEnabled:output_type -> hyapp.room.v1.SetChatEnabledResponse + 79, // 209: hyapp.room.v1.RoomCommandService.SetRoomPassword:output_type -> hyapp.room.v1.SetRoomPasswordResponse + 81, // 210: hyapp.room.v1.RoomCommandService.SetRoomAdmin:output_type -> hyapp.room.v1.SetRoomAdminResponse + 83, // 211: hyapp.room.v1.RoomCommandService.MuteUser:output_type -> hyapp.room.v1.MuteUserResponse + 85, // 212: hyapp.room.v1.RoomCommandService.KickUser:output_type -> hyapp.room.v1.KickUserResponse + 87, // 213: hyapp.room.v1.RoomCommandService.UnbanUser:output_type -> hyapp.room.v1.UnbanUserResponse + 89, // 214: hyapp.room.v1.RoomCommandService.SystemEvictUser:output_type -> hyapp.room.v1.SystemEvictUserResponse + 91, // 215: hyapp.room.v1.RoomCommandService.SendGift:output_type -> hyapp.room.v1.SendGiftResponse + 118, // 216: hyapp.room.v1.RoomCommandService.FollowRoom:output_type -> hyapp.room.v1.FollowRoomResponse + 120, // 217: hyapp.room.v1.RoomCommandService.UnfollowRoom:output_type -> hyapp.room.v1.UnfollowRoomResponse + 93, // 218: hyapp.room.v1.RoomGuardService.CheckSpeakPermission:output_type -> hyapp.room.v1.CheckSpeakPermissionResponse + 95, // 219: hyapp.room.v1.RoomGuardService.VerifyRoomPresence:output_type -> hyapp.room.v1.VerifyRoomPresenceResponse + 55, // 220: hyapp.room.v1.RoomQueryService.AdminListRooms:output_type -> hyapp.room.v1.AdminListRoomsResponse + 57, // 221: hyapp.room.v1.RoomQueryService.AdminGetRoom:output_type -> hyapp.room.v1.AdminGetRoomResponse + 15, // 222: hyapp.room.v1.RoomQueryService.AdminGetRoomTreasureConfig:output_type -> hyapp.room.v1.AdminGetRoomTreasureConfigResponse + 20, // 223: hyapp.room.v1.RoomQueryService.AdminGetRoomSeatConfig:output_type -> hyapp.room.v1.AdminGetRoomSeatConfigResponse + 26, // 224: hyapp.room.v1.RoomQueryService.AdminListRoomPins:output_type -> hyapp.room.v1.AdminListRoomPinsResponse + 101, // 225: hyapp.room.v1.RoomQueryService.ListRooms:output_type -> hyapp.room.v1.ListRoomsResponse + 101, // 226: hyapp.room.v1.RoomQueryService.ListRoomFeeds:output_type -> hyapp.room.v1.ListRoomsResponse + 103, // 227: hyapp.room.v1.RoomQueryService.ListRoomGiftLeaderboard:output_type -> hyapp.room.v1.ListRoomGiftLeaderboardResponse + 105, // 228: hyapp.room.v1.RoomQueryService.GetMyRoom:output_type -> hyapp.room.v1.GetMyRoomResponse + 107, // 229: hyapp.room.v1.RoomQueryService.GetCurrentRoom:output_type -> hyapp.room.v1.GetCurrentRoomResponse + 109, // 230: hyapp.room.v1.RoomQueryService.GetRoomSnapshot:output_type -> hyapp.room.v1.GetRoomSnapshotResponse + 37, // 231: hyapp.room.v1.RoomQueryService.ListRoomBackgrounds:output_type -> hyapp.room.v1.ListRoomBackgroundsResponse + 111, // 232: hyapp.room.v1.RoomQueryService.GetRoomTreasure:output_type -> hyapp.room.v1.GetRoomTreasureResponse + 113, // 233: hyapp.room.v1.RoomQueryService.ListRoomOnlineUsers:output_type -> hyapp.room.v1.ListRoomOnlineUsersResponse + 116, // 234: hyapp.room.v1.RoomQueryService.ListRoomBannedUsers:output_type -> hyapp.room.v1.ListRoomBannedUsersResponse + 187, // [187:235] is the sub-list for method output_type + 139, // [139:187] is the sub-list for method input_type + 139, // [139:139] is the sub-list for extension type_name + 139, // [139:139] is the sub-list for extension extendee + 0, // [0:139] is the sub-list for field type_name } func init() { file_proto_room_v1_room_proto_init() } diff --git a/api/proto/room/v1/room.proto b/api/proto/room/v1/room.proto index 69728d49..e5461030 100644 --- a/api/proto/room/v1/room.proto +++ b/api/proto/room/v1/room.proto @@ -96,6 +96,8 @@ message LuckyGiftDrawResult { int64 created_at_ms = 17; string wallet_transaction_id = 18; int64 coin_balance_after = 19; + // target_user_id 是本次幸运礼物抽奖对应的收礼用户;多目标送礼用它关联结果。 + int64 target_user_id = 20; } // RoomTreasureRewardItem 是后台配置给客户端展示的宝箱奖励候选项。 @@ -809,7 +811,10 @@ message SendGiftResponse { repeated RankItem gift_rank = 4; RoomSnapshot room = 5; RoomTreasureState treasure = 6; + // lucky_gift 是本次送礼的聚合幸运礼物结果;多目标时倍率和返奖金额按所有目标累加。 LuckyGiftDrawResult lucky_gift = 7; + // lucky_gifts 是每个收礼目标的独立抽奖结果;多目标客户端用 target_user_id 对齐明细表现。 + repeated LuckyGiftDrawResult lucky_gifts = 8; } // CheckSpeakPermissionRequest 让腾讯云 IM 发言回调或 gateway 在公屏前同步问房间业务态。 diff --git a/docs/用户装扮Flutter对接文档.md b/docs/用户装扮Flutter对接文档.md index cffbcc7c..4a5bfc15 100644 --- a/docs/用户装扮Flutter对接文档.md +++ b/docs/用户装扮Flutter对接文档.md @@ -320,6 +320,18 @@ GET /api/v1/users/room-display-profiles:batch?user_ids=42,43 "entitlement_id": "ent-frame", "expires_at_ms": 1999999999999 }, + "profile_card": { + "resource_id": 8104, + "resource_code": "profile_card_gold", + "resource_type": "profile_card", + "name": "Gold Profile Card", + "asset_url": "https://cdn.example/profile-card.png", + "preview_url": "", + "animation_url": "https://cdn.example/profile-card.mp4", + "metadata_json": "{\"profile_card_layout\":{\"source_width\":1136,\"source_height\":1680,\"content_top\":143,\"content_height\":1525}}", + "entitlement_id": "ent-profile-card", + "expires_at_ms": 1999999999999 + }, "vehicle": { "resource_id": 8102, "resource_code": "vehicle_gold", @@ -341,6 +353,7 @@ GET /api/v1/users/room-display-profiles:batch?user_ids=42,43 说明: - 房间、公屏、在线列表展示头像框用 `avatar_frame`。 +- 房间资料卡展示用 `profile_card`,从 `metadata_json.profile_card_layout` 解析 `content_top` 和 `content_height`。 - 入场座驾展示用 `vehicle`。 - 徽章条展示用 `badges`。 - 过期佩戴不会返回。 @@ -470,6 +483,5 @@ Flutter 处理: 2. 背包页调 `/api/v1/users/me/resources?resource_type=xxx`。 3. 点击佩戴调 `POST /api/v1/users/me/resources/{resource_id}/equip`,成功后刷新 `/appearance`。 4. 点击取消佩戴调 `DELETE /api/v1/users/me/resources/{resource_type}/unequip`,成功后刷新 `/appearance`。 -5. 房间、公屏、在线用户资料优先使用 `room-display-profiles:batch` 的 `avatar_frame`、`vehicle`、`badges`。 +5. 房间、公屏、在线用户资料优先使用 `room-display-profiles:batch` 的 `avatar_frame`、`profile_card`、`vehicle`、`badges`。 6. 收到 `room_user_joined` IM 时,只按 `entry_vehicle` 播放入场座驾。 - diff --git a/docs/语音房客户端接口流程.md b/docs/语音房客户端接口流程.md index c271f0b5..f8c35f11 100644 --- a/docs/语音房客户端接口流程.md +++ b/docs/语音房客户端接口流程.md @@ -376,6 +376,11 @@ Response `data`: "level": {}, "badges": [], "avatar_frame": {}, + "profile_card": { + "resource_id": 8104, + "animation_url": "https://cdn.example/profile-card.mp4", + "metadata_json": "{\"profile_card_layout\":{\"source_height\":1680,\"content_top\":143,\"content_height\":1525}}" + }, "vehicle": {}, "charm": 10 } @@ -386,7 +391,7 @@ Response `data`: Rules: - 该接口专门服务房间麦位、公屏和礼物动效展示;不要复用“我的页”或用户主页重接口。 -- 当前已接入用户基础资料,`vip/level/badges/avatar_frame/vehicle/charm` 保持稳定字段,后续接入对应 read model 后填充。 +- 当前已接入用户基础资料,`vip/level/badges/avatar_frame/profile_card/vehicle/charm` 保持稳定字段;资料卡高度从 `profile_card.metadata_json.profile_card_layout` 解析。 ### IM UserSig diff --git a/scripts/mysql/backfill_level_badge_metadata.sql b/scripts/mysql/backfill_level_badge_metadata.sql new file mode 100644 index 00000000..d5193e2d --- /dev/null +++ b/scripts/mysql/backfill_level_badge_metadata.sql @@ -0,0 +1,97 @@ +-- Backfill level badge resource metadata and user level display projections. +-- Run this on a MySQL account that can read hyapp_activity and update hyapp_wallet. +-- If the conflict SELECT returns rows, copy the shared resource into separate resources first. + +SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; + +DROP TEMPORARY TABLE IF EXISTS tmp_level_badge_resource_refs; + +CREATE TEMPORARY TABLE tmp_level_badge_resource_refs AS +SELECT + app_code, + track, + level, + CAST(JSON_UNQUOTE(JSON_EXTRACT(display_config_json, '$.long_badge_resource_id')) AS UNSIGNED) AS resource_id +FROM hyapp_activity.growth_level_rules +WHERE status = 'active' + AND COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(display_config_json, '$.long_badge_resource_id')) AS UNSIGNED), 0) > 0; + +SELECT + resource_id, + GROUP_CONCAT(DISTINCT track ORDER BY track) AS tracks, + COUNT(DISTINCT track) AS track_count +FROM tmp_level_badge_resource_refs +GROUP BY resource_id +HAVING COUNT(DISTINCT track) > 1; + +UPDATE hyapp_wallet.resources AS r +JOIN ( + SELECT app_code, resource_id, MIN(track) AS track, COUNT(DISTINCT track) AS track_count + FROM tmp_level_badge_resource_refs + GROUP BY app_code, resource_id +) AS refs + ON refs.app_code = r.app_code AND refs.resource_id = r.resource_id +SET r.metadata_json = JSON_SET( + COALESCE(r.metadata_json, JSON_OBJECT()), + '$.badge_form', 'strip', + '$.badge_kind', 'level', + '$.level_track', refs.track + ), + r.updated_at_ms = UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000 +WHERE refs.track_count = 1 + AND r.resource_type = 'badge'; + +UPDATE hyapp_activity.user_level_display_profiles AS p +LEFT JOIN ( + SELECT a.app_code, a.user_id, refs.level, refs.resource_id + FROM hyapp_activity.user_growth_level_accounts AS a + JOIN tmp_level_badge_resource_refs AS refs + ON refs.app_code = a.app_code AND refs.track = a.track AND refs.level <= a.current_level + LEFT JOIN tmp_level_badge_resource_refs AS higher + ON higher.app_code = a.app_code + AND higher.track = a.track + AND higher.level <= a.current_level + AND higher.level > refs.level + WHERE a.track = 'wealth' AND higher.resource_id IS NULL +) AS picked + ON picked.app_code = p.app_code AND picked.user_id = p.user_id +SET p.wealth_badge_resource_id = COALESCE(picked.resource_id, 0), + p.wealth_badge_source_level = COALESCE(picked.level, 0), + p.updated_at_ms = UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000; + +UPDATE hyapp_activity.user_level_display_profiles AS p +LEFT JOIN ( + SELECT a.app_code, a.user_id, refs.level, refs.resource_id + FROM hyapp_activity.user_growth_level_accounts AS a + JOIN tmp_level_badge_resource_refs AS refs + ON refs.app_code = a.app_code AND refs.track = a.track AND refs.level <= a.current_level + LEFT JOIN tmp_level_badge_resource_refs AS higher + ON higher.app_code = a.app_code + AND higher.track = a.track + AND higher.level <= a.current_level + AND higher.level > refs.level + WHERE a.track = 'game' AND higher.resource_id IS NULL +) AS picked + ON picked.app_code = p.app_code AND picked.user_id = p.user_id +SET p.game_badge_resource_id = COALESCE(picked.resource_id, 0), + p.game_badge_source_level = COALESCE(picked.level, 0), + p.updated_at_ms = UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000; + +UPDATE hyapp_activity.user_level_display_profiles AS p +LEFT JOIN ( + SELECT a.app_code, a.user_id, refs.level, refs.resource_id + FROM hyapp_activity.user_growth_level_accounts AS a + JOIN tmp_level_badge_resource_refs AS refs + ON refs.app_code = a.app_code AND refs.track = a.track AND refs.level <= a.current_level + LEFT JOIN tmp_level_badge_resource_refs AS higher + ON higher.app_code = a.app_code + AND higher.track = a.track + AND higher.level <= a.current_level + AND higher.level > refs.level + WHERE a.track = 'charm' AND higher.resource_id IS NULL +) AS picked + ON picked.app_code = p.app_code AND picked.user_id = p.user_id +SET p.charm_badge_resource_id = COALESCE(picked.resource_id, 0), + p.charm_badge_source_level = COALESCE(picked.level, 0), + p.updated_at_ms = UNIX_TIMESTAMP(CURRENT_TIMESTAMP(3)) * 1000; + diff --git a/server/admin/cmd/server/main.go b/server/admin/cmd/server/main.go index 0e3f4b5c..1d1f6aec 100644 --- a/server/admin/cmd/server/main.go +++ b/server/admin/cmd/server/main.go @@ -236,7 +236,7 @@ func main() { HostOrg: hostorgmodule.New(userclient.NewGRPC(userConn), walletclient.NewGRPC(walletConn), userDB, walletDB, sqlDB, auditHandler), HostSalarySettlement: hostsalarysettlementmodule.New(walletDB, userDB), Job: jobmodule.New(store, cfg, auditHandler), - LevelConfig: levelconfigmodule.New(activityclient.NewGRPC(activityConn), auditHandler), + LevelConfig: levelconfigmodule.New(activityclient.NewGRPC(activityConn), walletclient.NewGRPC(walletConn), auditHandler), LuckyGift: luckygiftmodule.New(activityclient.NewGRPC(activityConn), cfg.ActivityService.RequestTimeout, auditHandler), Menu: menumodule.New(store, auditHandler), Payment: paymentmodule.New(walletclient.NewGRPC(walletConn), auditHandler), diff --git a/server/admin/internal/modules/levelconfig/handler.go b/server/admin/internal/modules/levelconfig/handler.go index 656704e7..bbf4879e 100644 --- a/server/admin/internal/modules/levelconfig/handler.go +++ b/server/admin/internal/modules/levelconfig/handler.go @@ -10,10 +10,12 @@ import ( "hyapp-admin-server/internal/appctx" "hyapp-admin-server/internal/integration/activityclient" + "hyapp-admin-server/internal/integration/walletclient" "hyapp-admin-server/internal/middleware" "hyapp-admin-server/internal/modules/shared" "hyapp-admin-server/internal/response" activityv1 "hyapp.local/api/proto/activity/v1" + walletv1 "hyapp.local/api/proto/wallet/v1" "github.com/gin-gonic/gin" ) @@ -27,11 +29,12 @@ const ( type Handler struct { activity activityclient.Client + wallet walletclient.Client audit shared.OperationLogger } -func New(activity activityclient.Client, audit shared.OperationLogger) *Handler { - return &Handler{activity: activity, audit: audit} +func New(activity activityclient.Client, wallet walletclient.Client, audit shared.OperationLogger) *Handler { + return &Handler{activity: activity, wallet: wallet, audit: audit} } type levelConfigDTO struct { @@ -208,7 +211,7 @@ func (h *Handler) UpdateTrack(c *gin.Context) { // UpsertRule 配置某条轨道某一级的升级阈值、等级奖励资源组和每级长徽章素材。 func (h *Handler) UpsertRule(c *gin.Context) { track := normalizeTrack(c.Param("track")) - level, ok := parsePositiveInt32(c.Param("level")) + level, ok := parseNonNegativeInt32(c.Param("level")) if !ok || !validTrack(track) { response.BadRequest(c, "等级参数不正确") return @@ -233,8 +236,8 @@ func (h *Handler) UpsertRule(c *gin.Context) { if req.RequiredValue != nil { requiredValue = *req.RequiredValue } - if requiredValue <= 0 { - response.BadRequest(c, "升级所需数值必须大于 0") + if requiredValue < 0 { + response.BadRequest(c, "升级所需数值不能小于 0") return } name := current.GetName() @@ -279,6 +282,12 @@ func (h *Handler) UpsertRule(c *gin.Context) { response.BadRequest(c, "展示配置 JSON 不正确") return } + if badgeID := longBadgeResourceID(displayConfig); badgeID > 0 { + if err := h.validateLevelLongBadgeResource(c, track, badgeID); err != nil { + response.BadRequest(c, err.Error()) + return + } + } resp, err := h.activity.UpsertLevelRule(c.Request.Context(), &activityv1.UpsertLevelRuleRequest{ Meta: h.meta(c), Track: track, @@ -359,7 +368,7 @@ func (h *Handler) upsertTier(c *gin.Context, tierID int64) { if req.MaxLevel != nil { maxLevel = *req.MaxLevel } - if minLevel <= 0 || maxLevel < minLevel { + if minLevel < 0 || maxLevel < minLevel { response.BadRequest(c, "等级段范围不正确") return } @@ -453,6 +462,38 @@ func (h *Handler) loadConfig(c *gin.Context, track string, status string) (*acti }) } +func (h *Handler) validateLevelLongBadgeResource(c *gin.Context, track string, resourceID int64) error { + if resourceID <= 0 { + return nil + } + if h.wallet == nil { + return fmt.Errorf("资源服务不可用") + } + resp, err := h.wallet.GetResource(c.Request.Context(), &walletv1.GetResourceRequest{ + RequestId: middleware.CurrentRequestID(c), + AppCode: appctx.FromContext(c.Request.Context()), + ResourceId: resourceID, + }) + if err != nil { + return fmt.Errorf("等级徽章资源不存在") + } + resource := resp.GetResource() + if resource.GetResourceType() != "badge" { + return fmt.Errorf("等级长徽章必须选择徽章资源") + } + metadata := levelBadgeMetadataFromJSON(resource.GetMetadataJson()) + if metadata.BadgeForm != "strip" { + return fmt.Errorf("等级长徽章必须选择长徽章素材") + } + if metadata.BadgeKind != "level" { + return fmt.Errorf("等级长徽章必须选择等级徽章素材") + } + if metadata.LevelTrack != track { + return fmt.Errorf("等级长徽章所属等级不匹配") + } + return nil +} + func (h *Handler) currentTrack(c *gin.Context, track string) (trackDTO, bool, error) { config, err := h.loadConfig(c, track, "") if err != nil { @@ -669,9 +710,26 @@ func longBadgeResourceID(raw string) int64 { return 0 } -func parsePositiveInt32(raw string) (int32, bool) { +type levelBadgeMetadata struct { + BadgeForm string `json:"badge_form"` + BadgeKind string `json:"badge_kind"` + LevelTrack string `json:"level_track"` +} + +func levelBadgeMetadataFromJSON(raw string) levelBadgeMetadata { + payload := levelBadgeMetadata{} + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil { + return levelBadgeMetadata{} + } + payload.BadgeForm = strings.ToLower(strings.TrimSpace(payload.BadgeForm)) + payload.BadgeKind = strings.ToLower(strings.TrimSpace(payload.BadgeKind)) + payload.LevelTrack = normalizeTrack(payload.LevelTrack) + return payload +} + +func parseNonNegativeInt32(raw string) (int32, bool) { value, err := strconv.ParseInt(strings.TrimSpace(raw), 10, 32) - if err != nil || value <= 0 { + if err != nil || value < 0 { return 0, false } return int32(value), true diff --git a/server/admin/internal/modules/resource/dto.go b/server/admin/internal/modules/resource/dto.go index c1345505..0e8161cb 100644 --- a/server/admin/internal/modules/resource/dto.go +++ b/server/admin/internal/modules/resource/dto.go @@ -18,6 +18,8 @@ type resourceDTO struct { CoinPrice int64 `json:"coinPrice"` GiftPointAmount int64 `json:"giftPointAmount"` BadgeForm string `json:"badgeForm,omitempty"` + BadgeKind string `json:"badgeKind,omitempty"` + LevelTrack string `json:"levelTrack,omitempty"` UsageScopes []string `json:"usageScopes"` AssetURL string `json:"assetUrl"` PreviewURL string `json:"previewUrl"` @@ -198,6 +200,8 @@ func resourceFromProto(item *walletv1.Resource) resourceDTO { CoinPrice: item.GetCoinPrice(), GiftPointAmount: item.GetGiftPointAmount(), BadgeForm: badgeFormForResource(item.GetResourceType(), item.GetMetadataJson()), + BadgeKind: badgeKindForResource(item.GetResourceType(), item.GetMetadataJson()), + LevelTrack: badgeLevelTrackForResource(item.GetResourceType(), item.GetMetadataJson()), UsageScopes: item.GetUsageScopes(), AssetURL: item.GetAssetUrl(), PreviewURL: item.GetPreviewUrl(), diff --git a/server/admin/internal/modules/resource/operator_test.go b/server/admin/internal/modules/resource/operator_test.go index 21c20fe0..a60a2c9b 100644 --- a/server/admin/internal/modules/resource/operator_test.go +++ b/server/admin/internal/modules/resource/operator_test.go @@ -2,6 +2,7 @@ package resource import ( "encoding/json" + "strings" "testing" ) @@ -92,19 +93,78 @@ func TestEmojiPackMetadataKeepsLimitedRegions(t *testing.T) { } func TestBadgeResourceMetadataUsesSelectedForm(t *testing.T) { - metadata := resourceMetadataJSON(resourceTypeBadge, "long") + metadata := resourceMetadataJSON(resourceTypeBadge, "long", "", "", "") payload := badgeMetadataPayload{} if err := json.Unmarshal([]byte(metadata), &payload); err != nil { t.Fatalf("badge metadata json mismatch: %v", err) } - if payload.BadgeForm != badgeFormStrip || payload.DefaultSlot != "profile_strip" { + if payload.BadgeForm != badgeFormStrip || payload.BadgeKind != badgeKindNormal || payload.DefaultSlot != "profile_strip" { t.Fatalf("long badge metadata mismatch: %+v", payload) } - if got := badgeFormFromMetadata(resourceMetadataJSON(resourceTypeBadge, "short")); got != badgeFormTile { + if got := badgeFormFromMetadata(resourceMetadataJSON(resourceTypeBadge, "short", "", "", "")); got != badgeFormTile { t.Fatalf("short badge form mismatch: %s", got) } } +func TestBadgeResourceMetadataMarksLevelBadge(t *testing.T) { + metadata := resourceMetadataJSON(resourceTypeBadge, badgeFormStrip, "", badgeKindLevel, "wealth") + payload := badgeMetadataPayload{} + if err := json.Unmarshal([]byte(metadata), &payload); err != nil { + t.Fatalf("badge metadata json mismatch: %v", err) + } + if payload.BadgeKind != badgeKindLevel || payload.LevelTrack != "wealth" || payload.BadgeForm != badgeFormStrip { + t.Fatalf("level badge metadata mismatch: %+v", payload) + } +} + +func TestProfileCardResourceMetadataKeepsLayoutOnly(t *testing.T) { + metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{ + "profile_card_layout": { + "source_width": 1136, + "source_height": 1680, + "color_content_width": 750, + "content_top": 117, + "content_bottom": 1666, + "content_height": 1550, + "content_top_ratio": 0, + "content_height_ratio": 0, + "detect_version": 99 + }, + "debug": true + }`, "", "") + payload := profileCardMetadataPayload{} + if err := json.Unmarshal([]byte(metadata), &payload); err != nil { + t.Fatalf("profile card metadata json mismatch: %v", err) + } + if payload.ProfileCardLayout == nil { + t.Fatalf("profile card layout should be kept") + } + if payload.ProfileCardLayout.ContentTop != 117 || payload.ProfileCardLayout.ContentHeight != 1550 { + t.Fatalf("profile card layout mismatch: %+v", payload.ProfileCardLayout) + } + if payload.ProfileCardLayout.DetectVersion != 1 { + t.Fatalf("profile card detect version should be normalized: %+v", payload.ProfileCardLayout) + } + if metadata == "{}" || strings.Contains(metadata, "debug") { + t.Fatalf("profile card metadata should keep only layout: %s", metadata) + } +} + +func TestProfileCardResourceMetadataRejectsInvalidLayout(t *testing.T) { + metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{ + "profile_card_layout": { + "source_width": 1136, + "source_height": 1680, + "content_top": 117, + "content_bottom": 1666, + "content_height": 1 + } + }`, "", "") + if metadata != "{}" { + t.Fatalf("invalid profile card layout should be dropped: %s", metadata) + } +} + func TestValidateBadgeResourceRequiresBadgeForm(t *testing.T) { if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge}); err == nil { t.Fatalf("badge form should be required") @@ -112,6 +172,12 @@ func TestValidateBadgeResourceRequiresBadgeForm(t *testing.T) { if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge, BadgeForm: badgeFormTile}); err != nil { t.Fatalf("valid badge form rejected: %v", err) } + if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge, BadgeForm: badgeFormStrip, BadgeKind: badgeKindLevel}); err == nil { + t.Fatalf("level badge should require level track") + } + if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge, BadgeForm: badgeFormStrip, BadgeKind: badgeKindLevel, LevelTrack: "wealth"}); err != nil { + t.Fatalf("valid level badge rejected: %v", err) + } if err := validateResourceBadgeForm(resourceRequest{ResourceType: "gift", BadgeForm: ""}); err != nil { t.Fatalf("non-badge resource should not require badge form: %v", err) } diff --git a/server/admin/internal/modules/resource/request.go b/server/admin/internal/modules/resource/request.go index e332a656..81426397 100644 --- a/server/admin/internal/modules/resource/request.go +++ b/server/admin/internal/modules/resource/request.go @@ -18,9 +18,10 @@ import ( const dayMillis int64 = 24 * 60 * 60 * 1000 const ( - resourceTypeCoin = "coin" - resourceTypeBadge = "badge" - resourceTypeEmojiPack = "emoji_pack" + resourceTypeCoin = "coin" + resourceTypeBadge = "badge" + resourceTypeEmojiPack = "emoji_pack" + resourceTypeProfileCard = "profile_card" ) const ( @@ -33,6 +34,11 @@ const ( badgeFormTile = "tile" ) +const ( + badgeKindNormal = "normal" + badgeKindLevel = "level" +) + const ( defaultEmojiPackCategory = "默认" emojiPackPricingFree = "free" @@ -49,6 +55,9 @@ type resourceRequest struct { CoinPrice int64 `json:"coinPrice"` GiftPointAmount int64 `json:"giftPointAmount"` BadgeForm string `json:"badgeForm"` + BadgeKind string `json:"badgeKind"` + LevelTrack string `json:"levelTrack"` + MetadataJSON string `json:"metadataJson"` ManagerGrantEnabled *bool `json:"managerGrantEnabled"` AssetURL string `json:"assetUrl"` PreviewURL string `json:"previewUrl"` @@ -75,9 +84,27 @@ type emojiPackMetadataPayload struct { type badgeMetadataPayload struct { BadgeForm string `json:"badge_form"` + BadgeKind string `json:"badge_kind,omitempty"` + LevelTrack string `json:"level_track,omitempty"` DefaultSlot string `json:"default_slot,omitempty"` } +type profileCardMetadataPayload struct { + ProfileCardLayout *profileCardLayoutMetadataPayload `json:"profile_card_layout,omitempty"` +} + +type profileCardLayoutMetadataPayload struct { + SourceWidth int64 `json:"source_width"` + SourceHeight int64 `json:"source_height"` + ColorContentWidth int64 `json:"color_content_width"` + ContentTop int64 `json:"content_top"` + ContentBottom int64 `json:"content_bottom"` + ContentHeight int64 `json:"content_height"` + ContentTopRatio float64 `json:"content_top_ratio"` + ContentHeightRatio float64 `json:"content_height_ratio"` + DetectVersion int64 `json:"detect_version"` +} + type resourceGroupRequest struct { GroupCode string `json:"groupCode"` Name string `json:"name"` @@ -169,7 +196,7 @@ func (r resourceRequest) createProto(c *gin.Context) *walletv1.CreateResourceReq resourceType := normalizeResourceType(r.ResourceType) walletAssetType, walletAssetAmount := resourceWalletAsset(resourceType, r.Amount) priceType, coinPrice, giftPointAmount := resourcePricing(r.PriceType, r.CoinPrice) - metadataJSON := resourceMetadataJSON(resourceType, r.BadgeForm) + metadataJSON := resourceMetadataJSON(resourceType, r.BadgeForm, r.MetadataJSON, r.BadgeKind, r.LevelTrack) return &walletv1.CreateResourceRequest{ RequestId: middleware.CurrentRequestID(c), AppCode: appctx.FromContext(c.Request.Context()), @@ -238,7 +265,7 @@ func (r resourceRequest) updateProto(c *gin.Context, resourceID int64) *walletv1 resourceType := normalizeResourceType(r.ResourceType) walletAssetType, walletAssetAmount := resourceWalletAsset(resourceType, r.Amount) priceType, coinPrice, giftPointAmount := resourcePricing(r.PriceType, r.CoinPrice) - metadataJSON := resourceMetadataJSON(resourceType, r.BadgeForm) + metadataJSON := resourceMetadataJSON(resourceType, r.BadgeForm, r.MetadataJSON, r.BadgeKind, r.LevelTrack) return &walletv1.UpdateResourceRequest{ RequestId: middleware.CurrentRequestID(c), AppCode: appctx.FromContext(c.Request.Context()), @@ -500,6 +527,13 @@ func validateResourceBadgeForm(req resourceRequest) error { if normalizeBadgeForm(req.BadgeForm) == "" { return fmt.Errorf("请选择徽章属性") } + badgeKind := normalizeBadgeKind(req.BadgeKind) + if badgeKind == "" { + return fmt.Errorf("请选择徽章类型") + } + if badgeKind == badgeKindLevel && !validLevelTrack(req.LevelTrack) { + return fmt.Errorf("请选择等级徽章所属等级") + } return nil } @@ -516,11 +550,19 @@ func resourceGrantStrategy(resourceType string) string { } } -func resourceMetadataJSON(resourceType string, badgeForm string) string { - if resourceType != resourceTypeBadge { +func resourceMetadataJSON(resourceType string, badgeForm string, metadataJSON string, badgeKind string, levelTrack string) string { + switch resourceType { + case resourceTypeBadge: + return badgeMetadataJSON(badgeForm, badgeKind, levelTrack) + case resourceTypeProfileCard: + return profileCardMetadataJSON(metadataJSON) + default: return "{}" } - payload := badgeMetadataPayload{BadgeForm: normalizeBadgeForm(badgeForm)} +} + +func badgeMetadataJSON(badgeForm string, badgeKind string, levelTrack string) string { + payload := badgeMetadataPayload{BadgeForm: normalizeBadgeForm(badgeForm), BadgeKind: normalizeBadgeKind(badgeKind)} switch payload.BadgeForm { case badgeFormStrip: payload.DefaultSlot = "profile_strip" @@ -529,6 +571,12 @@ func resourceMetadataJSON(resourceType string, badgeForm string) string { default: return "{}" } + if payload.BadgeKind == badgeKindLevel { + payload.LevelTrack = normalizeLevelTrack(levelTrack) + if payload.LevelTrack == "" { + return "{}" + } + } body, err := json.Marshal(payload) if err != nil { return "{}" @@ -536,6 +584,66 @@ func resourceMetadataJSON(resourceType string, badgeForm string) string { return string(body) } +func profileCardMetadataJSON(metadataJSON string) string { + raw := strings.TrimSpace(metadataJSON) + if raw == "" { + return "{}" + } + payload := profileCardMetadataPayload{} + if err := json.Unmarshal([]byte(raw), &payload); err != nil { + return "{}" + } + layout := sanitizeProfileCardLayoutMetadata(payload.ProfileCardLayout) + if layout == nil { + return "{}" + } + // 资料卡上传解析只允许写入布局字段;不要把后台临时调试数据透给 app。 + body, err := json.Marshal(profileCardMetadataPayload{ProfileCardLayout: layout}) + if err != nil { + return "{}" + } + return string(body) +} + +func sanitizeProfileCardLayoutMetadata(layout *profileCardLayoutMetadataPayload) *profileCardLayoutMetadataPayload { + if layout == nil { + return nil + } + if layout.SourceWidth <= 0 || layout.SourceHeight <= 0 { + return nil + } + if layout.ContentTop < 0 || layout.ContentBottom < layout.ContentTop || layout.ContentBottom >= layout.SourceHeight { + return nil + } + contentHeight := layout.ContentBottom - layout.ContentTop + 1 + if layout.ContentHeight != contentHeight { + return nil + } + colorContentWidth := layout.ColorContentWidth + if colorContentWidth <= 0 || colorContentWidth > layout.SourceWidth { + colorContentWidth = layout.SourceWidth + } + // 比例字段由整数边界重新计算,避免前端浮点误差或脏数据进入客户端展示协议。 + return &profileCardLayoutMetadataPayload{ + SourceWidth: layout.SourceWidth, + SourceHeight: layout.SourceHeight, + ColorContentWidth: colorContentWidth, + ContentTop: layout.ContentTop, + ContentBottom: layout.ContentBottom, + ContentHeight: contentHeight, + ContentTopRatio: profileCardRatio(layout.ContentTop, layout.SourceHeight), + ContentHeightRatio: profileCardRatio(contentHeight, layout.SourceHeight), + DetectVersion: 1, + } +} + +func profileCardRatio(value int64, total int64) float64 { + if total <= 0 { + return 0 + } + return float64(value) / float64(total) +} + func normalizeBadgeForm(value string) string { switch strings.ToLower(strings.TrimSpace(value)) { case badgeFormStrip, "long": @@ -547,6 +655,34 @@ func normalizeBadgeForm(value string) string { } } +func normalizeBadgeKind(value string) string { + switch strings.ToLower(strings.TrimSpace(value)) { + case "", badgeKindNormal: + return badgeKindNormal + case badgeKindLevel: + return badgeKindLevel + default: + return "" + } +} + +func normalizeLevelTrack(value string) string { + value = strings.ToLower(strings.TrimSpace(value)) + if validLevelTrack(value) { + return value + } + return "" +} + +func validLevelTrack(value string) bool { + switch strings.ToLower(strings.TrimSpace(value)) { + case "wealth", "game", "charm": + return true + default: + return false + } +} + func badgeFormFromMetadata(metadataJSON string) string { payload := badgeMetadataPayload{} if err := json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &payload); err != nil { @@ -555,6 +691,22 @@ func badgeFormFromMetadata(metadataJSON string) string { return normalizeBadgeForm(payload.BadgeForm) } +func badgeKindFromMetadata(metadataJSON string) string { + payload := badgeMetadataPayload{} + if err := json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &payload); err != nil { + return badgeKindNormal + } + return normalizeBadgeKind(payload.BadgeKind) +} + +func badgeLevelTrackFromMetadata(metadataJSON string) string { + payload := badgeMetadataPayload{} + if err := json.Unmarshal([]byte(strings.TrimSpace(metadataJSON)), &payload); err != nil { + return "" + } + return normalizeLevelTrack(payload.LevelTrack) +} + func badgeFormForResource(resourceType string, metadataJSON string) string { if normalizeResourceType(resourceType) != resourceTypeBadge { return "" @@ -562,6 +714,24 @@ func badgeFormForResource(resourceType string, metadataJSON string) string { return badgeFormFromMetadata(metadataJSON) } +func badgeKindForResource(resourceType string, metadataJSON string) string { + if normalizeResourceType(resourceType) != resourceTypeBadge { + return "" + } + kind := badgeKindFromMetadata(metadataJSON) + if kind == "" { + return badgeKindNormal + } + return kind +} + +func badgeLevelTrackForResource(resourceType string, metadataJSON string) string { + if normalizeResourceType(resourceType) != resourceTypeBadge { + return "" + } + return badgeLevelTrackFromMetadata(metadataJSON) +} + func emojiPackMetadataJSON(regionIDs []int64, category string, pricingType string) (string, error) { normalizedRegionIDs := normalizeRegionIDs(regionIDs) metadata := emojiPackMetadataPayload{ diff --git a/services/activity-service/configs/config.docker.yaml b/services/activity-service/configs/config.docker.yaml index 8db07f36..3ee8a7b2 100644 --- a/services/activity-service/configs/config.docker.yaml +++ b/services/activity-service/configs/config.docker.yaml @@ -60,6 +60,7 @@ rocketmq: wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" + realtime_topic: "hyapp_wallet_realtime_outbox" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/configs/config.tencent.example.yaml b/services/activity-service/configs/config.tencent.example.yaml index 1687a4b2..01fe3f8d 100644 --- a/services/activity-service/configs/config.tencent.example.yaml +++ b/services/activity-service/configs/config.tencent.example.yaml @@ -59,6 +59,7 @@ rocketmq: wallet_outbox: enabled: true topic: "hyapp_wallet_outbox" + realtime_topic: "hyapp_wallet_realtime_outbox" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/configs/config.yaml b/services/activity-service/configs/config.yaml index fdf50deb..a807dab7 100644 --- a/services/activity-service/configs/config.yaml +++ b/services/activity-service/configs/config.yaml @@ -59,6 +59,7 @@ rocketmq: wallet_outbox: enabled: false topic: "hyapp_wallet_outbox" + realtime_topic: "" first_recharge_consumer_group: "hyapp-activity-first-recharge-wallet-outbox" red_packet_broadcast_consumer_group: "hyapp-activity-red-packet-wallet-outbox" consumer_max_reconsume_times: 16 diff --git a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql index 8ed8b138..9f6f03a5 100644 --- a/services/activity-service/deploy/mysql/initdb/001_activity_service.sql +++ b/services/activity-service/deploy/mysql/initdb/001_activity_service.sql @@ -716,18 +716,48 @@ CREATE TABLE IF NOT EXISTS user_level_display_profiles ( wealth_tier_id BIGINT NOT NULL DEFAULT 0 COMMENT '财富档位 ID', wealth_avatar_frame_resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '财富头像框资源 ID', wealth_badge_resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '财富徽章资源 ID', + wealth_badge_source_level INT NOT NULL DEFAULT 0 COMMENT '财富徽章来源等级', game_level INT NOT NULL DEFAULT 0 COMMENT '游戏等级', game_tier_id BIGINT NOT NULL DEFAULT 0 COMMENT '游戏档位 ID', game_avatar_frame_resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '游戏头像框资源 ID', game_badge_resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '游戏徽章资源 ID', + game_badge_source_level INT NOT NULL DEFAULT 0 COMMENT '游戏徽章来源等级', charm_level INT NOT NULL DEFAULT 0 COMMENT '魅力等级', charm_tier_id BIGINT NOT NULL DEFAULT 0 COMMENT '魅力档位 ID', charm_avatar_frame_resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '魅力头像框资源 ID', charm_badge_resource_id BIGINT NOT NULL DEFAULT 0 COMMENT '魅力徽章资源 ID', + charm_badge_source_level INT NOT NULL DEFAULT 0 COMMENT '魅力徽章来源等级', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', PRIMARY KEY (app_code, user_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户等级展示资料表'; +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_level_display_profiles' AND COLUMN_NAME = 'wealth_badge_source_level') = 0, + 'ALTER TABLE user_level_display_profiles ADD COLUMN wealth_badge_source_level INT NOT NULL DEFAULT 0 COMMENT ''财富徽章来源等级'' AFTER wealth_badge_resource_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_level_display_profiles' AND COLUMN_NAME = 'game_badge_source_level') = 0, + 'ALTER TABLE user_level_display_profiles ADD COLUMN game_badge_source_level INT NOT NULL DEFAULT 0 COMMENT ''游戏徽章来源等级'' AFTER game_badge_resource_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'user_level_display_profiles' AND COLUMN_NAME = 'charm_badge_source_level') = 0, + 'ALTER TABLE user_level_display_profiles ADD COLUMN charm_badge_source_level INT NOT NULL DEFAULT 0 COMMENT ''魅力徽章来源等级'' AFTER charm_badge_resource_id', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + INSERT INTO growth_level_tracks (app_code, track, name, status, sort_order, display_config_json, created_at_ms, updated_at_ms) VALUES ('lalu', 'wealth', '财富等级', 'active', 10, JSON_OBJECT(), 0, 0), diff --git a/services/activity-service/internal/app/app.go b/services/activity-service/internal/app/app.go index aa3b86b1..beed293a 100644 --- a/services/activity-service/internal/app/app.go +++ b/services/activity-service/internal/app/app.go @@ -232,7 +232,7 @@ func New(cfg config.Config) (*App, error) { _ = repository.Close() return nil, err } - if err := consumer.Subscribe(cfg.RocketMQ.WalletOutbox.Topic, walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { + if err := consumer.Subscribe(redPacketWalletOutboxTopic(cfg.RocketMQ.WalletOutbox), walletmq.TagWalletOutboxEvent, func(ctx context.Context, message rocketmqx.ConsumedMessage) error { event, ok, err := redPacketEventFromWalletMessage(message.Body) if err != nil || !ok { return err @@ -389,6 +389,14 @@ func walletOutboxConsumerConfig(cfg config.RocketMQConfig, group string) rocketm return rocketMQConsumerConfig(cfg, group, cfg.WalletOutbox.ConsumerMaxReconsumeTimes) } +func redPacketWalletOutboxTopic(cfg config.WalletOutboxMQConfig) string { + // 钱包实时 topic 显式配置后,红包 worker 只消费实时通道,避免继续被普通账务 topic 的高频消息拖慢。 + if cfg.RealtimeTopic != "" { + return cfg.RealtimeTopic + } + return cfg.Topic +} + func rocketMQConsumerConfig(cfg config.RocketMQConfig, group string, maxReconsume int32) rocketmqx.ConsumerConfig { return rocketmqx.ConsumerConfig{ EndpointConfig: rocketmqx.EndpointConfig{ diff --git a/services/activity-service/internal/config/config.go b/services/activity-service/internal/config/config.go index ab3cef1a..86e2c266 100644 --- a/services/activity-service/internal/config/config.go +++ b/services/activity-service/internal/config/config.go @@ -145,6 +145,7 @@ type RoomOutboxMQConfig struct { type WalletOutboxMQConfig struct { Enabled bool `yaml:"enabled"` Topic string `yaml:"topic"` + RealtimeTopic string `yaml:"realtime_topic"` FirstRechargeConsumerGroup string `yaml:"first_recharge_consumer_group"` RedPacketBroadcastConsumerGroup string `yaml:"red_packet_broadcast_consumer_group"` ConsumerMaxReconsumeTimes int32 `yaml:"consumer_max_reconsume_times"` @@ -222,6 +223,7 @@ func defaultRocketMQConfig() RocketMQConfig { WalletOutbox: WalletOutboxMQConfig{ Enabled: false, Topic: "hyapp_wallet_outbox", + RealtimeTopic: "", FirstRechargeConsumerGroup: "hyapp-activity-first-recharge-wallet-outbox", RedPacketBroadcastConsumerGroup: "hyapp-activity-red-packet-wallet-outbox", ConsumerMaxReconsumeTimes: 16, @@ -351,6 +353,7 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.WalletOutbox.Topic = strings.TrimSpace(cfg.WalletOutbox.Topic); cfg.WalletOutbox.Topic == "" { cfg.WalletOutbox.Topic = defaults.WalletOutbox.Topic } + cfg.WalletOutbox.RealtimeTopic = strings.TrimSpace(cfg.WalletOutbox.RealtimeTopic) if cfg.WalletOutbox.FirstRechargeConsumerGroup = strings.TrimSpace(cfg.WalletOutbox.FirstRechargeConsumerGroup); cfg.WalletOutbox.FirstRechargeConsumerGroup == "" { cfg.WalletOutbox.FirstRechargeConsumerGroup = defaults.WalletOutbox.FirstRechargeConsumerGroup } diff --git a/services/activity-service/internal/config/config_test.go b/services/activity-service/internal/config/config_test.go index 69191880..2ff151f0 100644 --- a/services/activity-service/internal/config/config_test.go +++ b/services/activity-service/internal/config/config_test.go @@ -20,4 +20,7 @@ func TestLoadTencentExampleEnablesRoomOutboxMQ(t *testing.T) { if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.RoomOutbox.Enabled || cfg.RocketMQ.RoomOutbox.Topic == "" || cfg.RocketMQ.RoomOutbox.ConsumerGroup == "" { t.Fatalf("tencent example must configure room outbox MQ consumer: %+v", cfg.RocketMQ) } + if cfg.RocketMQ.WalletOutbox.RealtimeTopic != "hyapp_wallet_realtime_outbox" { + t.Fatalf("red packet worker should consume realtime wallet topic in tencent example: %+v", cfg.RocketMQ.WalletOutbox) + } } diff --git a/services/activity-service/internal/domain/growth/growth.go b/services/activity-service/internal/domain/growth/growth.go index 9f284526..faaf3948 100644 --- a/services/activity-service/internal/domain/growth/growth.go +++ b/services/activity-service/internal/domain/growth/growth.go @@ -100,10 +100,49 @@ type TrackOverview struct { NextLevelRequiredValue int64 DisplayAvatarFrameResourceID int64 DisplayBadgeResourceID int64 + DisplayBadgeSourceLevel int32 RewardPendingCount int64 SortOrder int32 } +// LevelDisplayTrackProfile 是房间资料、资料卡这类轻量展示面读取的单轨道投影。 +type LevelDisplayTrackProfile struct { + Track string + Level int32 + TierID int64 + AvatarFrameResourceID int64 + BadgeResourceID int64 + BadgeSourceLevel int32 + UpdatedAtMS int64 +} + +// UserLevelDisplayProfile 固定携带 wealth/game/charm 三条轨道,避免展示层按数组顺序猜含义。 +type UserLevelDisplayProfile struct { + UserID int64 + Wealth LevelDisplayTrackProfile + Game LevelDisplayTrackProfile + Charm LevelDisplayTrackProfile +} + +// LevelDisplayProfiles 是批量用户展示投影的只读快照。 +type LevelDisplayProfiles struct { + Profiles []UserLevelDisplayProfile + ServerTimeMS int64 +} + +// RegistrationLevelBadgeGrant 是注册后按等级规则补发的 1 级等级徽章发放结果。 +type RegistrationLevelBadgeGrant struct { + Track string + Level int32 + ResourceID int64 + GrantID string +} + +type RegistrationLevelBadgeGrants struct { + Grants []RegistrationLevelBadgeGrant + ServerTimeMS int64 +} + // TrackDetail 返回一个轨道的完整阈值、等级段和当前用户进度。 type TrackDetail struct { Overview TrackOverview diff --git a/services/activity-service/internal/service/growth/service.go b/services/activity-service/internal/service/growth/service.go index 0be0d99a..dc0665f1 100644 --- a/services/activity-service/internal/service/growth/service.go +++ b/services/activity-service/internal/service/growth/service.go @@ -3,6 +3,8 @@ package growth import ( "context" "encoding/json" + "fmt" + "strconv" "strings" "time" @@ -17,10 +19,16 @@ import ( const levelRewardReason = "growth_level_reward" +const ( + registrationLevelBadgeReason = "growth_level_registration_badge" + registrationLevelBadgeSourceLevel = int32(1) +) + // Repository 是等级体系的 activity MySQL 持久化边界。 type Repository interface { ListLevelOverview(ctx context.Context, userID int64, nowMS int64) (domain.Overview, error) GetLevelTrack(ctx context.Context, userID int64, track string, nowMS int64) (domain.TrackDetail, error) + BatchGetUserLevelDisplayProfiles(ctx context.Context, userIDs []int64, nowMS int64) (domain.LevelDisplayProfiles, error) ListLevelConfig(ctx context.Context, query domain.ConfigQuery, nowMS int64) (domain.Config, error) ListLevelRewards(ctx context.Context, query domain.RewardQuery) ([]domain.RewardJob, int64, error) ConsumeLevelEvent(ctx context.Context, event domain.ValueEvent, nowMS int64) (domain.EventResult, error) @@ -34,6 +42,7 @@ type Repository interface { // WalletClient 是等级奖励发放的唯一外部副作用。 type WalletClient interface { + GrantResource(ctx context.Context, req *walletv1.GrantResourceRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) GrantResourceGroup(ctx context.Context, req *walletv1.GrantResourceGroupRequest, opts ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) } @@ -75,6 +84,76 @@ func (s *Service) GetLevelTrack(ctx context.Context, userID int64, track string) return s.repository.GetLevelTrack(ctx, userID, track, s.now().UnixMilli()) } +func (s *Service) BatchGetUserLevelDisplayProfiles(ctx context.Context, userIDs []int64) (domain.LevelDisplayProfiles, error) { + if err := s.requireRepository(); err != nil { + return domain.LevelDisplayProfiles{}, err + } + normalized := make([]int64, 0, len(userIDs)) + seen := make(map[int64]struct{}, len(userIDs)) + for _, userID := range userIDs { + if userID <= 0 { + continue + } + if _, exists := seen[userID]; exists { + continue + } + seen[userID] = struct{}{} + normalized = append(normalized, userID) + } + if len(normalized) == 0 { + return domain.LevelDisplayProfiles{Profiles: []domain.UserLevelDisplayProfile{}, ServerTimeMS: s.now().UnixMilli()}, nil + } + return s.repository.BatchGetUserLevelDisplayProfiles(ctx, normalized, s.now().UnixMilli()) +} + +func (s *Service) IssueRegistrationLevelBadges(ctx context.Context, userID int64, commandID string) (domain.RegistrationLevelBadgeGrants, error) { + if err := s.requireRepository(); err != nil { + return domain.RegistrationLevelBadgeGrants{}, err + } + if s.wallet == nil { + return domain.RegistrationLevelBadgeGrants{}, xerr.New(xerr.Unavailable, "wallet client is not configured") + } + commandID = strings.TrimSpace(commandID) + if userID <= 0 || commandID == "" { + return domain.RegistrationLevelBadgeGrants{}, xerr.New(xerr.InvalidArgument, "registration level badge command is incomplete") + } + nowMS := s.now().UnixMilli() + config, err := s.repository.ListLevelConfig(ctx, domain.ConfigQuery{Status: domain.StatusActive}, nowMS) + if err != nil { + return domain.RegistrationLevelBadgeGrants{}, err + } + grants := make([]domain.RegistrationLevelBadgeGrant, 0, 3) + for _, track := range []string{domain.TrackWealth, domain.TrackGame, domain.TrackCharm} { + resourceID := levelRuleBadgeForExactLevel(config.Rules, track, registrationLevelBadgeSourceLevel) + if resourceID <= 0 { + // 没有配置 1 级等级徽章时跳过该轨道;注册主流程和其它轨道不受影响。 + continue + } + // wallet 的 command_id 是实际发放幂等边界;按轨道拆分后,部分成功的重试只会补齐失败轨道。 + resp, err := s.wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{ + CommandId: fmt.Sprintf("%s:%s", commandID, track), + AppCode: appcode.FromContext(ctx), + TargetUserId: userID, + ResourceId: resourceID, + Quantity: 1, + DurationMs: 0, + Reason: registrationLevelBadgeReason, + OperatorUserId: userID, + GrantSource: domain.GrantSourceGrowthLevel, + }) + if err != nil { + return domain.RegistrationLevelBadgeGrants{}, err + } + grants = append(grants, domain.RegistrationLevelBadgeGrant{ + Track: track, + Level: registrationLevelBadgeSourceLevel, + ResourceID: resourceID, + GrantID: resp.GetGrant().GetGrantId(), + }) + } + return domain.RegistrationLevelBadgeGrants{Grants: grants, ServerTimeMS: nowMS}, nil +} + func (s *Service) ListLevelConfig(ctx context.Context, query domain.ConfigQuery) (domain.Config, error) { if err := s.requireRepository(); err != nil { return domain.Config{}, err @@ -93,6 +172,16 @@ func (s *Service) ListLevelConfig(ctx context.Context, query domain.ConfigQuery) return s.repository.ListLevelConfig(ctx, query, s.now().UnixMilli()) } +func levelRuleBadgeForExactLevel(rules []domain.Rule, track string, level int32) int64 { + for _, rule := range rules { + if rule.Track != track || rule.Level != level || rule.Status != domain.StatusActive { + continue + } + return longBadgeResourceIDFromDisplayConfig(rule.DisplayConfigJSON) + } + return 0 +} + func (s *Service) ListLevelRewards(ctx context.Context, query domain.RewardQuery) ([]domain.RewardJob, int64, error) { if err := s.requireRepository(); err != nil { return nil, 0, err @@ -212,7 +301,7 @@ func (s *Service) UpsertLevelRule(ctx context.Context, command domain.RuleComman command.Name = strings.TrimSpace(command.Name) command.Status = normalizeStatus(command.Status) command.DisplayConfigJSON = normalizeJSON(command.DisplayConfigJSON) - if !validTrack(command.Track) || command.Level <= 0 || command.RequiredValue <= 0 || command.Name == "" || !validStatus(command.Status) || command.OperatorAdminID <= 0 { + if !validTrack(command.Track) || command.Level < 0 || command.RequiredValue < 0 || command.Name == "" || !validStatus(command.Status) || command.OperatorAdminID <= 0 { return domain.Rule{}, false, xerr.New(xerr.InvalidArgument, "level rule command is invalid") } if command.RewardResourceGroupID < 0 { @@ -229,7 +318,7 @@ func (s *Service) UpsertLevelTier(ctx context.Context, command domain.TierComman command.Name = strings.TrimSpace(command.Name) command.Status = normalizeStatus(command.Status) command.DisplayConfigJSON = normalizeJSON(command.DisplayConfigJSON) - if !validTrack(command.Track) || command.MinLevel <= 0 || command.MaxLevel < command.MinLevel || command.Name == "" || !validStatus(command.Status) || command.OperatorAdminID <= 0 { + if !validTrack(command.Track) || command.MinLevel < 0 || command.MaxLevel < command.MinLevel || command.Name == "" || !validStatus(command.Status) || command.OperatorAdminID <= 0 { return domain.Tier{}, false, xerr.New(xerr.InvalidArgument, "level tier command is invalid") } if command.DisplayAvatarFrameResourceID < 0 || command.DisplayBadgeResourceID < 0 || command.RewardResourceGroupID < 0 { @@ -356,6 +445,29 @@ func normalizeJSON(value string) string { return value } +func longBadgeResourceIDFromDisplayConfig(raw string) int64 { + payload := map[string]any{} + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil { + return 0 + } + value, ok := payload["long_badge_resource_id"] + if !ok { + return 0 + } + switch typed := value.(type) { + case float64: + if typed > 0 { + return int64(typed) + } + case string: + parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) + if err == nil && parsed > 0 { + return parsed + } + } + return 0 +} + func validTrack(value string) bool { switch value { case domain.TrackWealth, domain.TrackGame, domain.TrackCharm: diff --git a/services/activity-service/internal/service/growth/service_test.go b/services/activity-service/internal/service/growth/service_test.go index 42e2c344..be5e2d25 100644 --- a/services/activity-service/internal/service/growth/service_test.go +++ b/services/activity-service/internal/service/growth/service_test.go @@ -61,9 +61,16 @@ func TestGrowthLevelEventRewardFlow(t *testing.T) { t.Fatalf("GetMyLevelOverview failed: %v", err) } wealth := findTrackOverview(overview.Tracks, domain.TrackWealth) - if wealth.Level != 2 || wealth.TierID == 0 || wealth.TotalValue != 250 || wealth.DisplayAvatarFrameResourceID != 502 || wealth.DisplayBadgeResourceID != 602 || wealth.RewardPendingCount != 2 { + if wealth.Level != 2 || wealth.TierID == 0 || wealth.TotalValue != 250 || wealth.DisplayAvatarFrameResourceID != 502 || wealth.DisplayBadgeResourceID != 702 || wealth.DisplayBadgeSourceLevel != 2 || wealth.RewardPendingCount != 2 { t.Fatalf("wealth overview mismatch: %+v", wealth) } + profiles, err := svc.BatchGetUserLevelDisplayProfiles(ctx, []int64{10001}) + if err != nil { + t.Fatalf("BatchGetUserLevelDisplayProfiles failed: %v", err) + } + if len(profiles.Profiles) != 1 || profiles.Profiles[0].Wealth.BadgeResourceID != 702 || profiles.Profiles[0].Wealth.BadgeSourceLevel != 2 { + t.Fatalf("display profile should use highest configured rule badge: %+v", profiles.Profiles) + } claimed, processed, success, failure, hasMore, err := svc.ProcessLevelRewardBatch(ctx, "run-1", "worker-1", 10, time.Second) if err != nil { @@ -154,6 +161,61 @@ func TestListLevelConfigReturnsAdminRulesAndTiers(t *testing.T) { } } +func TestZeroLevelRuleAndTierAreValid(t *testing.T) { + svc, _ := newGrowthService(t) + ctx := appcode.WithContext(context.Background(), "lalu") + if _, created, err := svc.UpsertLevelRule(ctx, domain.RuleCommand{ + Track: domain.TrackWealth, + Level: 0, + RequiredValue: 0, + Name: "wealth 0", + Status: domain.StatusActive, + SortOrder: 0, + DisplayConfigJSON: `{"long_badge_resource_id":700}`, + OperatorAdminID: 90001, + }); err != nil || !created { + t.Fatalf("seed level rule 0 failed: created=%v err=%v", created, err) + } + if _, created, err := svc.UpsertLevelTier(ctx, domain.TierCommand{ + Track: domain.TrackWealth, + MinLevel: 0, + MaxLevel: 9, + Name: "wealth 0-9", + Status: domain.StatusActive, + OperatorAdminID: 90001, + }); err != nil || !created { + t.Fatalf("seed tier 0 failed: created=%v err=%v", created, err) + } + detail, err := svc.GetLevelTrack(ctx, 30001, domain.TrackWealth) + if err != nil { + t.Fatalf("GetLevelTrack failed: %v", err) + } + if detail.Overview.Level != 0 || detail.Overview.CurrentLevelRequiredValue != 0 || detail.Overview.DisplayBadgeResourceID != 700 || detail.Overview.DisplayBadgeSourceLevel != 0 { + t.Fatalf("zero level overview mismatch: %+v", detail.Overview) + } +} + +func TestIssueRegistrationLevelBadgesGrantsLevelOneBadges(t *testing.T) { + svc, wallet := newGrowthService(t) + ctx := appcode.WithContext(context.Background(), "lalu") + seedGrowthRules(t, ctx, svc, domain.TrackWealth) + seedGrowthRules(t, ctx, svc, domain.TrackGame) + seedGrowthRules(t, ctx, svc, domain.TrackCharm) + + grants, err := svc.IssueRegistrationLevelBadges(ctx, 30001, "registration_level_badges:lalu:30001") + if err != nil { + t.Fatalf("IssueRegistrationLevelBadges failed: %v", err) + } + if len(grants.Grants) != 3 || len(wallet.resourceGrants) != 3 { + t.Fatalf("registration level badge grants mismatch: grants=%+v wallet=%d", grants.Grants, len(wallet.resourceGrants)) + } + for _, req := range wallet.resourceGrants { + if req.GetTargetUserId() != 30001 || req.GetResourceId() != 701 || req.GetQuantity() != 1 || req.GetGrantSource() != domain.GrantSourceGrowthLevel { + t.Fatalf("wallet grant request mismatch: %+v", req) + } + } +} + func newGrowthService(t *testing.T) (*growthservice.Service, *fakeGrowthWallet) { t.Helper() repository := mysqltest.NewRepository(t) @@ -173,6 +235,7 @@ func seedGrowthRules(t *testing.T, ctx context.Context, svc *growthservice.Servi Status: domain.StatusActive, RewardResourceGroupID: 0, SortOrder: 1, + DisplayConfigJSON: `{"long_badge_resource_id":701}`, OperatorAdminID: 90001, }); err != nil || !created { t.Fatalf("seed level rule 1 failed: created=%v err=%v", created, err) @@ -185,6 +248,7 @@ func seedGrowthRules(t *testing.T, ctx context.Context, svc *growthservice.Servi Status: domain.StatusActive, RewardResourceGroupID: 9002, SortOrder: 2, + DisplayConfigJSON: `{"long_badge_resource_id":702}`, OperatorAdminID: 90001, }); err != nil || !created { t.Fatalf("seed level rule 2 failed: created=%v err=%v", created, err) @@ -231,7 +295,18 @@ func findTrackOverview(items []domain.TrackOverview, track string) domain.TrackO } type fakeGrowthWallet struct { - grants []*walletv1.GrantResourceGroupRequest + grants []*walletv1.GrantResourceGroupRequest + resourceGrants []*walletv1.GrantResourceRequest +} + +func (f *fakeGrowthWallet) GrantResource(_ context.Context, req *walletv1.GrantResourceRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { + f.resourceGrants = append(f.resourceGrants, req) + return &walletv1.ResourceGrantResponse{ + Grant: &walletv1.ResourceGrant{ + GrantId: fmt.Sprintf("resource-grant-%d", len(f.resourceGrants)), + Status: "succeeded", + }, + }, nil } func (f *fakeGrowthWallet) GrantResourceGroup(_ context.Context, req *walletv1.GrantResourceGroupRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { diff --git a/services/activity-service/internal/storage/mysql/growth_repository.go b/services/activity-service/internal/storage/mysql/growth_repository.go index 4f8e3335..0c650395 100644 --- a/services/activity-service/internal/storage/mysql/growth_repository.go +++ b/services/activity-service/internal/storage/mysql/growth_repository.go @@ -3,8 +3,10 @@ package mysql import ( "context" "database/sql" + "encoding/json" "errors" "fmt" + "strconv" "strings" "time" @@ -63,6 +65,66 @@ func (r *Repository) GetLevelTrack(ctx context.Context, userID int64, track stri return domain.TrackDetail{Overview: overview, Rules: rules, Tiers: tiers, ServerTimeMS: nowMS}, nil } +// BatchGetUserLevelDisplayProfiles 返回资料卡和房间列表需要的等级展示投影。 +func (r *Repository) BatchGetUserLevelDisplayProfiles(ctx context.Context, userIDs []int64, nowMS int64) (domain.LevelDisplayProfiles, error) { + if r == nil || r.db == nil { + return domain.LevelDisplayProfiles{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + userIDs = uniquePositiveInt64s(userIDs) + if len(userIDs) == 0 { + return domain.LevelDisplayProfiles{Profiles: []domain.UserLevelDisplayProfile{}, ServerTimeMS: nowMS}, nil + } + profiles := make(map[int64]domain.UserLevelDisplayProfile, len(userIDs)) + for _, userID := range userIDs { + profiles[userID] = defaultLevelDisplayProfile(userID) + } + args := make([]any, 0, len(userIDs)+1) + args = append(args, appcode.FromContext(ctx)) + for _, userID := range userIDs { + args = append(args, userID) + } + rows, err := r.db.QueryContext(ctx, ` + SELECT user_id, + wealth_level, wealth_tier_id, wealth_avatar_frame_resource_id, wealth_badge_resource_id, wealth_badge_source_level, + game_level, game_tier_id, game_avatar_frame_resource_id, game_badge_resource_id, game_badge_source_level, + charm_level, charm_tier_id, charm_avatar_frame_resource_id, charm_badge_resource_id, charm_badge_source_level, + updated_at_ms + FROM user_level_display_profiles + WHERE app_code = ? AND user_id IN (`+placeholders(len(userIDs))+`)`, args...) + if err != nil { + return domain.LevelDisplayProfiles{}, err + } + defer rows.Close() + for rows.Next() { + var item domain.UserLevelDisplayProfile + var updatedAtMS int64 + if err := rows.Scan( + &item.UserID, + &item.Wealth.Level, &item.Wealth.TierID, &item.Wealth.AvatarFrameResourceID, &item.Wealth.BadgeResourceID, &item.Wealth.BadgeSourceLevel, + &item.Game.Level, &item.Game.TierID, &item.Game.AvatarFrameResourceID, &item.Game.BadgeResourceID, &item.Game.BadgeSourceLevel, + &item.Charm.Level, &item.Charm.TierID, &item.Charm.AvatarFrameResourceID, &item.Charm.BadgeResourceID, &item.Charm.BadgeSourceLevel, + &updatedAtMS, + ); err != nil { + return domain.LevelDisplayProfiles{}, err + } + item.Wealth.Track = domain.TrackWealth + item.Game.Track = domain.TrackGame + item.Charm.Track = domain.TrackCharm + item.Wealth.UpdatedAtMS = updatedAtMS + item.Game.UpdatedAtMS = updatedAtMS + item.Charm.UpdatedAtMS = updatedAtMS + profiles[item.UserID] = item + } + if err := rows.Err(); err != nil { + return domain.LevelDisplayProfiles{}, err + } + result := make([]domain.UserLevelDisplayProfile, 0, len(userIDs)) + for _, userID := range userIDs { + result = append(result, profiles[userID]) + } + return domain.LevelDisplayProfiles{Profiles: result, ServerTimeMS: nowMS}, nil +} + // ListLevelConfig 返回后台等级配置页需要的轨道、单级阈值和等级段展示配置。 func (r *Repository) ListLevelConfig(ctx context.Context, query domain.ConfigQuery, nowMS int64) (domain.Config, error) { if r == nil || r.db == nil { @@ -186,7 +248,8 @@ func (r *Repository) ConsumeLevelEvent(ctx context.Context, event domain.ValueEv } rewardJobCount = count } - if err := r.upsertLevelDisplayProfile(ctx, tx, event.UserID, event.Track, nextLevel, nextTier, nowMS); err != nil { + badgeResourceID, badgeSourceLevel := levelRuleBadgeForLevel(rules, nextLevel) + if err := r.upsertLevelDisplayProfile(ctx, tx, event.UserID, event.Track, nextLevel, nextTier, badgeResourceID, badgeSourceLevel, nowMS); err != nil { return domain.EventResult{}, err } if err := tx.Commit(); err != nil { @@ -405,6 +468,7 @@ func (r *Repository) levelTrackOverview(ctx context.Context, userID int64, track return domain.TrackOverview{}, err } tier := tierByID(tiers, account.CurrentTierID) + badgeResourceID, badgeSourceLevel := levelRuleBadgeForLevel(rules, account.CurrentLevel) currentRequired, nextLevel, nextRequired := levelProgress(rules, account.CurrentLevel) pendingCount, err := r.pendingLevelRewardCount(ctx, userID, track.Track) if err != nil { @@ -420,7 +484,8 @@ func (r *Repository) levelTrackOverview(ctx context.Context, userID int64, track NextLevel: nextLevel, NextLevelRequiredValue: nextRequired, DisplayAvatarFrameResourceID: tier.DisplayAvatarFrameResourceID, - DisplayBadgeResourceID: tier.DisplayBadgeResourceID, + DisplayBadgeResourceID: badgeResourceID, + DisplayBadgeSourceLevel: badgeSourceLevel, RewardPendingCount: pendingCount, SortOrder: track.SortOrder, }, nil @@ -532,7 +597,7 @@ func (r *Repository) insertLevelRewardJob(ctx context.Context, tx *sql.Tx, userI return affected > 0, err } -func (r *Repository) upsertLevelDisplayProfile(ctx context.Context, tx *sql.Tx, userID int64, track string, level int32, tier domain.Tier, nowMS int64) error { +func (r *Repository) upsertLevelDisplayProfile(ctx context.Context, tx *sql.Tx, userID int64, track string, level int32, tier domain.Tier, badgeResourceID int64, badgeSourceLevel int32, nowMS int64) error { columnPrefix := "" switch track { case domain.TrackWealth: @@ -546,18 +611,19 @@ func (r *Repository) upsertLevelDisplayProfile(ctx context.Context, tx *sql.Tx, } query := fmt.Sprintf(` INSERT INTO user_level_display_profiles ( - app_code, user_id, %s_level, %s_tier_id, %s_avatar_frame_resource_id, %s_badge_resource_id, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?) + app_code, user_id, %s_level, %s_tier_id, %s_avatar_frame_resource_id, %s_badge_resource_id, %s_badge_source_level, updated_at_ms + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE %s_level = VALUES(%s_level), %s_tier_id = VALUES(%s_tier_id), %s_avatar_frame_resource_id = VALUES(%s_avatar_frame_resource_id), %s_badge_resource_id = VALUES(%s_badge_resource_id), + %s_badge_source_level = VALUES(%s_badge_source_level), updated_at_ms = VALUES(updated_at_ms)`, - columnPrefix, columnPrefix, columnPrefix, columnPrefix, - columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, + columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, + columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, columnPrefix, ) - _, err := tx.ExecContext(ctx, query, appcode.FromContext(ctx), userID, level, tier.TierID, tier.DisplayAvatarFrameResourceID, tier.DisplayBadgeResourceID, nowMS) + _, err := tx.ExecContext(ctx, query, appcode.FromContext(ctx), userID, level, tier.TierID, tier.DisplayAvatarFrameResourceID, badgeResourceID, badgeSourceLevel, nowMS) return err } @@ -779,7 +845,7 @@ func levelForValue(rules []domain.Rule, totalValue int64) int32 { } func tierForLevel(tiers []domain.Tier, level int32) domain.Tier { - if level <= 0 { + if level < 0 { return domain.Tier{} } for _, tier := range tiers { @@ -819,6 +885,92 @@ func levelProgress(rules []domain.Rule, currentLevel int32) (int64, int32, int64 return currentRequired, nextLevel, nextRequired } +func levelRuleBadgeForLevel(rules []domain.Rule, currentLevel int32) (int64, int32) { + if currentLevel < 0 { + return 0, 0 + } + badgeResourceID := int64(0) + badgeSourceLevel := int32(0) + for _, rule := range rules { + if rule.Level > currentLevel || rule.Level < badgeSourceLevel { + continue + } + candidate := longBadgeResourceIDFromDisplayConfig(rule.DisplayConfigJSON) + if candidate <= 0 { + continue + } + // 等级徽章按当前等级以内最高配置生效;后续低等级配置不再覆盖更高等级展示。 + badgeResourceID = candidate + badgeSourceLevel = rule.Level + } + return badgeResourceID, badgeSourceLevel +} + +func longBadgeResourceIDFromDisplayConfig(raw string) int64 { + payload := map[string]any{} + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil { + return 0 + } + value, ok := payload["long_badge_resource_id"] + if !ok { + return 0 + } + switch typed := value.(type) { + case float64: + if typed > 0 { + return int64(typed) + } + case string: + parsed, err := strconv.ParseInt(strings.TrimSpace(typed), 10, 64) + if err == nil && parsed > 0 { + return parsed + } + } + return 0 +} + +func defaultLevelDisplayProfile(userID int64) domain.UserLevelDisplayProfile { + return domain.UserLevelDisplayProfile{ + UserID: userID, + Wealth: domain.LevelDisplayTrackProfile{ + Track: domain.TrackWealth, + }, + Game: domain.LevelDisplayTrackProfile{ + Track: domain.TrackGame, + }, + Charm: domain.LevelDisplayTrackProfile{ + Track: domain.TrackCharm, + }, + } +} + +func uniquePositiveInt64s(values []int64) []int64 { + seen := make(map[int64]struct{}, len(values)) + result := make([]int64, 0, len(values)) + for _, value := range values { + if value <= 0 { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + result = append(result, value) + } + return result +} + +func placeholders(count int) string { + if count <= 0 { + return "" + } + items := make([]string, count) + for index := range items { + items[index] = "?" + } + return strings.Join(items, ",") +} + func scanLevelTracks(rows *sql.Rows) ([]domain.Track, error) { items := make([]domain.Track, 0) for rows.Next() { diff --git a/services/activity-service/internal/transport/grpc/growth_server.go b/services/activity-service/internal/transport/grpc/growth_server.go index 68d1b206..4c8cf19e 100644 --- a/services/activity-service/internal/transport/grpc/growth_server.go +++ b/services/activity-service/internal/transport/grpc/growth_server.go @@ -58,6 +58,22 @@ func (s *GrowthLevelServer) GetLevelTrack(ctx context.Context, req *activityv1.G return resp, nil } +func (s *GrowthLevelServer) BatchGetUserLevelDisplayProfiles(ctx context.Context, req *activityv1.BatchGetUserLevelDisplayProfilesRequest) (*activityv1.BatchGetUserLevelDisplayProfilesResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + profiles, err := s.svc.BatchGetUserLevelDisplayProfiles(ctx, req.GetUserIds()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &activityv1.BatchGetUserLevelDisplayProfilesResponse{ + Profiles: make([]*activityv1.UserLevelDisplayProfile, 0, len(profiles.Profiles)), + ServerTimeMs: profiles.ServerTimeMS, + } + for _, item := range profiles.Profiles { + resp.Profiles = append(resp.Profiles, userLevelDisplayProfileToProto(item)) + } + return resp, nil +} + func (s *GrowthLevelServer) ListLevelRewards(ctx context.Context, req *activityv1.ListLevelRewardsRequest) (*activityv1.ListLevelRewardsResponse, error) { ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) rewards, total, err := s.svc.ListLevelRewards(ctx, domain.RewardQuery{ @@ -104,6 +120,22 @@ func (s *GrowthLevelServer) ConsumeLevelEvent(ctx context.Context, req *activity }, nil } +func (s *GrowthLevelServer) IssueRegistrationLevelBadges(ctx context.Context, req *activityv1.IssueRegistrationLevelBadgesRequest) (*activityv1.IssueRegistrationLevelBadgesResponse, error) { + ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode()) + grants, err := s.svc.IssueRegistrationLevelBadges(ctx, req.GetUserId(), req.GetCommandId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + resp := &activityv1.IssueRegistrationLevelBadgesResponse{ + Grants: make([]*activityv1.RegistrationLevelBadgeGrant, 0, len(grants.Grants)), + ServerTimeMs: grants.ServerTimeMS, + } + for _, item := range grants.Grants { + resp.Grants = append(resp.Grants, registrationLevelBadgeGrantToProto(item)) + } + return resp, nil +} + // AdminGrowthLevelServer 暴露后台等级配置入口,业务约束仍由 service/repository 校验。 type AdminGrowthLevelServer struct { activityv1.UnimplementedAdminGrowthLevelServiceServer @@ -257,11 +289,33 @@ func levelTrackOverviewToProto(item domain.TrackOverview) *activityv1.LevelTrack NextLevelRequiredValue: item.NextLevelRequiredValue, DisplayAvatarFrameResourceId: item.DisplayAvatarFrameResourceID, DisplayBadgeResourceId: item.DisplayBadgeResourceID, + DisplayBadgeSourceLevel: item.DisplayBadgeSourceLevel, RewardPendingCount: item.RewardPendingCount, SortOrder: item.SortOrder, } } +func userLevelDisplayProfileToProto(item domain.UserLevelDisplayProfile) *activityv1.UserLevelDisplayProfile { + return &activityv1.UserLevelDisplayProfile{ + UserId: item.UserID, + Wealth: levelDisplayTrackProfileToProto(item.Wealth), + Game: levelDisplayTrackProfileToProto(item.Game), + Charm: levelDisplayTrackProfileToProto(item.Charm), + } +} + +func levelDisplayTrackProfileToProto(item domain.LevelDisplayTrackProfile) *activityv1.LevelDisplayTrackProfile { + return &activityv1.LevelDisplayTrackProfile{ + Track: item.Track, + Level: item.Level, + TierId: item.TierID, + AvatarFrameResourceId: item.AvatarFrameResourceID, + BadgeResourceId: item.BadgeResourceID, + BadgeSourceLevel: item.BadgeSourceLevel, + UpdatedAtMs: item.UpdatedAtMS, + } +} + func levelRewardToProto(item domain.RewardJob) *activityv1.LevelRewardJob { return &activityv1.LevelRewardJob{ RewardJobId: item.RewardJobID, @@ -279,3 +333,12 @@ func levelRewardToProto(item domain.RewardJob) *activityv1.LevelRewardJob { UpdatedAtMs: item.UpdatedAtMS, } } + +func registrationLevelBadgeGrantToProto(item domain.RegistrationLevelBadgeGrant) *activityv1.RegistrationLevelBadgeGrant { + return &activityv1.RegistrationLevelBadgeGrant{ + Track: item.Track, + Level: item.Level, + ResourceId: item.ResourceID, + GrantId: item.GrantID, + } +} diff --git a/services/gateway-service/internal/client/growth_level_client.go b/services/gateway-service/internal/client/growth_level_client.go index 3862f225..e4521d9c 100644 --- a/services/gateway-service/internal/client/growth_level_client.go +++ b/services/gateway-service/internal/client/growth_level_client.go @@ -11,6 +11,7 @@ import ( type GrowthLevelClient interface { GetMyLevelOverview(ctx context.Context, req *activityv1.GetMyLevelOverviewRequest) (*activityv1.GetMyLevelOverviewResponse, error) GetLevelTrack(ctx context.Context, req *activityv1.GetLevelTrackRequest) (*activityv1.GetLevelTrackResponse, error) + BatchGetUserLevelDisplayProfiles(ctx context.Context, req *activityv1.BatchGetUserLevelDisplayProfilesRequest) (*activityv1.BatchGetUserLevelDisplayProfilesResponse, error) ListLevelRewards(ctx context.Context, req *activityv1.ListLevelRewardsRequest) (*activityv1.ListLevelRewardsResponse, error) } @@ -30,6 +31,10 @@ func (c *grpcGrowthLevelClient) GetLevelTrack(ctx context.Context, req *activity return c.client.GetLevelTrack(ctx, req) } +func (c *grpcGrowthLevelClient) BatchGetUserLevelDisplayProfiles(ctx context.Context, req *activityv1.BatchGetUserLevelDisplayProfilesRequest) (*activityv1.BatchGetUserLevelDisplayProfilesResponse, error) { + return c.client.BatchGetUserLevelDisplayProfiles(ctx, req) +} + func (c *grpcGrowthLevelClient) ListLevelRewards(ctx context.Context, req *activityv1.ListLevelRewardsRequest) (*activityv1.ListLevelRewardsResponse, error) { return c.client.ListLevelRewards(ctx, req) } diff --git a/services/gateway-service/internal/transport/http/activityapi/level_handler.go b/services/gateway-service/internal/transport/http/activityapi/level_handler.go index d75334a1..58650a7f 100644 --- a/services/gateway-service/internal/transport/http/activityapi/level_handler.go +++ b/services/gateway-service/internal/transport/http/activityapi/level_handler.go @@ -25,6 +25,7 @@ type levelTrackOverviewData struct { NextLevelRequiredValue int64 `json:"next_level_required_value"` DisplayAvatarFrameResourceID int64 `json:"display_avatar_frame_resource_id"` DisplayBadgeResourceID int64 `json:"display_badge_resource_id"` + DisplayBadgeSourceLevel int32 `json:"display_badge_source_level"` RewardPendingCount int64 `json:"reward_pending_count"` SortOrder int32 `json:"sort_order"` } @@ -190,6 +191,7 @@ func levelTrackOverviewFromProto(item *activityv1.LevelTrackOverview) levelTrack NextLevelRequiredValue: item.GetNextLevelRequiredValue(), DisplayAvatarFrameResourceID: item.GetDisplayAvatarFrameResourceId(), DisplayBadgeResourceID: item.GetDisplayBadgeResourceId(), + DisplayBadgeSourceLevel: item.GetDisplayBadgeSourceLevel(), RewardPendingCount: item.GetRewardPendingCount(), SortOrder: item.GetSortOrder(), } diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index c145af58..01fca1b9 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -425,64 +425,65 @@ type fakeUserHostClient struct { } type fakeWalletClient struct { - last *walletv1.GetBalancesRequest - balanceRequests []*walletv1.GetBalancesRequest - resp *walletv1.GetBalancesResponse - balancesByUserID map[int64]*walletv1.GetBalancesResponse - err error - lastOverview *walletv1.GetWalletOverviewRequest - overviewResp *walletv1.GetWalletOverviewResponse - overviewErr error - lastValueSummary *walletv1.GetWalletValueSummaryRequest - valueSummaryResp *walletv1.GetWalletValueSummaryResponse - valueSummaryErr error - lastGiftWall *walletv1.GetUserGiftWallRequest - giftWallResp *walletv1.GetUserGiftWallResponse - lastRechargeProducts *walletv1.ListRechargeProductsRequest - rechargeProductsResp *walletv1.ListRechargeProductsResponse - lastGoogleConfirm *walletv1.ConfirmGooglePaymentRequest - googleConfirmResp *walletv1.ConfirmGooglePaymentResponse - lastDiamondExchange *walletv1.GetDiamondExchangeConfigRequest - diamondExchangeResp *walletv1.GetDiamondExchangeConfigResponse - lastTransactions *walletv1.ListWalletTransactionsRequest - transactionsResp *walletv1.ListWalletTransactionsResponse - lastWithdrawal *walletv1.ApplyWithdrawalRequest - withdrawalResp *walletv1.ApplyWithdrawalResponse - lastVipPackages *walletv1.ListVipPackagesRequest - vipPackagesResp *walletv1.ListVipPackagesResponse - lastMyVip *walletv1.GetMyVipRequest - myVipResp *walletv1.GetMyVipResponse - vipErr error - lastPurchaseVip *walletv1.PurchaseVipRequest - purchaseVipResp *walletv1.PurchaseVipResponse - lastTransfer *walletv1.TransferCoinFromSellerRequest - transferResp *walletv1.TransferCoinFromSellerResponse - transferErr error - lastListResources *walletv1.ListResourcesRequest - listResourcesResp *walletv1.ListResourcesResponse - lastGetResource *walletv1.GetResourceRequest - resourcesByID map[int64]*walletv1.Resource - lastGetResourceGroup *walletv1.GetResourceGroupRequest - resourceGroupsByID map[int64]*walletv1.ResourceGroup - lastResourceShop *walletv1.ListResourceShopItemsRequest - resourceShopResp *walletv1.ListResourceShopItemsResponse - lastPurchaseShop *walletv1.PurchaseResourceShopItemRequest - purchaseShopResp *walletv1.PurchaseResourceShopItemResponse - lastListGiftConfigs *walletv1.ListGiftConfigsRequest - listGiftConfigsResp *walletv1.ListGiftConfigsResponse - lastListGiftTypes *walletv1.ListGiftTypeConfigsRequest - listGiftTypesResp *walletv1.ListGiftTypeConfigsResponse - lastGrantResource *walletv1.GrantResourceRequest - grantResourceResp *walletv1.ResourceGrantResponse - grantResourceErr error - lastBatchEquipped *walletv1.BatchGetUserEquippedResourcesRequest - batchEquippedResp *walletv1.BatchGetUserEquippedResourcesResponse - lastListRedPackets *walletv1.ListRedPacketsRequest - listRedPacketsResp *walletv1.ListRedPacketsResponse - lastGetRedPacket *walletv1.GetRedPacketRequest - getRedPacketResp *walletv1.GetRedPacketResponse - lastClaimRedPacket *walletv1.ClaimRedPacketRequest - claimRedPacketResp *walletv1.ClaimRedPacketResponse + last *walletv1.GetBalancesRequest + balanceRequests []*walletv1.GetBalancesRequest + resp *walletv1.GetBalancesResponse + balancesByUserID map[int64]*walletv1.GetBalancesResponse + err error + lastOverview *walletv1.GetWalletOverviewRequest + overviewResp *walletv1.GetWalletOverviewResponse + overviewErr error + lastValueSummary *walletv1.GetWalletValueSummaryRequest + valueSummaryResp *walletv1.GetWalletValueSummaryResponse + valueSummaryErr error + lastGiftWall *walletv1.GetUserGiftWallRequest + giftWallResp *walletv1.GetUserGiftWallResponse + lastRechargeProducts *walletv1.ListRechargeProductsRequest + rechargeProductsResp *walletv1.ListRechargeProductsResponse + lastGoogleConfirm *walletv1.ConfirmGooglePaymentRequest + googleConfirmResp *walletv1.ConfirmGooglePaymentResponse + lastDiamondExchange *walletv1.GetDiamondExchangeConfigRequest + diamondExchangeResp *walletv1.GetDiamondExchangeConfigResponse + lastTransactions *walletv1.ListWalletTransactionsRequest + transactionsResp *walletv1.ListWalletTransactionsResponse + lastWithdrawal *walletv1.ApplyWithdrawalRequest + withdrawalResp *walletv1.ApplyWithdrawalResponse + lastVipPackages *walletv1.ListVipPackagesRequest + vipPackagesResp *walletv1.ListVipPackagesResponse + lastMyVip *walletv1.GetMyVipRequest + myVipResp *walletv1.GetMyVipResponse + vipErr error + lastPurchaseVip *walletv1.PurchaseVipRequest + purchaseVipResp *walletv1.PurchaseVipResponse + lastTransfer *walletv1.TransferCoinFromSellerRequest + transferResp *walletv1.TransferCoinFromSellerResponse + transferErr error + lastListResources *walletv1.ListResourcesRequest + listResourcesResp *walletv1.ListResourcesResponse + lastGetResource *walletv1.GetResourceRequest + resourcesByID map[int64]*walletv1.Resource + lastGetResourceGroup *walletv1.GetResourceGroupRequest + resourceGroupsByID map[int64]*walletv1.ResourceGroup + lastResourceShop *walletv1.ListResourceShopItemsRequest + resourceShopResp *walletv1.ListResourceShopItemsResponse + lastPurchaseShop *walletv1.PurchaseResourceShopItemRequest + purchaseShopResp *walletv1.PurchaseResourceShopItemResponse + lastListGiftConfigs *walletv1.ListGiftConfigsRequest + listGiftConfigsResp *walletv1.ListGiftConfigsResponse + lastListGiftTypes *walletv1.ListGiftTypeConfigsRequest + listGiftTypesResp *walletv1.ListGiftTypeConfigsResponse + lastGrantResource *walletv1.GrantResourceRequest + grantResourceResp *walletv1.ResourceGrantResponse + grantResourceErr error + lastBatchEquipped *walletv1.BatchGetUserEquippedResourcesRequest + batchEquippedRequests []*walletv1.BatchGetUserEquippedResourcesRequest + batchEquippedResp *walletv1.BatchGetUserEquippedResourcesResponse + lastListRedPackets *walletv1.ListRedPacketsRequest + listRedPacketsResp *walletv1.ListRedPacketsResponse + lastGetRedPacket *walletv1.GetRedPacketRequest + getRedPacketResp *walletv1.GetRedPacketResponse + lastClaimRedPacket *walletv1.ClaimRedPacketRequest + claimRedPacketResp *walletv1.ClaimRedPacketResponse } type fakeMessageInboxClient struct { @@ -1474,6 +1475,7 @@ func (f *fakeWalletClient) UnequipUserResource(context.Context, *walletv1.Unequi func (f *fakeWalletClient) BatchGetUserEquippedResources(_ context.Context, req *walletv1.BatchGetUserEquippedResourcesRequest) (*walletv1.BatchGetUserEquippedResourcesResponse, error) { f.lastBatchEquipped = req + f.batchEquippedRequests = append(f.batchEquippedRequests, req) if f.err != nil { return nil, f.err } @@ -1839,14 +1841,17 @@ func TestSendGiftResponseIncludesLuckyGiftDraw(t *testing.T) { GiftId: "rose", RuleVersion: 12, ExperiencePool: "novice", - SelectedTierId: "novice_2x", - MultiplierPpm: 2_000_000, - EffectiveRewardCoins: 200, + SelectedTierId: "batch", + MultiplierPpm: 5_000_000, + EffectiveRewardCoins: 500, RewardStatus: "granted", - WalletTransactionId: "wallet_tx_lucky", CoinBalanceAfter: 8800, CreatedAtMs: 1_779_258_000_000, }, + LuckyGifts: []*roomv1.LuckyGiftDrawResult{ + {Enabled: true, DrawId: "lucky_draw_1", CommandId: "cmd-gift-lucky:target:43", PoolId: "super_lucky", GiftId: "rose", MultiplierPpm: 2_000_000, EffectiveRewardCoins: 200, RewardStatus: "granted", WalletTransactionId: "wallet_tx_lucky_1", TargetUserId: 43}, + {Enabled: true, DrawId: "lucky_draw_2", CommandId: "cmd-gift-lucky:target:44", PoolId: "super_lucky", GiftId: "rose", MultiplierPpm: 3_000_000, EffectiveRewardCoins: 300, RewardStatus: "granted", WalletTransactionId: "wallet_tx_lucky_2", TargetUserId: 44}, + }, }} router := NewHandlerWithClients(roomClient, nil, nil, &fakeUserProfileClient{regionID: 1001}).Routes(auth.NewVerifier("secret")) request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/gift/send", bytes.NewReader([]byte(`{"room_id":"room-1","command_id":"cmd-gift-lucky","target_user_id":43,"gift_id":"rose","gift_count":1,"pool_id":"super_lucky"}`))) @@ -1870,14 +1875,22 @@ func TestSendGiftResponseIncludesLuckyGiftDraw(t *testing.T) { WalletTransactionID string `json:"wallet_transaction_id"` CoinBalanceAfter int64 `json:"coin_balance_after"` } `json:"lucky_gift"` + LuckyGifts []struct { + TargetUserID int64 `json:"target_user_id"` + MultiplierPPM int64 `json:"multiplier_ppm"` + EffectiveRewardCoins int64 `json:"effective_reward_coins"` + } `json:"lucky_gifts"` } `json:"data"` } if err := json.Unmarshal(recorder.Body.Bytes(), &envelope); err != nil { t.Fatalf("decode response failed: %v body=%s", err, recorder.Body.String()) } - if envelope.Code != "OK" || envelope.Data.LuckyGift.DrawID != "lucky_draw_test" || envelope.Data.LuckyGift.PoolID != "super_lucky" || envelope.Data.LuckyGift.MultiplierPPM != 2_000_000 || envelope.Data.LuckyGift.EffectiveRewardCoins != 200 || envelope.Data.LuckyGift.RewardStatus != "granted" || envelope.Data.LuckyGift.WalletTransactionID != "wallet_tx_lucky" || envelope.Data.LuckyGift.CoinBalanceAfter != 8800 { + if envelope.Code != "OK" || envelope.Data.LuckyGift.DrawID != "lucky_draw_test" || envelope.Data.LuckyGift.PoolID != "super_lucky" || envelope.Data.LuckyGift.MultiplierPPM != 5_000_000 || envelope.Data.LuckyGift.EffectiveRewardCoins != 500 || envelope.Data.LuckyGift.RewardStatus != "granted" || envelope.Data.LuckyGift.WalletTransactionID != "" || envelope.Data.LuckyGift.CoinBalanceAfter != 8800 { t.Fatalf("lucky_gift response mismatch: %+v", envelope.Data.LuckyGift) } + if len(envelope.Data.LuckyGifts) != 2 || envelope.Data.LuckyGifts[0].TargetUserID != 43 || envelope.Data.LuckyGifts[0].MultiplierPPM != 2_000_000 || envelope.Data.LuckyGifts[1].TargetUserID != 44 || envelope.Data.LuckyGifts[1].MultiplierPPM != 3_000_000 { + t.Fatalf("lucky_gifts response mismatch: %+v", envelope.Data.LuckyGifts) + } } func TestSendGiftInjectsActiveHostPeriodScope(t *testing.T) { @@ -1964,8 +1977,35 @@ func TestJoinRoomReturnsInitialDataWithIMGroupRTCTokenAndProfiles(t *testing.T) RoomShortId: "100101", }, }} + profileCardResource := &walletv1.Resource{ + ResourceId: 8104, + ResourceCode: "profile_card_gold", + ResourceType: "profile_card", + Name: "Gold Profile Card", + AssetUrl: "https://cdn.example/profile-card.png", + AnimationUrl: "https://cdn.example/profile-card.mp4", + MetadataJson: `{"profile_card_layout":{"source_width":1136,"source_height":1680,"content_top":143,"content_bottom":1667,"content_height":1525}}`, + GrantStrategy: "extend_expiry", + Status: "active", + } + walletClient := &fakeWalletClient{ + batchEquippedResp: &walletv1.BatchGetUserEquippedResourcesResponse{ + Users: []*walletv1.UserEquippedResources{{ + UserId: 42, + Resources: []*walletv1.UserResourceEntitlement{{ + UserId: 42, + EntitlementId: "ent-profile-card", + ResourceId: 8104, + Resource: profileCardResource, + Equipped: true, + ExpiresAtMs: 1999999999999, + }}, + }}, + }, + } profileClient := &fakeUserProfileClient{} handler := NewHandlerWithConfig(roomClient, nil, nil, nil, TencentIMConfig{}, profileClient) + handler.SetWalletClient(walletClient) handler.SetTencentRTC(TencentRTCConfig{ Enabled: true, SDKAppID: 1400000000, @@ -2017,6 +2057,34 @@ func TestJoinRoomReturnsInitialDataWithIMGroupRTCTokenAndProfiles(t *testing.T) if profileClient.lastBatch == nil || strings.Join(int64SliceToStrings(profileClient.lastBatch.GetUserIds()), ",") != "101,42,102,301" { t.Fatalf("join response must batch only first-screen profile users: %+v", profileClient.lastBatch) } + hasRoomDisplayResourceQuery := false + for _, req := range walletClient.batchEquippedRequests { + if strings.Join(req.GetResourceTypes(), ",") == "avatar_frame,profile_card,vehicle" { + hasRoomDisplayResourceQuery = true + break + } + } + if !hasRoomDisplayResourceQuery { + t.Fatalf("join response must batch room wearable resources including profile_card: %+v", walletClient.batchEquippedRequests) + } + profiles, profilesOK := data["profiles"].([]any) + if !profilesOK || len(profiles) == 0 { + t.Fatalf("join response must include room display profiles: %+v", data["profiles"]) + } + var currentProfile map[string]any + for _, item := range profiles { + profile, ok := item.(map[string]any) + if !ok || profile["user_id"] != "42" { + continue + } + currentProfile = profile + break + } + profileCard, profileCardOK := currentProfile["profile_card"].(map[string]any) + metadataJSON, _ := profileCard["metadata_json"].(string) + if !profileCardOK || profileCard["resource_id"] != float64(8104) || !strings.Contains(metadataJSON, `"content_top":143`) { + t.Fatalf("join profile must include profile_card metadata_json: %+v", currentProfile) + } } func TestListRoomsUsesUserRegionFromUserService(t *testing.T) { @@ -4038,6 +4106,17 @@ func TestAppearanceAggregatesEquippedResourcesAndBadges(t *testing.T) { GrantStrategy: "set_active_flag", Status: "active", } + profileCardResource := &walletv1.Resource{ + ResourceId: 8104, + ResourceCode: "profile_card_gold", + ResourceType: "profile_card", + Name: "Gold Profile Card", + AssetUrl: "https://cdn.example/profile-card.png", + AnimationUrl: "https://cdn.example/profile-card.mp4", + MetadataJson: `{"profile_card_layout":{"source_width":1136,"source_height":1680,"content_top":117,"content_bottom":1666,"content_height":1550}}`, + GrantStrategy: "extend_expiry", + Status: "active", + } badgeResource := &walletv1.Resource{ ResourceId: 8103, ResourceCode: "badge_host", @@ -4054,6 +4133,7 @@ func TestAppearanceAggregatesEquippedResourcesAndBadges(t *testing.T) { Resources: []*walletv1.UserResourceEntitlement{ {UserId: 42, EntitlementId: "ent-frame", ResourceId: 8101, Resource: frameResource, Equipped: true, ExpiresAtMs: 1999999999999}, {UserId: 42, EntitlementId: "ent-vehicle", ResourceId: 8102, Resource: vehicleResource, Equipped: true, ExpiresAtMs: 1999999999999}, + {UserId: 42, EntitlementId: "ent-profile-card", ResourceId: 8104, Resource: profileCardResource, Equipped: true, ExpiresAtMs: 1999999999999}, {UserId: 42, EntitlementId: "ent-badge", ResourceId: 8103, Resource: badgeResource, Equipped: true, ExpiresAtMs: 1999999999999}, }, }}, @@ -4100,6 +4180,10 @@ func TestAppearanceAggregatesEquippedResourcesAndBadges(t *testing.T) { ResourceID int64 `json:"resource_id"` AnimationURL string `json:"animation_url"` } `json:"vehicle"` + ProfileCard struct { + ResourceID int64 `json:"resource_id"` + MetadataJSON string `json:"metadata_json"` + } `json:"profile_card"` EquippedBadges []struct { ResourceID int64 `json:"resource_id"` } `json:"equipped_badges"` @@ -4120,7 +4204,7 @@ func TestAppearanceAggregatesEquippedResourcesAndBadges(t *testing.T) { if achievementClient.lastBadges == nil || achievementClient.lastBadges.GetUserId() != 42 { t.Fatalf("appearance should query displayed badges: %+v", achievementClient.lastBadges) } - if envelope.Code != httpkit.CodeOK || envelope.Data.UserID != "42" || envelope.Data.AvatarFrame.AnimationURL != "https://cdn.example/frame.svga" || envelope.Data.Vehicle.ResourceID != 8102 || len(envelope.Data.EquippedBadges) != 1 || len(envelope.Data.Badges) != 1 || envelope.Data.Badges[0].Resource.AssetURL != "https://cdn.example/badge.png" { + if envelope.Code != httpkit.CodeOK || envelope.Data.UserID != "42" || envelope.Data.AvatarFrame.AnimationURL != "https://cdn.example/frame.svga" || envelope.Data.Vehicle.ResourceID != 8102 || envelope.Data.ProfileCard.ResourceID != 8104 || !strings.Contains(envelope.Data.ProfileCard.MetadataJSON, `"content_top":117`) || len(envelope.Data.EquippedBadges) != 1 || len(envelope.Data.Badges) != 1 || envelope.Data.Badges[0].Resource.AssetURL != "https://cdn.example/badge.png" { t.Fatalf("appearance response mismatch: %+v", envelope) } } diff --git a/services/gateway-service/internal/transport/http/roomapi/handler.go b/services/gateway-service/internal/transport/http/roomapi/handler.go index 0f20cdff..f3193a57 100644 --- a/services/gateway-service/internal/transport/http/roomapi/handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/handler.go @@ -18,6 +18,7 @@ type Handler struct { userSocialClient client.UserSocialClient userHostClient client.UserHostClient walletClient client.WalletClient + growthLevelClient client.GrowthLevelClient achievementClient client.AchievementClient rtcTokenConfig tencentrtc.TokenConfig } @@ -30,6 +31,7 @@ type Config struct { UserSocialClient client.UserSocialClient UserHostClient client.UserHostClient WalletClient client.WalletClient + GrowthLevelClient client.GrowthLevelClient AchievementClient client.AchievementClient RTCTokenConfig tencentrtc.TokenConfig } @@ -43,6 +45,7 @@ func New(config Config) *Handler { userSocialClient: config.UserSocialClient, userHostClient: config.UserHostClient, walletClient: config.WalletClient, + growthLevelClient: config.GrowthLevelClient, achievementClient: config.AchievementClient, rtcTokenConfig: config.RTCTokenConfig, } diff --git a/services/gateway-service/internal/transport/http/roomapi/room_handler.go b/services/gateway-service/internal/transport/http/roomapi/room_handler.go index 336c2a88..85ff6688 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_handler.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_handler.go @@ -3,6 +3,7 @@ package roomapi import ( "encoding/base64" "encoding/json" + "fmt" "hyapp/services/gateway-service/internal/transport/http/httpkit" "net/http" "strconv" @@ -938,37 +939,11 @@ func (h *Handler) joinRoomRTCData(roomID string, viewerUserID int64) roomRTCData return rtc } -func (h *Handler) roomInitialProfiles(request *http.Request, snapshot *roomv1.RoomSnapshot, viewerUserID int64) []roomUserProfileData { - if h.userProfileClient == nil { - return nil - } +func (h *Handler) roomInitialProfiles(request *http.Request, snapshot *roomv1.RoomSnapshot, viewerUserID int64) []roomDisplayProfileData { userIDs := roomInitialProfileUserIDs(snapshot, viewerUserID, joinRoomContributionRankLimit) - if len(userIDs) == 0 { - return nil - } - resp, err := h.userProfileClient.BatchGetUsers(request.Context(), &userv1.BatchGetUsersRequest{ - Meta: httpkit.UserMeta(request, ""), - UserIds: userIDs, - }) - if err != nil { - // 用户资料只服务首屏展示,失败时让客户端用 /users/profiles:batch 补偿,避免阻塞进房。 - return nil - } - - profiles := make([]roomUserProfileData, 0, len(userIDs)) - for _, userID := range userIDs { - user := resp.GetUsers()[userID] - if user == nil { - continue - } - profiles = append(profiles, roomUserProfileData{ - UserID: strconv.FormatInt(user.GetUserId(), 10), - DisplayUserID: user.GetDisplayUserId(), - Username: user.GetUsername(), - Avatar: user.GetAvatar(), - }) - } - return profiles + // 进房首屏资料和 /users/room-display-profiles:batch 使用同一份聚合逻辑, + // 避免头像框、资料卡、徽章等展示资源在不同房间入口字段不一致。 + return h.roomDisplayProfiles(request, userIDs) } // roomHeartbeat 把客户端显式心跳转换为 room-service presence refresh 命令。 @@ -1444,9 +1419,10 @@ func (h *Handler) roomDisplayProfileMap(request *http.Request, userIDs []int64) // 房间资料是展示增强,失败时保留主房间数据,让客户端后续重试 batch 接口。 return nil } - // 房间资料高频读取,头像框/座驾用 wallet 批量查;badge 当前 activity 只有单用户查询,单独降级处理。 + // 房间资料高频读取,头像框/资料卡/座驾用 wallet 批量查;badge 当前 activity 只有单用户查询,单独降级处理。 appearanceMap := h.roomAppearanceResourceMap(request, userIDs) badgeMap := h.roomBadgeDisplayMap(request, userIDs) + levelMap := h.roomLevelDisplayProfileMap(request, userIDs) profiles := make(map[int64]roomDisplayProfileData, len(resp.GetUsers())) for _, userID := range userIDs { user := resp.GetUsers()[userID] @@ -1454,6 +1430,7 @@ func (h *Handler) roomDisplayProfileMap(request *http.Request, userIDs []int64) continue } appearance := appearanceMap[userID] + levels := levelMap[userID] profiles[userID] = roomDisplayProfileData{ UserID: formatOptionalUserID(user.GetUserId()), Username: user.GetUsername(), @@ -1466,9 +1443,11 @@ func (h *Handler) roomDisplayProfileMap(request *http.Request, userIDs []int64) CountryDisplayName: strings.TrimSpace(user.GetCountryDisplayName()), CountryFlag: roomProfileCountryFlag(user.GetCountry()), VIP: map[string]any{}, - Level: map[string]any{}, + Level: levels.Levels, + LevelBadges: levels.Badges, Badges: badgeMap[userID], AvatarFrame: appearance.AvatarFrame, + ProfileCard: appearance.ProfileCard, Vehicle: appearance.Vehicle, Charm: 0, } @@ -1483,17 +1462,33 @@ func (h *Handler) roomDisplayProfileMap(request *http.Request, userIDs []int64) profile.AvatarFrame = map[string]any{} profiles[userID] = profile } + if profiles[userID].Level == nil { + profile := profiles[userID] + profile.Level = roomDefaultLevelDisplay() + profiles[userID] = profile + } + if profiles[userID].LevelBadges == nil { + profile := profiles[userID] + profile.LevelBadges = map[string]any{} + profiles[userID] = profile + } if profiles[userID].Vehicle == nil { profile := profiles[userID] profile.Vehicle = map[string]any{} profiles[userID] = profile } + if profiles[userID].ProfileCard == nil { + profile := profiles[userID] + profile.ProfileCard = map[string]any{} + profiles[userID] = profile + } } return profiles } type roomAppearanceResources struct { AvatarFrame map[string]any + ProfileCard map[string]any Vehicle map[string]any } @@ -1507,7 +1502,7 @@ func (h *Handler) roomAppearanceResourceMap(request *http.Request, userIDs []int RequestId: httpkit.RequestIDFromContext(request.Context()), AppCode: appcode.FromContext(request.Context()), UserIds: userIDs, - ResourceTypes: []string{"avatar_frame", "vehicle"}, + ResourceTypes: []string{"avatar_frame", "profile_card", "vehicle"}, }) if err != nil { // 装扮是展示增强,失败不影响房间主数据返回。 @@ -1524,6 +1519,8 @@ func (h *Handler) roomAppearanceResourceMap(request *http.Request, userIDs []int switch item.GetResource().GetResourceType() { case "avatar_frame": current.AvatarFrame = resource + case "profile_card": + current.ProfileCard = resource case "vehicle": current.Vehicle = resource } @@ -1533,6 +1530,174 @@ func (h *Handler) roomAppearanceResourceMap(request *http.Request, userIDs []int return result } +type roomLevelDisplayResources struct { + Levels map[string]any + Badges map[string]any +} + +func (h *Handler) roomLevelDisplayProfileMap(request *http.Request, userIDs []int64) map[int64]roomLevelDisplayResources { + result := make(map[int64]roomLevelDisplayResources, len(userIDs)) + for _, userID := range userIDs { + result[userID] = roomLevelDisplayResources{Levels: roomDefaultLevelDisplay(), Badges: map[string]any{}} + } + if h.growthLevelClient != nil && len(userIDs) > 0 { + resp, err := h.growthLevelClient.BatchGetUserLevelDisplayProfiles(request.Context(), &activityv1.BatchGetUserLevelDisplayProfilesRequest{ + Meta: httpkit.ActivityMeta(request), + UserIds: userIDs, + }) + if err == nil { + resourceIDs := make([]int64, 0, len(resp.GetProfiles())*3) + for _, profile := range resp.GetProfiles() { + resourceIDs = append(resourceIDs, profile.GetWealth().GetBadgeResourceId()) + resourceIDs = append(resourceIDs, profile.GetGame().GetBadgeResourceId()) + resourceIDs = append(resourceIDs, profile.GetCharm().GetBadgeResourceId()) + } + resources := h.roomResourcesByID(request, resourceIDs) + for _, profile := range resp.GetProfiles() { + if profile.GetUserId() <= 0 { + continue + } + current := roomLevelDisplayResources{Levels: roomDefaultLevelDisplay(), Badges: map[string]any{}} + roomApplyLevelDisplayTrack(current, profile.GetWealth(), resources) + roomApplyLevelDisplayTrack(current, profile.GetGame(), resources) + roomApplyLevelDisplayTrack(current, profile.GetCharm(), resources) + result[profile.GetUserId()] = current + } + } + } + h.fillRoomLevelBadgeEntitlementFallback(request, userIDs, result) + return result +} + +func (h *Handler) fillRoomLevelBadgeEntitlementFallback(request *http.Request, userIDs []int64, result map[int64]roomLevelDisplayResources) { + if h.walletClient == nil || len(userIDs) == 0 { + return + } + for _, userID := range userIDs { + current := result[userID] + if !roomLevelBadgesNeedFallback(current.Badges) { + continue + } + // wallet 暂无按多用户批量查拥有资源 RPC;这里仅在 activity 投影缺轨道时逐用户读取 active badge 权益。 + // 已有投影仍优先返回,避免注册 1 级权益覆盖用户后续升级后的等级徽章。 + resp, err := h.walletClient.ListUserResources(request.Context(), &walletv1.ListUserResourcesRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: userID, + ResourceType: "badge", + ActiveOnly: true, + }) + if err != nil { + continue + } + for _, item := range resp.GetResources() { + roomApplyLevelBadgeEntitlement(current, item) + if !roomLevelBadgesNeedFallback(current.Badges) { + break + } + } + result[userID] = current + } +} + +func roomLevelBadgesNeedFallback(levelBadges map[string]any) bool { + if levelBadges == nil { + return true + } + for _, track := range []string{"wealth", "game", "charm"} { + if _, ok := levelBadges[track]; !ok { + return true + } + } + return false +} + +func roomApplyLevelBadgeEntitlement(current roomLevelDisplayResources, item *walletv1.UserResourceEntitlement) { + if item == nil || current.Badges == nil { + return + } + resource := roomAppearanceResourceFromEntitlement(item) + track := strings.TrimSpace(fmt.Sprint(resource["level_track"])) + if track == "" || fmt.Sprint(resource["badge_kind"]) != "level" || fmt.Sprint(resource["badge_form"]) != "strip" { + return + } + if _, exists := current.Badges[track]; exists { + return + } + resourceID, _ := resource["resource_id"].(int64) + if resourceID <= 0 { + return + } + current.Badges[track] = map[string]any{ + "track": track, + "source_type": "wallet_entitlement", + "source_level": roomLevelValue(current.Levels, track), + "badge_kind": "level", + "resource_id": resourceID, + "entitlement_id": item.GetEntitlementId(), + "updated_at_ms": item.GetUpdatedAtMs(), + "resource": resource, + } +} + +func roomLevelValue(levels map[string]any, track string) int64 { + trackLevel, ok := levels[track].(map[string]any) + if !ok { + return 0 + } + switch value := trackLevel["level"].(type) { + case int64: + return value + case int32: + return int64(value) + case int: + return int64(value) + case float64: + return int64(value) + default: + return 0 + } +} + +func roomApplyLevelDisplayTrack(current roomLevelDisplayResources, item *activityv1.LevelDisplayTrackProfile, resources map[int64]map[string]any) { + if item == nil || strings.TrimSpace(item.GetTrack()) == "" { + return + } + track := strings.TrimSpace(item.GetTrack()) + current.Levels[track] = map[string]any{ + "level": item.GetLevel(), + "tier_id": item.GetTierId(), + "badge_resource_id": item.GetBadgeResourceId(), + "badge_source_level": item.GetBadgeSourceLevel(), + "avatar_frame_id": item.GetAvatarFrameResourceId(), + "updated_at_ms": item.GetUpdatedAtMs(), + "display_badge_source": "level_rule", + } + if item.GetBadgeResourceId() <= 0 { + return + } + badge := map[string]any{ + "track": track, + "source_type": "level_rule", + "source_level": item.GetBadgeSourceLevel(), + "badge_kind": "level", + "resource_id": item.GetBadgeResourceId(), + "updated_at_ms": item.GetUpdatedAtMs(), + } + if resource, ok := resources[item.GetBadgeResourceId()]; ok { + badge["resource"] = resource + } + current.Badges[track] = badge +} + +func roomDefaultLevelDisplay() map[string]any { + return map[string]any{ + "wealth": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"}, + "game": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"}, + "charm": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"}, + } +} + func (h *Handler) roomBadgeDisplayMap(request *http.Request, userIDs []int64) map[int64][]map[string]any { result := make(map[int64][]map[string]any, len(userIDs)) if h.achievementClient == nil || len(userIDs) == 0 { @@ -1624,7 +1789,7 @@ func roomAppearanceResourceFromProto(item *walletv1.Resource, entitlementID stri if item == nil || item.GetResourceId() <= 0 { return map[string]any{} } - return map[string]any{ + resource := map[string]any{ "resource_id": item.GetResourceId(), "resource_code": item.GetResourceCode(), "resource_type": item.GetResourceType(), @@ -1636,6 +1801,35 @@ func roomAppearanceResourceFromProto(item *walletv1.Resource, entitlementID stri "entitlement_id": entitlementID, "expires_at_ms": expiresAtMS, } + if item.GetResourceType() == "badge" { + badgeForm, badgeKind, levelTrack := roomBadgeMetadataFields(item.GetMetadataJson()) + resource["badge_form"] = badgeForm + resource["badge_kind"] = badgeKind + resource["level_track"] = levelTrack + } + return resource +} + +func roomBadgeMetadataFields(raw string) (string, string, string) { + payload := map[string]any{} + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil { + return "", "normal", "" + } + badgeForm := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["badge_form"]))) + if badgeForm != "strip" && badgeForm != "tile" { + badgeForm = "" + } + badgeKind := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["badge_kind"]))) + if badgeKind != "level" { + badgeKind = "normal" + } + levelTrack := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["level_track"]))) + switch levelTrack { + case "wealth", "game", "charm": + default: + levelTrack = "" + } + return badgeForm, badgeKind, levelTrack } func roomProfileAgeFromBirth(birth string, now time.Time) int32 { diff --git a/services/gateway-service/internal/transport/http/roomapi/room_view.go b/services/gateway-service/internal/transport/http/roomapi/room_view.go index 38d893e4..ffab7e39 100644 --- a/services/gateway-service/internal/transport/http/roomapi/room_view.go +++ b/services/gateway-service/internal/transport/http/roomapi/room_view.go @@ -48,15 +48,15 @@ type createRoomData struct { } type joinRoomData struct { - Result roomCommandResultData `json:"result"` - Room roomInitialData `json:"room"` - Viewer roomViewerData `json:"viewer"` - Seats []roomSeatData `json:"seats"` - ContributionRank []roomRankItemData `json:"contribution_rank"` - Profiles []roomUserProfileData `json:"profiles"` - IM roomIMData `json:"im"` - RTC roomRTCData `json:"rtc"` - ServerTimeMS int64 `json:"server_time_ms"` + Result roomCommandResultData `json:"result"` + Room roomInitialData `json:"room"` + Viewer roomViewerData `json:"viewer"` + Seats []roomSeatData `json:"seats"` + ContributionRank []roomRankItemData `json:"contribution_rank"` + Profiles []roomDisplayProfileData `json:"profiles"` + IM roomIMData `json:"im"` + RTC roomRTCData `json:"rtc"` + ServerTimeMS int64 `json:"server_time_ms"` } type roomDetailData struct { @@ -151,8 +151,10 @@ type roomDisplayProfileData struct { CountryFlag string `json:"country_flag"` VIP map[string]any `json:"vip"` Level map[string]any `json:"level"` + LevelBadges map[string]any `json:"level_badges"` Badges []map[string]any `json:"badges"` AvatarFrame map[string]any `json:"avatar_frame"` + ProfileCard map[string]any `json:"profile_card"` Vehicle map[string]any `json:"vehicle"` Charm int64 `json:"charm"` } @@ -339,13 +341,6 @@ type roomRankItemData struct { UpdatedAtMS int64 `json:"updated_at_ms,omitempty"` } -type roomUserProfileData struct { - UserID string `json:"user_id"` - DisplayUserID string `json:"display_user_id,omitempty"` - Username string `json:"username,omitempty"` - Avatar string `json:"avatar,omitempty"` -} - type roomIMData struct { GroupID string `json:"group_id"` NeedJoinGroup bool `json:"need_join_group"` diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index 35696424..2150fac0 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -29,6 +29,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { UserSocialClient: h.userSocialClient, UserHostClient: h.userHostClient, WalletClient: h.walletClient, + GrowthLevelClient: h.growthLevelClient, AchievementClient: h.achievementClient, RTCTokenConfig: tencentrtc.TokenConfig{ Enabled: h.tencentRTC.Enabled, @@ -55,6 +56,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { UserCountryClient: h.userCountryClient, UserHostClient: h.userHostClient, WalletClient: h.walletClient, + GrowthLevelClient: h.growthLevelClient, AchievementClient: h.achievementClient, BroadcastClient: h.broadcastClient, }) diff --git a/services/gateway-service/internal/transport/http/userapi/appearance_handler.go b/services/gateway-service/internal/transport/http/userapi/appearance_handler.go index 598e4f68..385acc95 100644 --- a/services/gateway-service/internal/transport/http/userapi/appearance_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/appearance_handler.go @@ -1,9 +1,12 @@ package userapi import ( + "encoding/json" "errors" + "fmt" "net/http" "strconv" + "strings" activityv1 "hyapp.local/api/proto/activity/v1" walletv1 "hyapp.local/api/proto/wallet/v1" @@ -27,6 +30,8 @@ type appearanceData struct { AvatarFrame map[string]any `json:"avatar_frame"` ProfileCard map[string]any `json:"profile_card"` Vehicle map[string]any `json:"vehicle"` + Level map[string]any `json:"level"` + LevelBadges map[string]any `json:"level_badges"` EquippedBadges []map[string]any `json:"equipped_badges"` Badges []map[string]any `json:"badges"` BadgeProfile appearanceBadgeData `json:"badge_profile"` @@ -79,6 +84,8 @@ func (h *Handler) appearanceForUser(request *http.Request, userID int64) (appear AvatarFrame: map[string]any{}, ProfileCard: map[string]any{}, Vehicle: map[string]any{}, + Level: defaultAppearanceLevelDisplay(), + LevelBadges: map[string]any{}, EquippedBadges: []map[string]any{}, Badges: []map[string]any{}, Resources: []map[string]any{}, @@ -125,10 +132,172 @@ func (h *Handler) appearanceForUser(request *http.Request, userID int64) (appear data.BadgeProfile = badgeProfile // 顶层 badges 给高频展示位直接消费;完整 profile 仍保留在 badge_profile 中。 data.Badges = badgeProfile.StripBadges + level, levelBadges := h.levelDisplayForUser(request, userID) + data.Level = level + data.LevelBadges = levelBadges + h.fillAppearanceLevelBadgeEntitlementFallback(request, userID, data.Level, data.LevelBadges) data.ServerTimeMS = badgeProfile.ServerTimeMS return data, nil } +func (h *Handler) fillAppearanceLevelBadgeEntitlementFallback(request *http.Request, userID int64, levels map[string]any, levelBadges map[string]any) { + if h.walletClient == nil || userID <= 0 || !appearanceLevelBadgesNeedFallback(levelBadges) { + return + } + // activity 的等级投影仍是首选来源;这里只在投影缺轨道时读取 wallet active 权益。 + // 注册补发的一等级徽章当前已经落在权益表里,这层兜底让老用户不等等级事件也能展示素材。 + resp, err := h.walletClient.ListUserResources(request.Context(), &walletv1.ListUserResourcesRequest{ + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + UserId: userID, + ResourceType: appearanceResourceBadge, + ActiveOnly: true, + }) + if err != nil { + return + } + for _, item := range resp.GetResources() { + appearanceApplyLevelBadgeEntitlement(levels, levelBadges, item) + if !appearanceLevelBadgesNeedFallback(levelBadges) { + return + } + } +} + +func appearanceLevelBadgesNeedFallback(levelBadges map[string]any) bool { + if levelBadges == nil { + return true + } + for _, track := range []string{"wealth", "game", "charm"} { + if _, ok := levelBadges[track]; !ok { + return true + } + } + return false +} + +func appearanceApplyLevelBadgeEntitlement(levels map[string]any, levelBadges map[string]any, item *walletv1.UserResourceEntitlement) { + if item == nil || levelBadges == nil { + return + } + resource := appearanceResourceFromEntitlement(item) + track := strings.TrimSpace(fmt.Sprint(resource["level_track"])) + if track == "" || fmt.Sprint(resource["badge_kind"]) != "level" || fmt.Sprint(resource["badge_form"]) != "strip" { + return + } + if _, exists := levelBadges[track]; exists { + return + } + resourceID, _ := resource["resource_id"].(int64) + if resourceID <= 0 { + return + } + levelBadges[track] = map[string]any{ + "track": track, + "source_type": "wallet_entitlement", + "source_level": appearanceLevelValue(levels, track), + "badge_kind": "level", + "resource_id": resourceID, + "entitlement_id": item.GetEntitlementId(), + "updated_at_ms": item.GetUpdatedAtMs(), + "resource": resource, + } +} + +func appearanceLevelValue(levels map[string]any, track string) int64 { + trackLevel, ok := levels[track].(map[string]any) + if !ok { + return 0 + } + switch value := trackLevel["level"].(type) { + case int64: + return value + case int32: + return int64(value) + case int: + return int64(value) + case float64: + return int64(value) + default: + return 0 + } +} + +func (h *Handler) levelDisplayForUser(request *http.Request, userID int64) (map[string]any, map[string]any) { + levels := defaultAppearanceLevelDisplay() + levelBadges := map[string]any{} + if h.growthLevelClient == nil { + return levels, levelBadges + } + resp, err := h.growthLevelClient.BatchGetUserLevelDisplayProfiles(request.Context(), &activityv1.BatchGetUserLevelDisplayProfilesRequest{ + Meta: httpkit.ActivityMeta(request), + UserIds: []int64{userID}, + }) + if err != nil { + // 等级徽章只是展示增强,activity 短暂失败时保留装扮和普通徽章结果。 + return levels, levelBadges + } + resourceIDs := make([]int64, 0, 3) + var profile *activityv1.UserLevelDisplayProfile + for _, item := range resp.GetProfiles() { + if item.GetUserId() == userID { + profile = item + break + } + } + if profile == nil { + return levels, levelBadges + } + resourceIDs = append(resourceIDs, profile.GetWealth().GetBadgeResourceId(), profile.GetGame().GetBadgeResourceId(), profile.GetCharm().GetBadgeResourceId()) + resources := map[int64]map[string]any{} + if resolved, err := h.appearanceResourcesByID(request, resourceIDs); err == nil { + resources = resolved + } + applyAppearanceLevelTrack(levels, levelBadges, profile.GetWealth(), resources) + applyAppearanceLevelTrack(levels, levelBadges, profile.GetGame(), resources) + applyAppearanceLevelTrack(levels, levelBadges, profile.GetCharm(), resources) + return levels, levelBadges +} + +func applyAppearanceLevelTrack(levels map[string]any, levelBadges map[string]any, item *activityv1.LevelDisplayTrackProfile, resources map[int64]map[string]any) { + if item == nil || strings.TrimSpace(item.GetTrack()) == "" { + return + } + track := strings.TrimSpace(item.GetTrack()) + levels[track] = map[string]any{ + "level": item.GetLevel(), + "tier_id": item.GetTierId(), + "badge_resource_id": item.GetBadgeResourceId(), + "badge_source_level": item.GetBadgeSourceLevel(), + "avatar_frame_id": item.GetAvatarFrameResourceId(), + "updated_at_ms": item.GetUpdatedAtMs(), + "display_badge_source": "level_rule", + } + if item.GetBadgeResourceId() <= 0 { + return + } + badge := map[string]any{ + "track": track, + "source_type": "level_rule", + "source_level": item.GetBadgeSourceLevel(), + "badge_kind": "level", + "resource_id": item.GetBadgeResourceId(), + "updated_at_ms": item.GetUpdatedAtMs(), + } + if resource, ok := resources[item.GetBadgeResourceId()]; ok { + badge["resource"] = resource + } + levelBadges[track] = badge +} + +func defaultAppearanceLevelDisplay() map[string]any { + return map[string]any{ + "wealth": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"}, + "game": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"}, + "charm": map[string]any{"level": int32(0), "tier_id": int64(0), "badge_resource_id": int64(0), "badge_source_level": int32(0), "avatar_frame_id": int64(0), "updated_at_ms": int64(0), "display_badge_source": "level_rule"}, + } +} + func (h *Handler) badgeProfileForUser(request *http.Request, userID int64) (appearanceBadgeData, error) { result := appearanceBadgeData{ StripBadges: []map[string]any{}, @@ -197,7 +366,7 @@ func appearanceResourceFromProto(item *walletv1.Resource, entitlementID string, if item == nil || item.GetResourceId() <= 0 { return map[string]any{} } - return map[string]any{ + resource := map[string]any{ "resource_id": item.GetResourceId(), "resource_code": item.GetResourceCode(), "resource_type": item.GetResourceType(), @@ -209,6 +378,35 @@ func appearanceResourceFromProto(item *walletv1.Resource, entitlementID string, "entitlement_id": entitlementID, "expires_at_ms": expiresAtMS, } + if item.GetResourceType() == appearanceResourceBadge { + badgeForm, badgeKind, levelTrack := appearanceBadgeMetadataFields(item.GetMetadataJson()) + resource["badge_form"] = badgeForm + resource["badge_kind"] = badgeKind + resource["level_track"] = levelTrack + } + return resource +} + +func appearanceBadgeMetadataFields(raw string) (string, string, string) { + payload := map[string]any{} + if err := json.Unmarshal([]byte(strings.TrimSpace(raw)), &payload); err != nil { + return "", "normal", "" + } + badgeForm := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["badge_form"]))) + if badgeForm != "strip" && badgeForm != "tile" { + badgeForm = "" + } + badgeKind := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["badge_kind"]))) + if badgeKind != "level" { + badgeKind = "normal" + } + levelTrack := strings.ToLower(strings.TrimSpace(fmt.Sprint(payload["level_track"]))) + switch levelTrack { + case "wealth", "game", "charm": + default: + levelTrack = "" + } + return badgeForm, badgeKind, levelTrack } func badgeItemsFromProto(items []*activityv1.BadgeDisplayItem, resources map[int64]map[string]any) []map[string]any { diff --git a/services/gateway-service/internal/transport/http/userapi/handler.go b/services/gateway-service/internal/transport/http/userapi/handler.go index 8c3b0cec..435fcf31 100644 --- a/services/gateway-service/internal/transport/http/userapi/handler.go +++ b/services/gateway-service/internal/transport/http/userapi/handler.go @@ -16,6 +16,7 @@ type Handler struct { userCountryClient client.UserCountryQueryClient userHostClient client.UserHostClient walletClient client.WalletClient + growthLevelClient client.GrowthLevelClient achievementClient client.AchievementClient broadcastClient client.BroadcastClient } @@ -27,6 +28,7 @@ type Config struct { UserCountryClient client.UserCountryQueryClient UserHostClient client.UserHostClient WalletClient client.WalletClient + GrowthLevelClient client.GrowthLevelClient AchievementClient client.AchievementClient BroadcastClient client.BroadcastClient } @@ -39,6 +41,7 @@ func New(config Config) *Handler { userCountryClient: config.UserCountryClient, userHostClient: config.UserHostClient, walletClient: config.WalletClient, + growthLevelClient: config.GrowthLevelClient, achievementClient: config.AchievementClient, broadcastClient: config.BroadcastClient, } diff --git a/services/room-service/internal/room/service/gift.go b/services/room-service/internal/room/service/gift.go index 8d8105ec..8530a439 100644 --- a/services/room-service/internal/room/service/gift.go +++ b/services/room-service/internal/room/service/gift.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "google.golang.org/protobuf/proto" activityv1 "hyapp.local/api/proto/activity/v1" roomeventsv1 "hyapp.local/api/proto/events/room/v1" roomv1 "hyapp.local/api/proto/room/v1" @@ -68,10 +69,6 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r // 礼物 ID 和数量是钱包查服务端价格的最小输入,客户端不再提交礼物单价。 return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "gift_id and gift_count must be valid") } - if len(cmd.TargetUserIDs) > 1 && cmd.PoolID != "" { - // 幸运礼物 activity 协议当前只有单个 target_user_id;多目标先只开放普通礼物,避免抽奖事实错绑接收方。 - return mutationResult{}, nil, xerr.New(xerr.InvalidArgument, "lucky gift requires one target user") - } roomMeta, exists, err := s.repository.GetRoomMeta(ctx, cmd.RoomID()) if err != nil { return mutationResult{}, nil, err @@ -111,6 +108,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r var walletDebitMS int64 settledCommand := cmd var luckyGift *roomv1.LuckyGiftDrawResult + var luckyGifts []*roomv1.LuckyGiftDrawResult if err := s.withLuckyGiftSendLock(ctx, luckyEnabled, cmd.ActorUserID(), func() error { // 钱包扣费在房间状态变更前完成;失败时不写 command log、不进入 Room Cell 提交态。 walletStartedAt := time.Now() @@ -131,40 +129,20 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r settledCommand.GiftTypeCode = billing.GetGiftTypeCode() settledCommand.HostPeriodDiamondAdded = billing.GetHostPeriodDiamondAdded() settledCommand.HostPeriodCycleKey = billing.GetHostPeriodCycleKey() - if !luckyEnabled && len(cmd.TargetUserIDs) == 1 { + if !luckyEnabled { // 未显式传 pool_id 的旧客户端仍可通过钱包礼物类型触发默认幸运礼物逻辑;新客户端应传明确 pool_id。 luckyEnabled = s.shouldDrawLuckyGift(cmd.PoolID, billing.GetGiftTypeCode()) } if luckyEnabled { - // 抽奖必须发生在扣费成功之后;activity-service 只接收钱包结算后的 coin_spent,不信任客户端价格。 - drawResp, err := s.luckyGift.ExecuteLuckyGiftDraw(ctx, &activityv1.ExecuteLuckyGiftDrawRequest{ - LuckyGift: &activityv1.LuckyGiftMeta{ - Meta: activityMetaFromRoom(ctx, req.GetMeta()), - CommandId: cmd.ID(), - UserId: cmd.ActorUserID(), - TargetUserId: cmd.TargetUserID, - // 目前没有独立设备 ID 字段,优先用 session_id;没有时退回 command_id,保证风控 scope 不为空。 - DeviceId: luckyGiftDeviceID(cmd), - RoomId: cmd.RoomID(), - AnchorId: luckyGiftAnchorID(roomMeta), - GiftId: cmd.GiftID, - GiftCount: cmd.GiftCount, - CoinSpent: billing.GetCoinSpent(), - // 房间链路统一使用 UTC epoch ms;不能把本地时区时间传给活动风控窗口。 - PaidAtMs: now.UTC().UnixMilli(), - PoolId: cmd.PoolID, - VisibleRegionId: roomMeta.VisibleRegionID, - }, - }) + luckyGifts, err = s.executeLuckyGiftDraws(ctx, req.GetMeta(), cmd, roomMeta, targetBillings, now) if err != nil { // 抽奖失败时整笔送礼失败并依赖 wallet 幂等重试;不能落普通礼物后丢失幸运礼物抽奖事实。 return err } - if drawResp == nil { - return xerr.New(xerr.Unavailable, "lucky gift draw response is empty") + if len(luckyGifts) > 0 { + // lucky_gifts 保留每个收礼目标的独立抽奖事实;lucky_gift 返回整次送礼的聚合表现,复用已有批量送礼的倍率累加语义。 + luckyGift = aggregateLuckyGiftResults(cmd.ID(), luckyGifts) } - // 同步返回只给当前送礼用户做即时表现;金币余额以 activity/wallet 快路径回执为准,房间开奖结果仍由 activity outbox 补偿。 - luckyGift = luckyGiftResultFromProto(drawResp.GetResult()) } return nil }); err != nil { @@ -247,6 +225,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r roomHeat: current.Heat, giftRank: cloneProtoRank(current.GiftRank), luckyGift: luckyGift, + luckyGifts: luckyGifts, commandPayload: commandPayload, walletDebitMS: walletDebitMS, roomGiftLeaderboard: &RoomGiftLeaderboardIncrement{ @@ -302,6 +281,7 @@ func (s *Service) SendGift(ctx context.Context, req *roomv1.SendGiftRequest) (*r Room: result.snapshot, Treasure: result.snapshot.GetTreasure(), LuckyGift: result.luckyGift, + LuckyGifts: result.luckyGifts, }, nil } @@ -369,6 +349,44 @@ func (s *Service) debitGiftTargets(ctx context.Context, cmd command.SendGift, ro return resp.GetAggregate(), targetBillings, nil } +func (s *Service) executeLuckyGiftDraws(ctx context.Context, meta *roomv1.RequestMeta, cmd command.SendGift, roomMeta RoomMeta, targetBillings []giftTargetBilling, now time.Time) ([]*roomv1.LuckyGiftDrawResult, error) { + results := make([]*roomv1.LuckyGiftDrawResult, 0, len(targetBillings)) + for _, targetBilling := range targetBillings { + if targetBilling.Billing == nil { + return nil, xerr.New(xerr.Unavailable, "wallet target debit response is empty") + } + // 抽奖必须按每个收礼目标独立落事实;command_id、target_user_id 和 coin_spent 都来自该目标的 wallet 子交易。 + drawResp, err := s.luckyGift.ExecuteLuckyGiftDraw(ctx, &activityv1.ExecuteLuckyGiftDrawRequest{ + LuckyGift: &activityv1.LuckyGiftMeta{ + Meta: activityMetaFromRoom(ctx, meta), + CommandId: targetBilling.CommandID, + UserId: cmd.ActorUserID(), + TargetUserId: targetBilling.TargetUserID, + // 目前没有独立设备 ID 字段,优先用 session_id;没有时退回目标子 command_id,保证每次抽奖 scope 不为空。 + DeviceId: luckyGiftDeviceID(cmd, targetBilling.CommandID), + RoomId: cmd.RoomID(), + AnchorId: luckyGiftAnchorID(roomMeta), + GiftId: cmd.GiftID, + GiftCount: cmd.GiftCount, + CoinSpent: targetBilling.Billing.GetCoinSpent(), + // 房间链路统一使用 UTC epoch ms;不能把本地时区时间传给活动风控窗口。 + PaidAtMs: now.UTC().UnixMilli(), + PoolId: cmd.PoolID, + VisibleRegionId: roomMeta.VisibleRegionID, + }, + }) + if err != nil { + return nil, err + } + if drawResp == nil || drawResp.GetResult() == nil { + return nil, xerr.New(xerr.Unavailable, "lucky gift draw response is empty") + } + // 同步返回只给当前送礼用户做即时表现;金币余额以 activity/wallet 快路径回执为准,房间开奖结果仍由 activity outbox 补偿。 + results = append(results, luckyGiftResultFromProto(drawResp.GetResult(), targetBilling.TargetUserID)) + } + return results, nil +} + func roomGiftLeaderboardCoinSpent(billing *walletv1.DebitGiftResponse) int64 { if billing == nil { return 0 @@ -419,10 +437,13 @@ func activityMetaFromRoom(ctx context.Context, meta *roomv1.RequestMeta) *activi } } -func luckyGiftDeviceID(cmd command.SendGift) string { +func luckyGiftDeviceID(cmd command.SendGift, fallbackCommandID string) string { if value := strings.TrimSpace(cmd.SessionID); value != "" { return value } + if value := strings.TrimSpace(fallbackCommandID); value != "" { + return value + } return cmd.ID() } @@ -433,7 +454,7 @@ func luckyGiftAnchorID(roomMeta RoomMeta) string { return fmt.Sprintf("%d", roomMeta.OwnerUserID) } -func luckyGiftResultFromProto(result *activityv1.LuckyGiftDrawResult) *roomv1.LuckyGiftDrawResult { +func luckyGiftResultFromProto(result *activityv1.LuckyGiftDrawResult, targetUserID int64) *roomv1.LuckyGiftDrawResult { if result == nil { return nil } @@ -457,9 +478,66 @@ func luckyGiftResultFromProto(result *activityv1.LuckyGiftDrawResult) *roomv1.Lu CreatedAtMs: result.GetCreatedAtMs(), WalletTransactionId: result.GetWalletTransactionId(), CoinBalanceAfter: result.GetCoinBalanceAfter(), + TargetUserId: targetUserID, } } +func aggregateLuckyGiftResults(commandID string, results []*roomv1.LuckyGiftDrawResult) *roomv1.LuckyGiftDrawResult { + if len(results) == 0 { + return nil + } + if len(results) == 1 { + return results[0] + } + aggregate := proto.Clone(results[0]).(*roomv1.LuckyGiftDrawResult) + aggregate.CommandId = commandID + aggregate.SelectedTierId = "batch" + aggregate.TargetUserId = 0 + aggregate.WalletTransactionId = "" + aggregate.MultiplierPpm = 0 + aggregate.BaseRewardCoins = 0 + aggregate.RoomAtmosphereRewardCoins = 0 + aggregate.ActivitySubsidyCoins = 0 + aggregate.EffectiveRewardCoins = 0 + aggregate.RewardStatus = "granted" + aggregate.StageFeedback = false + aggregate.HighMultiplier = false + aggregate.CoinBalanceAfter = 0 + for _, result := range results { + if result == nil { + continue + } + aggregate.RuleVersion = result.GetRuleVersion() + aggregate.ExperiencePool = result.GetExperiencePool() + aggregate.CreatedAtMs = result.GetCreatedAtMs() + aggregate.MultiplierPpm += result.GetMultiplierPpm() + aggregate.BaseRewardCoins += result.GetBaseRewardCoins() + aggregate.RoomAtmosphereRewardCoins += result.GetRoomAtmosphereRewardCoins() + aggregate.ActivitySubsidyCoins += result.GetActivitySubsidyCoins() + aggregate.EffectiveRewardCoins += result.GetEffectiveRewardCoins() + aggregate.StageFeedback = aggregate.StageFeedback || result.GetStageFeedback() + aggregate.HighMultiplier = aggregate.HighMultiplier || result.GetHighMultiplier() + if result.GetCoinBalanceAfter() > 0 { + // 多目标会产生多笔返奖交易;聚合字段不能表达多个交易号,只保留最后一次快路径回执后的余额给客户端刷新资产。 + aggregate.CoinBalanceAfter = result.GetCoinBalanceAfter() + } + switch result.GetRewardStatus() { + case "pending": + aggregate.RewardStatus = "pending" + case "failed": + if aggregate.RewardStatus != "pending" { + aggregate.RewardStatus = "failed" + } + case "granted": + default: + if aggregate.RewardStatus == "" { + aggregate.RewardStatus = result.GetRewardStatus() + } + } + } + return aggregate +} + func normalizeGiftTargetType(raw string) string { raw = strings.TrimSpace(raw) switch raw { diff --git a/services/room-service/internal/room/service/lucky_gift_test.go b/services/room-service/internal/room/service/lucky_gift_test.go index 2573f91f..f4a94144 100644 --- a/services/room-service/internal/room/service/lucky_gift_test.go +++ b/services/room-service/internal/room/service/lucky_gift_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "google.golang.org/protobuf/proto" activityv1 "hyapp.local/api/proto/activity/v1" roomv1 "hyapp.local/api/proto/room/v1" walletv1 "hyapp.local/api/proto/wallet/v1" @@ -16,8 +17,9 @@ import ( ) type luckyGiftTestClient struct { - checks []*activityv1.CheckLuckyGiftRequest - draws []*activityv1.ExecuteLuckyGiftDrawRequest + checks []*activityv1.CheckLuckyGiftRequest + draws []*activityv1.ExecuteLuckyGiftDrawRequest + drawResults []*activityv1.LuckyGiftDrawResult } func (c *luckyGiftTestClient) CheckLuckyGift(_ context.Context, req *activityv1.CheckLuckyGiftRequest) (*activityv1.CheckLuckyGiftResponse, error) { @@ -35,6 +37,23 @@ func (c *luckyGiftTestClient) CheckLuckyGift(_ context.Context, req *activityv1. func (c *luckyGiftTestClient) ExecuteLuckyGiftDraw(_ context.Context, req *activityv1.ExecuteLuckyGiftDrawRequest) (*activityv1.ExecuteLuckyGiftDrawResponse, error) { c.draws = append(c.draws, req) meta := req.GetLuckyGift() + if len(c.drawResults) > 0 { + index := len(c.draws) - 1 + if index >= len(c.drawResults) { + index = len(c.drawResults) - 1 + } + result := proto.Clone(c.drawResults[index]).(*activityv1.LuckyGiftDrawResult) + if result.CommandId == "" { + result.CommandId = meta.GetCommandId() + } + if result.PoolId == "" { + result.PoolId = meta.GetPoolId() + } + if result.GiftId == "" { + result.GiftId = meta.GetGiftId() + } + return &activityv1.ExecuteLuckyGiftDrawResponse{Result: result}, nil + } return &activityv1.ExecuteLuckyGiftDrawResponse{Result: &activityv1.LuckyGiftDrawResult{ DrawId: "lucky_draw_test", CommandId: meta.GetCommandId(), @@ -63,7 +82,32 @@ func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) { HeatValue: 100, GiftTypeCode: "super_lucky", }}} - luckyGift := &luckyGiftTestClient{} + luckyGift := &luckyGiftTestClient{drawResults: []*activityv1.LuckyGiftDrawResult{ + { + DrawId: "lucky_draw_target_202", + SelectedTierId: "novice_2x", + MultiplierPpm: 2_000_000, + BaseRewardCoins: 200, + EffectiveRewardCoins: 200, + RewardStatus: "granted", + WalletTransactionId: "wallet_tx_lucky_202", + CoinBalanceAfter: 8800, + CreatedAtMs: 1_779_258_000_000, + }, + { + DrawId: "lucky_draw_target_303", + SelectedTierId: "normal_3x", + MultiplierPpm: 3_000_000, + BaseRewardCoins: 300, + EffectiveRewardCoins: 300, + RewardStatus: "granted", + WalletTransactionId: "wallet_tx_lucky_303", + CoinBalanceAfter: 9100, + CreatedAtMs: 1_779_258_000_001, + StageFeedback: true, + HighMultiplier: true, + }, + }} svc := roomservice.New(roomservice.Config{ NodeID: "node-lucky-test", LeaseTTL: 10 * time.Second, @@ -99,6 +143,9 @@ func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) { if resp.GetLuckyGift().GetDrawId() != "lucky_draw_test" || resp.GetLuckyGift().GetMultiplierPpm() != 2_000_000 || resp.GetLuckyGift().GetEffectiveRewardCoins() != 200 || resp.GetLuckyGift().GetRewardStatus() != "granted" || resp.GetLuckyGift().GetWalletTransactionId() != "wallet_tx_lucky" || resp.GetLuckyGift().GetCoinBalanceAfter() != 8800 { t.Fatalf("lucky gift result mismatch: %+v", resp.GetLuckyGift()) } + if len(resp.GetLuckyGifts()) != 1 || resp.GetLuckyGifts()[0].GetTargetUserId() != viewerID { + t.Fatalf("single-target lucky_gifts response mismatch: %+v", resp.GetLuckyGifts()) + } if len(luckyGift.checks) != 1 || luckyGift.checks[0].GetPoolId() != "super_lucky" || luckyGift.checks[0].GetGiftId() != "rose" { t.Fatalf("lucky check request mismatch: %+v", luckyGift.checks) } @@ -114,3 +161,76 @@ func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) { t.Fatalf("lucky draw meta mismatch: %+v", drawMeta) } } + +func TestSendGiftReturnsLuckyGiftDrawResultsForMultipleTargets(t *testing.T) { + ctx := context.Background() + repository := mysqltest.NewRepository(t) + wallet := &treasureTestWallet{debits: []*walletv1.DebitGiftResponse{ + {BillingReceiptId: "receipt-lucky-202", CoinSpent: 100, ChargeAmount: 100, GiftPointAdded: 100, HeatValue: 100, GiftTypeCode: "super_lucky"}, + {BillingReceiptId: "receipt-lucky-303", CoinSpent: 200, ChargeAmount: 200, GiftPointAdded: 200, HeatValue: 200, GiftTypeCode: "super_lucky"}, + }} + luckyGift := &luckyGiftTestClient{} + svc := roomservice.New(roomservice.Config{ + NodeID: "node-lucky-multi-test", + LeaseTTL: 10 * time.Second, + RankLimit: 20, + SnapshotEveryN: 1, + }, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher(), luckyGift) + + roomID := "room-lucky-gift-multi" + ownerID := int64(101) + firstTargetID := int64(202) + secondTargetID := int64(303) + createTreasureRoom(t, ctx, svc, roomID, ownerID, 9001) + joinTreasureRoom(t, ctx, svc, roomID, firstTargetID) + joinTreasureRoom(t, ctx, svc, roomID, secondTargetID) + + resp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{ + Meta: &roomv1.RequestMeta{ + RequestId: "req-lucky-multi", + CommandId: "cmd-lucky-multi", + ActorUserId: ownerID, + RoomId: roomID, + SessionId: "device-session-multi", + AppCode: appcode.Default, + SentAtMs: time.Date(2026, 5, 20, 12, 0, 0, 0, time.UTC).UnixMilli(), + }, + TargetType: "user", + TargetUserIds: []int64{firstTargetID, secondTargetID}, + GiftId: "rose", + GiftCount: 1, + PoolId: "super_lucky", + }) + if err != nil { + t.Fatalf("send multi-target lucky gift failed: %v", err) + } + if len(luckyGift.checks) != 1 || luckyGift.checks[0].GetPoolId() != "super_lucky" { + t.Fatalf("multi-target lucky gift should check pool once: %+v", luckyGift.checks) + } + if len(luckyGift.draws) != 2 { + t.Fatalf("multi-target lucky gift should draw once per target, got %d", len(luckyGift.draws)) + } + firstDraw := luckyGift.draws[0].GetLuckyGift() + secondDraw := luckyGift.draws[1].GetLuckyGift() + if firstDraw.GetCommandId() != "cmd-lucky-multi:target:202" || firstDraw.GetTargetUserId() != firstTargetID || firstDraw.GetCoinSpent() != 100 { + t.Fatalf("first target lucky draw mismatch: %+v", firstDraw) + } + if secondDraw.GetCommandId() != "cmd-lucky-multi:target:303" || secondDraw.GetTargetUserId() != secondTargetID || secondDraw.GetCoinSpent() != 200 { + t.Fatalf("second target lucky draw mismatch: %+v", secondDraw) + } + if resp.GetLuckyGift().GetCommandId() != "cmd-lucky-multi" || + resp.GetLuckyGift().GetTargetUserId() != 0 || + resp.GetLuckyGift().GetSelectedTierId() != "batch" || + resp.GetLuckyGift().GetMultiplierPpm() != 5_000_000 || + resp.GetLuckyGift().GetEffectiveRewardCoins() != 500 || + resp.GetLuckyGift().GetWalletTransactionId() != "" || + resp.GetLuckyGift().GetCoinBalanceAfter() != 9100 || + !resp.GetLuckyGift().GetStageFeedback() || + !resp.GetLuckyGift().GetHighMultiplier() || + len(resp.GetLuckyGifts()) != 2 { + t.Fatalf("multi-target lucky response must include aggregate and list results: %+v", resp) + } + if resp.GetLuckyGifts()[0].GetCommandId() != "cmd-lucky-multi:target:202" || resp.GetLuckyGifts()[0].GetMultiplierPpm() != 2_000_000 || resp.GetLuckyGifts()[1].GetCommandId() != "cmd-lucky-multi:target:303" || resp.GetLuckyGifts()[1].GetMultiplierPpm() != 3_000_000 || resp.GetLuckyGifts()[1].GetTargetUserId() != secondTargetID { + t.Fatalf("multi-target lucky_gifts response mismatch: %+v", resp.GetLuckyGifts()) + } +} diff --git a/services/room-service/internal/room/service/service.go b/services/room-service/internal/room/service/service.go index c136ecd3..0a89e6ce 100644 --- a/services/room-service/internal/room/service/service.go +++ b/services/room-service/internal/room/service/service.go @@ -113,6 +113,8 @@ type mutationResult struct { giftRank []*roomv1.RankItem // luckyGift 是扣费成功后同步执行的幸运礼物抽奖结果;普通礼物保持 nil。 luckyGift *roomv1.LuckyGiftDrawResult + // luckyGifts 是多目标幸运礼物的逐接收方抽奖结果;单目标时只包含 luckyGift。 + luckyGifts []*roomv1.LuckyGiftDrawResult // commandPayload 允许少数命令把外部依赖的结算快照写入 command log,用于恢复。 commandPayload []byte // syncEvent 标记该命令存在腾讯云 IM 房间消息形态;实际投递只由 outbox worker 异步执行。 diff --git a/services/user-service/internal/app/app.go b/services/user-service/internal/app/app.go index ebc03fd1..21eb649f 100644 --- a/services/user-service/internal/app/app.go +++ b/services/user-service/internal/app/app.go @@ -168,6 +168,7 @@ func New(cfg config.Config) (*App, error) { } var activityConn *grpc.ClientConn var registrationRewardIssuer authservice.RegistrationRewardIssuer + var registrationLevelBadgeIssuer authservice.RegistrationLevelBadgeIssuer if cfg.ActivityService.Enabled { activityConn, err = grpcclient.Dial(cfg.ActivityService.Addr, grpcclient.Config{ DefaultTimeout: time.Duration(cfg.ActivityService.RequestTimeoutMs) * time.Millisecond, @@ -183,7 +184,9 @@ func New(cfg config.Config) (*App, error) { _ = mysqlRepo.Close() return nil, err } - registrationRewardIssuer = activityclient.NewGRPC(activityConn) + activityClient := activityclient.NewGRPC(activityConn) + registrationRewardIssuer = activityClient + registrationLevelBadgeIssuer = activityClient } // auth service 负责登录、session 和 token;用户主数据和短号事务仍通过各自领域存储完成。 @@ -214,6 +217,7 @@ func New(cfg config.Config) (*App, error) { authservice.WithIPDecisionCache(ipDecisionCache), authservice.WithIPGeoProviders(authservice.BuildIPGeoProviders(cfg.LoginRisk.Providers)), authservice.WithRegistrationRewardIssuer(registrationRewardIssuer), + authservice.WithRegistrationLevelBadgeIssuer(registrationLevelBadgeIssuer), authservice.WithIMAccountImporter(imAccountImporter), ) // user service 负责用户主状态和 display_user_id/靓号用例,不进入房间高频流程。 diff --git a/services/user-service/internal/integration/activityclient/client.go b/services/user-service/internal/integration/activityclient/client.go index 9bb78767..1fae382e 100644 --- a/services/user-service/internal/integration/activityclient/client.go +++ b/services/user-service/internal/integration/activityclient/client.go @@ -11,19 +11,41 @@ import ( ) type Client struct { - client activityv1.RegistrationRewardServiceClient + registrationRewardClient activityv1.RegistrationRewardServiceClient + growthLevelClient activityv1.GrowthLevelServiceClient } func NewGRPC(conn grpc.ClientConnInterface) *Client { - return &Client{client: activityv1.NewRegistrationRewardServiceClient(conn)} + return &Client{ + registrationRewardClient: activityv1.NewRegistrationRewardServiceClient(conn), + growthLevelClient: activityv1.NewGrowthLevelServiceClient(conn), + } } // IssueRegistrationReward 只发起注册奖励幂等命令;可领取判断和实际发放都在 activity-service。 func (c *Client) IssueRegistrationReward(ctx context.Context, command authservice.RegistrationRewardCommand) error { - if c == nil || c.client == nil { + if c == nil || c.registrationRewardClient == nil { return nil } - _, err := c.client.IssueRegistrationReward(ctx, &activityv1.IssueRegistrationRewardRequest{ + _, err := c.registrationRewardClient.IssueRegistrationReward(ctx, &activityv1.IssueRegistrationRewardRequest{ + Meta: &activityv1.RequestMeta{ + RequestId: command.RequestID, + Caller: "user-service", + AppCode: command.AppCode, + SentAtMs: time.Now().UnixMilli(), + }, + UserId: command.UserID, + CommandId: command.CommandID, + }) + return err +} + +// IssueRegistrationLevelBadges 发起注册等级徽章发放;资源选择和钱包发放都在 activity-service。 +func (c *Client) IssueRegistrationLevelBadges(ctx context.Context, command authservice.RegistrationLevelBadgeCommand) error { + if c == nil || c.growthLevelClient == nil { + return nil + } + _, err := c.growthLevelClient.IssueRegistrationLevelBadges(ctx, &activityv1.IssueRegistrationLevelBadgesRequest{ Meta: &activityv1.RequestMeta{ RequestId: command.RequestID, Caller: "user-service", diff --git a/services/user-service/internal/service/auth/service.go b/services/user-service/internal/service/auth/service.go index 290fa339..87632096 100644 --- a/services/user-service/internal/service/auth/service.go +++ b/services/user-service/internal/service/auth/service.go @@ -110,6 +110,19 @@ type RegistrationRewardIssuer interface { IssueRegistrationReward(ctx context.Context, command RegistrationRewardCommand) error } +// RegistrationLevelBadgeCommand 是三方首次注册成功后发往 activity-service 的等级徽章发放命令。 +type RegistrationLevelBadgeCommand struct { + AppCode string + RequestID string + UserID int64 + CommandID string +} + +// RegistrationLevelBadgeIssuer 隔离注册等级徽章发放 RPC;失败不能回滚用户注册主事务。 +type RegistrationLevelBadgeIssuer interface { + IssueRegistrationLevelBadges(ctx context.Context, command RegistrationLevelBadgeCommand) error +} + // IMAccountImporter 把 user-service 用户导入腾讯 IM 账号体系。 type IMAccountImporter interface { ImportAccount(ctx context.Context, userID int64, nickname string, faceURL string) error @@ -199,6 +212,8 @@ type Service struct { ipGeoProviders []IPGeoProvider // registrationRewardIssuer 在注册事务提交后异步式触发奖励;失败只记录日志,不回滚用户注册。 registrationRewardIssuer RegistrationRewardIssuer + // registrationLevelBadgeIssuer 在注册事务提交后补发三条等级轨道的 1 级徽章。 + registrationLevelBadgeIssuer RegistrationLevelBadgeIssuer // imAccountImporter 在用户创建成功后补齐腾讯 IM 账号;UserSig 入口仍会兜底补偿历史账号。 imAccountImporter IMAccountImporter } @@ -282,6 +297,15 @@ func WithRegistrationRewardIssuer(issuer RegistrationRewardIssuer) Option { } } +// WithRegistrationLevelBadgeIssuer 注入注册等级徽章发放 client;nil 表示本环境不启用该集成。 +func WithRegistrationLevelBadgeIssuer(issuer RegistrationLevelBadgeIssuer) Option { + return func(s *Service) { + if issuer != nil { + s.registrationLevelBadgeIssuer = issuer + } + } +} + // WithIMAccountImporter 注入腾讯 IM 账号导入边界;nil 表示当前环境不触发真实 IM 副作用。 func WithIMAccountImporter(importer IMAccountImporter) Option { return func(s *Service) { diff --git a/services/user-service/internal/service/auth/third_party.go b/services/user-service/internal/service/auth/third_party.go index 9aa39269..33a7b0bf 100644 --- a/services/user-service/internal/service/auth/third_party.go +++ b/services/user-service/internal/service/auth/third_party.go @@ -177,6 +177,7 @@ func (s *Service) createThirdPartyUser(ctx context.Context, provider string, pro s.importIMAccountAfterCommit(ctx, user, meta) s.enqueueLoginIPRiskJob(ctx, meta, token, normalizeThirdPartyChannel(provider), registration.Platform, registration.Language, registration.Timezone) s.issueRegistrationRewardAfterCommit(ctx, registration.AppCode, user.UserID, meta) + s.issueRegistrationLevelBadgesAfterCommit(ctx, registration.AppCode, user.UserID, meta) return token, true, nil } if xerr.IsCode(err, xerr.DisplayUserIDExists) { @@ -225,6 +226,27 @@ func (s *Service) issueRegistrationRewardAfterCommit(ctx context.Context, appCod } } +func (s *Service) issueRegistrationLevelBadgesAfterCommit(ctx context.Context, appCode string, userID int64, meta Meta) { + if s.registrationLevelBadgeIssuer == nil { + return + } + commandID := fmt.Sprintf("registration_level_badges:%s:%d", appcode.Normalize(appCode), userID) + if err := s.registrationLevelBadgeIssuer.IssueRegistrationLevelBadges(ctx, RegistrationLevelBadgeCommand{ + AppCode: appcode.Normalize(appCode), + RequestID: meta.RequestID, + UserID: userID, + CommandID: commandID, + }); err != nil { + // 等级徽章是注册后置权益;用户注册事务已经提交,只能依赖同 command_id 重试或补发修复。 + logx.Warn(logx.With(ctx, slog.String("request_id", meta.RequestID), slog.String("app_code", appcode.Normalize(appCode))), "registration_level_badges_issue_failed", + slog.String("component", "user_auth"), + slog.Int64("user_id", userID), + slog.String("command_id", commandID), + slog.String("error", err.Error()), + ) + } +} + func normalizeThirdPartyRegistration(registration authdomain.ThirdPartyRegistration, meta Meta) authdomain.ThirdPartyRegistration { // 注册资料来自公网入口,进入领域前统一裁剪空白,避免数据库里出现不可见差异。 registration.Username = strings.TrimSpace(registration.Username) diff --git a/services/wallet-service/configs/config.docker.yaml b/services/wallet-service/configs/config.docker.yaml index fa176a70..a29f18b0 100644 --- a/services/wallet-service/configs/config.docker.yaml +++ b/services/wallet-service/configs/config.docker.yaml @@ -38,6 +38,10 @@ rocketmq: enabled: true topic: "hyapp_wallet_outbox" producer_group: "hyapp-wallet-outbox-producer" + realtime_outbox: + enabled: true + topic: "hyapp_wallet_realtime_outbox" + producer_group: "hyapp-wallet-realtime-outbox-producer" google_play: enabled: false package_name: "" @@ -50,8 +54,22 @@ google_play: outbox_worker: enabled: true poll_interval: "1s" - batch_size: 100 + batch_size: 200 + concurrency: 4 publish_timeout: "3s" max_retry_count: 10 initial_backoff: "5s" max_backoff: "5m" +realtime_outbox_worker: + enabled: true + poll_interval: "500ms" + batch_size: 50 + concurrency: 2 + publish_timeout: "3s" + max_retry_count: 10 + initial_backoff: "2s" + max_backoff: "1m" + event_types: + - "WalletRedPacketCreated" + - "WalletRedPacketClaimed" + - "WalletRedPacketRefunded" diff --git a/services/wallet-service/configs/config.tencent.example.yaml b/services/wallet-service/configs/config.tencent.example.yaml index 39c8a358..bfdf1b2f 100644 --- a/services/wallet-service/configs/config.tencent.example.yaml +++ b/services/wallet-service/configs/config.tencent.example.yaml @@ -38,6 +38,10 @@ rocketmq: enabled: true topic: "hyapp_wallet_outbox" producer_group: "hyapp-wallet-outbox-producer" + realtime_outbox: + enabled: true + topic: "hyapp_wallet_realtime_outbox" + producer_group: "hyapp-wallet-realtime-outbox-producer" google_play: enabled: false package_name: "com.example.hyapp" @@ -50,8 +54,22 @@ google_play: outbox_worker: enabled: true poll_interval: "1s" - batch_size: 100 + batch_size: 200 + concurrency: 8 publish_timeout: "3s" max_retry_count: 10 initial_backoff: "5s" max_backoff: "5m" +realtime_outbox_worker: + enabled: true + poll_interval: "500ms" + batch_size: 50 + concurrency: 4 + publish_timeout: "3s" + max_retry_count: 10 + initial_backoff: "2s" + max_backoff: "1m" + event_types: + - "WalletRedPacketCreated" + - "WalletRedPacketClaimed" + - "WalletRedPacketRefunded" diff --git a/services/wallet-service/configs/config.yaml b/services/wallet-service/configs/config.yaml index dfc32b74..1d6580af 100644 --- a/services/wallet-service/configs/config.yaml +++ b/services/wallet-service/configs/config.yaml @@ -38,6 +38,10 @@ rocketmq: enabled: false topic: "hyapp_wallet_outbox" producer_group: "hyapp-wallet-outbox-producer" + realtime_outbox: + enabled: false + topic: "hyapp_wallet_realtime_outbox" + producer_group: "hyapp-wallet-realtime-outbox-producer" google_play: enabled: false package_name: "" @@ -51,7 +55,21 @@ outbox_worker: enabled: false poll_interval: "1s" batch_size: 100 + concurrency: 1 publish_timeout: "3s" max_retry_count: 10 initial_backoff: "5s" max_backoff: "5m" +realtime_outbox_worker: + enabled: false + poll_interval: "500ms" + batch_size: 50 + concurrency: 2 + publish_timeout: "3s" + max_retry_count: 10 + initial_backoff: "2s" + max_backoff: "1m" + event_types: + - "WalletRedPacketCreated" + - "WalletRedPacketClaimed" + - "WalletRedPacketRefunded" diff --git a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql index a3b1a777..09d2f7a8 100644 --- a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql +++ b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql @@ -75,6 +75,8 @@ CREATE TABLE IF NOT EXISTS wallet_outbox ( PRIMARY KEY (app_code, event_id), KEY idx_wallet_outbox_pending (app_code, status, next_retry_at_ms, created_at_ms, event_id), KEY idx_wallet_outbox_claim (app_code, status, lock_until_ms, created_at_ms, event_id), + KEY idx_wallet_outbox_event_pending (app_code, status, event_type, next_retry_at_ms, created_at_ms, event_id), + KEY idx_wallet_outbox_event_claim (app_code, status, event_type, lock_until_ms, created_at_ms, event_id), KEY idx_wallet_outbox_retention (app_code, status, updated_at_ms, event_id), KEY idx_wallet_outbox_event_created (app_code, event_type, created_at_ms, event_id), KEY idx_wallet_outbox_asset_event_created (app_code, asset_type, event_type, created_at_ms, event_id), @@ -346,6 +348,25 @@ PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; +-- 实时红包/房间 UI 事件按 event_type 独立 claim;缺这个索引会在通用账务 backlog 上扫描,仍然可能延迟弹窗。 +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'wallet_outbox' AND INDEX_NAME = 'idx_wallet_outbox_event_pending') = 0, + 'ALTER TABLE wallet_outbox ADD INDEX idx_wallet_outbox_event_pending (app_code, status, event_type, next_retry_at_ms, created_at_ms, event_id)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + +SET @ddl := IF( + (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'wallet_outbox' AND INDEX_NAME = 'idx_wallet_outbox_event_claim') = 0, + 'ALTER TABLE wallet_outbox ADD INDEX idx_wallet_outbox_event_claim (app_code, status, event_type, lock_until_ms, created_at_ms, event_id)', + 'SELECT 1' +); +PREPARE stmt FROM @ddl; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; + SET @ddl := IF( (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'wallet_outbox' AND INDEX_NAME = 'idx_wallet_outbox_retention') = 0, 'ALTER TABLE wallet_outbox ADD INDEX idx_wallet_outbox_retention (app_code, status, updated_at_ms, event_id)', diff --git a/services/wallet-service/internal/app/app.go b/services/wallet-service/internal/app/app.go index 20d7b358..c8ec00a7 100644 --- a/services/wallet-service/internal/app/app.go +++ b/services/wallet-service/internal/app/app.go @@ -31,23 +31,26 @@ import ( // App 装配 wallet-service gRPC 入口。 type App struct { - server *grpc.Server - listener net.Listener - health *grpchealth.ServingChecker - healthHTTP *healthhttp.Server - mysqlRepo *mysqlstorage.Repository - walletSvc *walletservice.Service - activityConn *grpc.ClientConn - outboxProducer *rocketmqx.Producer - projectionConsumer *rocketmqx.Consumer - outboxWorkerCfg config.OutboxWorkerConfig - projectionWorkerCfg config.ProjectionWorkerConfig - walletOutboxTopic string - nodeID string - redPacketExpiryWorkerCfg config.RedPacketExpiryWorkerConfig - stopWorker context.CancelFunc - workers sync.WaitGroup - closeOnce sync.Once + server *grpc.Server + listener net.Listener + health *grpchealth.ServingChecker + healthHTTP *healthhttp.Server + mysqlRepo *mysqlstorage.Repository + walletSvc *walletservice.Service + activityConn *grpc.ClientConn + outboxProducer *rocketmqx.Producer + realtimeOutboxProducer *rocketmqx.Producer + projectionConsumer *rocketmqx.Consumer + outboxWorkerCfg config.OutboxWorkerConfig + realtimeOutboxWorkerCfg config.OutboxWorkerConfig + projectionWorkerCfg config.ProjectionWorkerConfig + walletOutboxTopic string + realtimeWalletOutboxTopic string + nodeID string + redPacketExpiryWorkerCfg config.RedPacketExpiryWorkerConfig + stopWorker context.CancelFunc + workers sync.WaitGroup + closeOnce sync.Once } // New 初始化 wallet-service。 @@ -74,7 +77,7 @@ func New(cfg config.Config) (*App, error) { } var outboxProducer *rocketmqx.Producer if cfg.RocketMQ.WalletOutbox.Enabled { - outboxProducer, err = rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ)) + outboxProducer, err = rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.ProducerGroup)) if err != nil { _ = activityConn.Close() _ = listener.Close() @@ -82,15 +85,24 @@ func New(cfg config.Config) (*App, error) { return nil, err } } + var realtimeOutboxProducer *rocketmqx.Producer + if cfg.RocketMQ.RealtimeOutbox.Enabled { + realtimeOutboxProducer, err = rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ, cfg.RocketMQ.RealtimeOutbox.ProducerGroup)) + if err != nil { + shutdownProducers(outboxProducer) + _ = activityConn.Close() + _ = listener.Close() + _ = repository.Close() + return nil, err + } + } server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("wallet-service"))) svc := walletservice.New(repository, client.NewActivityAchievementClient(activityConn)) if cfg.GooglePlay.Enabled { googleClient, err := googleplay.New(cfg.GooglePlay) if err != nil { - if outboxProducer != nil { - _ = outboxProducer.Shutdown() - } + shutdownProducers(outboxProducer, realtimeOutboxProducer) _ = activityConn.Close() _ = listener.Close() _ = repository.Close() @@ -107,9 +119,7 @@ func New(cfg config.Config) (*App, error) { healthgrpc.RegisterHealthServer(server, grpchealth.NewServer(health)) healthHTTP, err := healthhttp.New(cfg.HealthHTTPAddr, cfg.NodeID, health) if err != nil { - if outboxProducer != nil { - _ = outboxProducer.Shutdown() - } + shutdownProducers(outboxProducer, realtimeOutboxProducer) _ = activityConn.Close() _ = listener.Close() _ = repository.Close() @@ -117,9 +127,7 @@ func New(cfg config.Config) (*App, error) { } projectionConsumer, err := newWalletProjectionConsumer(cfg, svc) if err != nil { - if outboxProducer != nil { - _ = outboxProducer.Shutdown() - } + shutdownProducers(outboxProducer, realtimeOutboxProducer) _ = activityConn.Close() _ = listener.Close() _ = repository.Close() @@ -127,20 +135,23 @@ func New(cfg config.Config) (*App, error) { } return &App{ - server: server, - listener: listener, - health: health, - healthHTTP: healthHTTP, - mysqlRepo: repository, - walletSvc: svc, - activityConn: activityConn, - outboxProducer: outboxProducer, - projectionConsumer: projectionConsumer, - outboxWorkerCfg: cfg.OutboxWorker, - projectionWorkerCfg: cfg.ProjectionWorker, - walletOutboxTopic: cfg.RocketMQ.WalletOutbox.Topic, - nodeID: cfg.NodeID, - redPacketExpiryWorkerCfg: cfg.RedPacketExpiryWorker, + server: server, + listener: listener, + health: health, + healthHTTP: healthHTTP, + mysqlRepo: repository, + walletSvc: svc, + activityConn: activityConn, + outboxProducer: outboxProducer, + realtimeOutboxProducer: realtimeOutboxProducer, + projectionConsumer: projectionConsumer, + outboxWorkerCfg: cfg.OutboxWorker, + realtimeOutboxWorkerCfg: cfg.RealtimeOutboxWorker, + projectionWorkerCfg: cfg.ProjectionWorker, + walletOutboxTopic: cfg.RocketMQ.WalletOutbox.Topic, + realtimeWalletOutboxTopic: cfg.RocketMQ.RealtimeOutbox.Topic, + nodeID: cfg.NodeID, + redPacketExpiryWorkerCfg: cfg.RedPacketExpiryWorker, }, nil } @@ -189,12 +200,16 @@ func (a *App) runBackgroundWorkers() { ctx, cancel := context.WithCancel(context.Background()) a.stopWorker = cancel if a.outboxWorkerCfg.Enabled && a.outboxProducer != nil { - outboxWorkerID := "wallet-outbox-" + a.nodeID - a.workers.Add(1) - go func() { - defer a.workers.Done() - a.runWalletOutboxWorker(ctx, outboxWorkerID) - }() + excludedRealtimeTypes := []string(nil) + if a.realtimeOutboxWorkerCfg.Enabled && a.realtimeOutboxProducer != nil { + // 普通账务 worker 在实时通道可用时主动跳过红包 UI 事件,避免两个 worker 抢同一行, + // 也避免红包事实继续排在 WalletBalanceChanged 和礼物流水之后。 + excludedRealtimeTypes = a.realtimeOutboxWorkerCfg.EventTypes + } + a.startWalletOutboxWorkers(ctx, "wallet-outbox", a.outboxWorkerCfg, a.outboxProducer, a.walletOutboxTopic, nil, excludedRealtimeTypes) + } + if a.realtimeOutboxWorkerCfg.Enabled && a.realtimeOutboxProducer != nil { + a.startWalletOutboxWorkers(ctx, "wallet-realtime-outbox", a.realtimeOutboxWorkerCfg, a.realtimeOutboxProducer, a.realtimeWalletOutboxTopic, a.realtimeOutboxWorkerCfg.EventTypes, nil) } if a.redPacketExpiryWorkerCfg.Enabled { redPacketWorkerID := "red-packet-expiry-" + a.nodeID @@ -228,12 +243,25 @@ func (a *App) runBackgroundWorkers() { } } -func (a *App) runWalletOutboxWorker(ctx context.Context, workerID string) { - options := a.outboxWorkerCfg +func (a *App) startWalletOutboxWorkers(ctx context.Context, workerName string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) { + if options.Concurrency <= 0 { + options.Concurrency = 1 + } + for workerIndex := 1; workerIndex <= options.Concurrency; workerIndex++ { + workerID := fmt.Sprintf("%s-%s-%02d", workerName, a.nodeID, workerIndex) + a.workers.Add(1) + go func() { + defer a.workers.Done() + a.runWalletOutboxWorker(ctx, workerID, options, producer, topic, includeEventTypes, excludeEventTypes) + }() + } +} + +func (a *App) runWalletOutboxWorker(ctx context.Context, workerID string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) { ticker := time.NewTicker(options.PollInterval) defer ticker.Stop() for { - processed, err := a.processWalletOutboxBatch(ctx, workerID, options) + processed, err := a.processWalletOutboxBatch(ctx, workerID, options, producer, topic, includeEventTypes, excludeEventTypes) if err != nil && !errors.Is(err, context.Canceled) { logx.Error(ctx, "wallet_outbox_publish_failed", err, slog.String("worker_id", workerID)) } @@ -248,11 +276,14 @@ func (a *App) runWalletOutboxWorker(ctx context.Context, workerID string) { } } -func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, options config.OutboxWorkerConfig) (int, error) { - if a.mysqlRepo == nil || a.outboxProducer == nil { +func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, options config.OutboxWorkerConfig, producer *rocketmqx.Producer, topic string, includeEventTypes []string, excludeEventTypes []string) (int, error) { + if a.mysqlRepo == nil || producer == nil { return 0, nil } - records, err := a.mysqlRepo.ClaimPendingWalletOutbox(ctx, workerID, options.BatchSize, time.Now().UTC().Add(options.PublishTimeout).UnixMilli()) + records, err := a.mysqlRepo.ClaimPendingWalletOutboxFiltered(ctx, workerID, options.BatchSize, time.Now().UTC().Add(options.PublishTimeout).UnixMilli(), mysqlstorage.WalletOutboxClaimFilter{ + IncludeEventTypes: includeEventTypes, + ExcludeEventTypes: excludeEventTypes, + }) if err != nil { return 0, err } @@ -267,7 +298,7 @@ func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, opt continue } publishCtx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), record.AppCode), options.PublishTimeout) - err := a.publishWalletOutboxRecord(publishCtx, record) + err := a.publishWalletOutboxRecord(publishCtx, producer, topic, record) cancel() if err != nil { nextRetryAtMS := time.Now().UTC().Add(walletOutboxBackoff(record.RetryCount+1, options)).UnixMilli() @@ -289,7 +320,7 @@ func (a *App) processWalletOutboxBatch(ctx context.Context, workerID string, opt return len(records), nil } -func (a *App) publishWalletOutboxRecord(ctx context.Context, record mysqlstorage.WalletOutboxRecord) error { +func (a *App) publishWalletOutboxRecord(ctx context.Context, producer *rocketmqx.Producer, topic string, record mysqlstorage.WalletOutboxRecord) error { body, err := walletmq.EncodeWalletOutboxMessage(walletmq.WalletOutboxMessage{ AppCode: record.AppCode, EventID: record.EventID, @@ -306,8 +337,8 @@ func (a *App) publishWalletOutboxRecord(ctx context.Context, record mysqlstorage if err != nil { return err } - return a.outboxProducer.SendSync(ctx, rocketmqx.Message{ - Topic: a.walletOutboxTopic, + return producer.SendSync(ctx, rocketmqx.Message{ + Topic: topic, Tag: walletmq.TagWalletOutboxEvent, Keys: []string{record.EventID, record.TransactionID, record.CommandID}, Body: body, @@ -349,14 +380,20 @@ func (a *App) startMQ() error { return err } } - if a.projectionConsumer != nil { - if err := a.projectionConsumer.Start(); err != nil { + if a.realtimeOutboxProducer != nil { + if err := a.realtimeOutboxProducer.Start(); err != nil { if a.outboxProducer != nil { _ = a.outboxProducer.Shutdown() } return err } } + if a.projectionConsumer != nil { + if err := a.projectionConsumer.Start(); err != nil { + shutdownProducers(a.outboxProducer, a.realtimeOutboxProducer) + return err + } + } return nil } @@ -371,6 +408,11 @@ func (a *App) shutdownMQ() { logx.Warn(context.Background(), "rocketmq_producer_shutdown_failed", slog.String("error", err.Error())) } } + if a.realtimeOutboxProducer != nil { + if err := a.realtimeOutboxProducer.Shutdown(); err != nil { + logx.Warn(context.Background(), "rocketmq_realtime_producer_shutdown_failed", slog.String("error", err.Error())) + } + } } func newWalletProjectionConsumer(cfg config.Config, svc *walletservice.Service) (*rocketmqx.Consumer, error) { @@ -413,7 +455,7 @@ func rocketMQProjectionConsumerConfig(cfg config.RocketMQConfig, projection conf } } -func rocketMQProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerConfig { +func rocketMQProducerConfig(cfg config.RocketMQConfig, groupName string) rocketmqx.ProducerConfig { return rocketmqx.ProducerConfig{ EndpointConfig: rocketmqx.EndpointConfig{ NameServers: cfg.NameServers, @@ -424,12 +466,20 @@ func rocketMQProducerConfig(cfg config.RocketMQConfig) rocketmqx.ProducerConfig Namespace: cfg.Namespace, VIPChannel: cfg.VIPChannel, }, - GroupName: cfg.WalletOutbox.ProducerGroup, + GroupName: groupName, SendTimeout: cfg.SendTimeout, Retry: cfg.Retry, } } +func shutdownProducers(producers ...*rocketmqx.Producer) { + for _, producer := range producers { + if producer != nil { + _ = producer.Shutdown() + } + } +} + func (a *App) runHealthHTTP() { if a.healthHTTP == nil { return diff --git a/services/wallet-service/internal/config/config.go b/services/wallet-service/internal/config/config.go index b89f2ab3..961244e6 100644 --- a/services/wallet-service/internal/config/config.go +++ b/services/wallet-service/internal/config/config.go @@ -29,6 +29,8 @@ type Config struct { GooglePlay GooglePlayConfig `yaml:"google_play"` // OutboxWorker 控制 wallet_outbox 到 MQ 的补偿投递。 OutboxWorker OutboxWorkerConfig `yaml:"outbox_worker"` + // RealtimeOutboxWorker 控制红包等实时 UI 事件到独立 MQ topic 的补偿投递。 + RealtimeOutboxWorker OutboxWorkerConfig `yaml:"realtime_outbox_worker"` // ProjectionWorker 控制 wallet_outbox MQ 到本服务读模型的投影消费。 ProjectionWorker ProjectionWorkerConfig `yaml:"projection_worker"` // MySQLAutoMigrate 保留给显式本地迁移;线上 schema 由发布流程或 initdb 管理。 @@ -57,6 +59,7 @@ type RocketMQConfig struct { SendTimeout time.Duration `yaml:"send_timeout"` Retry int `yaml:"retry"` WalletOutbox WalletOutboxMQConfig `yaml:"wallet_outbox"` + RealtimeOutbox WalletOutboxMQConfig `yaml:"realtime_outbox"` } // WalletOutboxMQConfig 保存 wallet_outbox topic 的生产者配置。 @@ -71,10 +74,12 @@ type OutboxWorkerConfig struct { Enabled bool `yaml:"enabled"` PollInterval time.Duration `yaml:"poll_interval"` BatchSize int `yaml:"batch_size"` + Concurrency int `yaml:"concurrency"` PublishTimeout time.Duration `yaml:"publish_timeout"` MaxRetryCount int `yaml:"max_retry_count"` InitialBackoff time.Duration `yaml:"initial_backoff"` MaxBackoff time.Duration `yaml:"max_backoff"` + EventTypes []string `yaml:"event_types"` } // ProjectionWorkerConfig 控制礼物墙和 badge 展示读模型从 wallet_outbox MQ 投影的节奏。 @@ -127,11 +132,23 @@ func Default() Config { Enabled: false, PollInterval: time.Second, BatchSize: 100, + Concurrency: 1, PublishTimeout: 3 * time.Second, MaxRetryCount: 10, InitialBackoff: 5 * time.Second, MaxBackoff: 5 * time.Minute, }, + RealtimeOutboxWorker: OutboxWorkerConfig{ + Enabled: false, + PollInterval: time.Second, + BatchSize: 50, + Concurrency: 2, + PublishTimeout: 3 * time.Second, + MaxRetryCount: 10, + InitialBackoff: 2 * time.Second, + MaxBackoff: time.Minute, + EventTypes: defaultRealtimeOutboxEventTypes(), + }, ProjectionWorker: ProjectionWorkerConfig{ Enabled: false, PollInterval: 60 * time.Second, @@ -159,6 +176,11 @@ func defaultRocketMQConfig() RocketMQConfig { Topic: "hyapp_wallet_outbox", ProducerGroup: "hyapp-wallet-outbox-producer", }, + RealtimeOutbox: WalletOutboxMQConfig{ + Enabled: false, + Topic: "hyapp_wallet_realtime_outbox", + ProducerGroup: "hyapp-wallet-realtime-outbox-producer", + }, } } @@ -230,30 +252,17 @@ func Load(path string) (Config, error) { return Config{}, errors.New("google_play service account is required") } } - if cfg.OutboxWorker.PollInterval <= 0 { - cfg.OutboxWorker.PollInterval = Default().OutboxWorker.PollInterval - } - if cfg.OutboxWorker.BatchSize <= 0 { - cfg.OutboxWorker.BatchSize = Default().OutboxWorker.BatchSize - } - if cfg.OutboxWorker.PublishTimeout <= 0 { - cfg.OutboxWorker.PublishTimeout = Default().OutboxWorker.PublishTimeout - } - if cfg.OutboxWorker.MaxRetryCount <= 0 { - cfg.OutboxWorker.MaxRetryCount = Default().OutboxWorker.MaxRetryCount - } - if cfg.OutboxWorker.InitialBackoff <= 0 { - cfg.OutboxWorker.InitialBackoff = Default().OutboxWorker.InitialBackoff - } - if cfg.OutboxWorker.MaxBackoff <= 0 { - cfg.OutboxWorker.MaxBackoff = Default().OutboxWorker.MaxBackoff - } - if cfg.OutboxWorker.MaxBackoff < cfg.OutboxWorker.InitialBackoff { - cfg.OutboxWorker.MaxBackoff = cfg.OutboxWorker.InitialBackoff - } + cfg.OutboxWorker = normalizeOutboxWorkerConfig(cfg.OutboxWorker, Default().OutboxWorker) if cfg.OutboxWorker.Enabled && !cfg.RocketMQ.WalletOutbox.Enabled { return Config{}, errors.New("outbox_worker requires rocketmq.wallet_outbox.enabled") } + cfg.RealtimeOutboxWorker = normalizeOutboxWorkerConfig(cfg.RealtimeOutboxWorker, Default().RealtimeOutboxWorker) + if len(cfg.RealtimeOutboxWorker.EventTypes) == 0 { + cfg.RealtimeOutboxWorker.EventTypes = defaultRealtimeOutboxEventTypes() + } + if cfg.RealtimeOutboxWorker.Enabled && !cfg.RocketMQ.RealtimeOutbox.Enabled { + return Config{}, errors.New("realtime_outbox_worker requires rocketmq.realtime_outbox.enabled") + } if cfg.ProjectionWorker.PollInterval <= 0 { cfg.ProjectionWorker.PollInterval = Default().ProjectionWorker.PollInterval } @@ -299,7 +308,13 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { if cfg.WalletOutbox.ProducerGroup = strings.TrimSpace(cfg.WalletOutbox.ProducerGroup); cfg.WalletOutbox.ProducerGroup == "" { cfg.WalletOutbox.ProducerGroup = defaults.WalletOutbox.ProducerGroup } - if cfg.WalletOutbox.Enabled { + if cfg.RealtimeOutbox.Topic = strings.TrimSpace(cfg.RealtimeOutbox.Topic); cfg.RealtimeOutbox.Topic == "" { + cfg.RealtimeOutbox.Topic = defaults.RealtimeOutbox.Topic + } + if cfg.RealtimeOutbox.ProducerGroup = strings.TrimSpace(cfg.RealtimeOutbox.ProducerGroup); cfg.RealtimeOutbox.ProducerGroup == "" { + cfg.RealtimeOutbox.ProducerGroup = defaults.RealtimeOutbox.ProducerGroup + } + if cfg.WalletOutbox.Enabled || cfg.RealtimeOutbox.Enabled { cfg.Enabled = true } if cfg.Enabled { @@ -313,12 +328,60 @@ func normalizeRocketMQConfig(cfg RocketMQConfig) (RocketMQConfig, error) { return cfg, nil } +func normalizeOutboxWorkerConfig(cfg OutboxWorkerConfig, defaults OutboxWorkerConfig) OutboxWorkerConfig { + if cfg.PollInterval <= 0 { + cfg.PollInterval = defaults.PollInterval + } + if cfg.BatchSize <= 0 { + cfg.BatchSize = defaults.BatchSize + } + if cfg.BatchSize > 500 { + cfg.BatchSize = 500 + } + if cfg.Concurrency <= 0 { + cfg.Concurrency = defaults.Concurrency + } + if cfg.Concurrency > 64 { + cfg.Concurrency = 64 + } + if cfg.PublishTimeout <= 0 { + cfg.PublishTimeout = defaults.PublishTimeout + } + if cfg.MaxRetryCount <= 0 { + cfg.MaxRetryCount = defaults.MaxRetryCount + } + if cfg.InitialBackoff <= 0 { + cfg.InitialBackoff = defaults.InitialBackoff + } + if cfg.MaxBackoff <= 0 { + cfg.MaxBackoff = defaults.MaxBackoff + } + if cfg.MaxBackoff < cfg.InitialBackoff { + cfg.MaxBackoff = cfg.InitialBackoff + } + cfg.EventTypes = normalizeStringSlice(cfg.EventTypes) + return cfg +} + func normalizeStringSlice(values []string) []string { normalized := make([]string, 0, len(values)) + seen := make(map[string]struct{}, len(values)) for _, value := range values { if value = strings.TrimSpace(value); value != "" { + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} normalized = append(normalized, value) } } return normalized } + +func defaultRealtimeOutboxEventTypes() []string { + return []string{ + "WalletRedPacketCreated", + "WalletRedPacketClaimed", + "WalletRedPacketRefunded", + } +} diff --git a/services/wallet-service/internal/config/config_test.go b/services/wallet-service/internal/config/config_test.go new file mode 100644 index 00000000..4b13530c --- /dev/null +++ b/services/wallet-service/internal/config/config_test.go @@ -0,0 +1,38 @@ +package config + +import "testing" + +func TestLoadLocalKeepsOutboxMQDisabled(t *testing.T) { + cfg, err := Load("../../configs/config.yaml") + if err != nil { + t.Fatalf("Load local config failed: %v", err) + } + if cfg.RocketMQ.Enabled || cfg.RocketMQ.WalletOutbox.Enabled || cfg.RocketMQ.RealtimeOutbox.Enabled { + t.Fatalf("local config must not require RocketMQ: %+v", cfg.RocketMQ) + } + if cfg.OutboxWorker.Concurrency != 1 || cfg.RealtimeOutboxWorker.Concurrency != 2 { + t.Fatalf("local outbox worker defaults mismatch: outbox=%+v realtime=%+v", cfg.OutboxWorker, cfg.RealtimeOutboxWorker) + } +} + +func TestLoadTencentExampleSplitsRealtimeOutboxAndRaisesThroughput(t *testing.T) { + cfg, err := Load("../../configs/config.tencent.example.yaml") + if err != nil { + t.Fatalf("Load tencent example failed: %v", err) + } + if !cfg.RocketMQ.Enabled || !cfg.RocketMQ.WalletOutbox.Enabled || !cfg.RocketMQ.RealtimeOutbox.Enabled { + t.Fatalf("tencent example must enable both wallet outbox producers: %+v", cfg.RocketMQ) + } + if cfg.RocketMQ.WalletOutbox.Topic == cfg.RocketMQ.RealtimeOutbox.Topic { + t.Fatalf("realtime outbox topic must be separated from generic wallet topic: %+v", cfg.RocketMQ) + } + if cfg.OutboxWorker.BatchSize != 200 || cfg.OutboxWorker.Concurrency != 8 { + t.Fatalf("generic outbox throughput config mismatch: %+v", cfg.OutboxWorker) + } + if cfg.RealtimeOutboxWorker.BatchSize != 50 || cfg.RealtimeOutboxWorker.Concurrency != 4 { + t.Fatalf("realtime outbox worker config mismatch: %+v", cfg.RealtimeOutboxWorker) + } + if len(cfg.RealtimeOutboxWorker.EventTypes) != 3 || cfg.RealtimeOutboxWorker.EventTypes[0] != "WalletRedPacketCreated" { + t.Fatalf("realtime outbox event types mismatch: %+v", cfg.RealtimeOutboxWorker.EventTypes) + } +} diff --git a/services/wallet-service/internal/service/wallet/service_test.go b/services/wallet-service/internal/service/wallet/service_test.go index 11fe7d2b..e5332b0a 100644 --- a/services/wallet-service/internal/service/wallet/service_test.go +++ b/services/wallet-service/internal/service/wallet/service_test.go @@ -1212,6 +1212,67 @@ func TestRedPacketLifecycleUsesRealMySQL(t *testing.T) { } } +func TestWalletOutboxClaimSeparatesRealtimeEventTypes(t *testing.T) { + repository := mysqltest.NewRepository(t) + repository.SetBalance(19101, 1000) + svc := walletservice.New(repository) + if _, err := svc.UpdateRedPacketConfig(context.Background(), ledger.RedPacketConfig{ + AppCode: "lalu", + Enabled: true, + CountTiers: []int32{5}, + AmountTiers: []int64{10}, + DelayedOpenSeconds: 1, + ExpireSeconds: 2, + DailySendLimit: 5, + UpdatedByAdminID: 9001, + }); err != nil { + t.Fatalf("UpdateRedPacketConfig failed: %v", err) + } + receipt, err := svc.CreateRedPacket(context.Background(), ledger.RedPacketCreateCommand{ + AppCode: "lalu", + CommandID: "cmd-red-packet-realtime-outbox", + SenderUserID: 19101, + RoomID: "room-191", + RegionID: 191, + PacketType: ledger.RedPacketTypeNormal, + TotalAmount: 10, + PacketCount: 5, + }) + if err != nil { + t.Fatalf("CreateRedPacket failed: %v", err) + } + + ctx := appcode.WithContext(context.Background(), "lalu") + realtimeTypes := []string{"WalletRedPacketCreated", "WalletRedPacketClaimed", "WalletRedPacketRefunded"} + realtimeRecords, err := repository.ClaimPendingWalletOutboxFiltered(ctx, "wallet-realtime-outbox-test", 10, time.Now().Add(time.Minute).UnixMilli(), mysqlstorage.WalletOutboxClaimFilter{ + IncludeEventTypes: realtimeTypes, + }) + if err != nil { + t.Fatalf("ClaimPendingWalletOutboxFiltered realtime failed: %v", err) + } + if len(realtimeRecords) != 1 || realtimeRecords[0].EventType != "WalletRedPacketCreated" { + t.Fatalf("realtime claim should only pick red packet fact, got %+v", realtimeRecords) + } + + normalRecords, err := repository.ClaimPendingWalletOutboxFiltered(ctx, "wallet-normal-outbox-test", 10, time.Now().Add(time.Minute).UnixMilli(), mysqlstorage.WalletOutboxClaimFilter{ + ExcludeEventTypes: realtimeTypes, + }) + if err != nil { + t.Fatalf("ClaimPendingWalletOutboxFiltered normal failed: %v", err) + } + if len(normalRecords) == 0 { + t.Fatalf("normal claim should still pick non-realtime wallet facts for packet %s", receipt.Packet.PacketID) + } + for _, record := range normalRecords { + if record.EventType == "WalletRedPacketCreated" || record.EventType == "WalletRedPacketClaimed" || record.EventType == "WalletRedPacketRefunded" { + t.Fatalf("normal claim must exclude realtime red packet fact: %+v", normalRecords) + } + } + if got := repository.CountRows("wallet_outbox", "event_id = ? AND status = ? AND worker_id = ?", realtimeRecords[0].EventID, "delivering", "wallet-realtime-outbox-test"); got != 1 { + t.Fatalf("realtime fact should be locked by realtime worker, got %d", got) + } +} + func TestWalletOutboxClaimUsesRealMySQLStatusFlow(t *testing.T) { repository := mysqltest.NewRepository(t) repository.SetAssetBalance(30001, ledger.AssetCoinSellerCoin, 1000) diff --git a/services/wallet-service/internal/storage/mysql/repository.go b/services/wallet-service/internal/storage/mysql/repository.go index f09cca1d..6e0ba633 100644 --- a/services/wallet-service/internal/storage/mysql/repository.go +++ b/services/wallet-service/internal/storage/mysql/repository.go @@ -68,6 +68,14 @@ type WalletOutboxRecord struct { LockUntilMS int64 } +// WalletOutboxClaimFilter narrows one MQ fanout lane by event type. +type WalletOutboxClaimFilter struct { + // IncludeEventTypes lets a realtime worker own only the configured UI event facts. + IncludeEventTypes []string + // ExcludeEventTypes lets the generic wallet worker skip facts owned by the realtime lane. + ExcludeEventTypes []string +} + // Open 创建 MySQL 连接池并做启动 ping。 func Open(ctx context.Context, dsn string) (*Repository, error) { if strings.TrimSpace(dsn) == "" { @@ -1893,6 +1901,11 @@ func (r *Repository) insertWalletOutbox(ctx context.Context, tx *sql.Tx, events // ClaimPendingWalletOutbox atomically claims committed wallet facts for MQ fanout. func (r *Repository) ClaimPendingWalletOutbox(ctx context.Context, workerID string, limit int, lockUntilMS int64) ([]WalletOutboxRecord, error) { + return r.ClaimPendingWalletOutboxFiltered(ctx, workerID, limit, lockUntilMS, WalletOutboxClaimFilter{}) +} + +// ClaimPendingWalletOutboxFiltered atomically claims committed wallet facts for one MQ fanout lane. +func (r *Repository) ClaimPendingWalletOutboxFiltered(ctx context.Context, workerID string, limit int, lockUntilMS int64, filter WalletOutboxClaimFilter) ([]WalletOutboxRecord, error) { if r == nil || r.db == nil { return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured") } @@ -1912,6 +1925,8 @@ func (r *Repository) ClaimPendingWalletOutbox(ctx context.Context, workerID stri } appCode := appcode.FromContext(ctx) + eventFilterSQL, eventFilterArgs := walletOutboxEventFilterSQL(filter) + pendingIndex, claimIndex := walletOutboxClaimIndexes(filter) tx, err := r.db.BeginTx(ctx, nil) if err != nil { return nil, err @@ -1924,45 +1939,21 @@ func (r *Repository) ClaimPendingWalletOutbox(ctx context.Context, workerID stri args []any }{ { - query: ` - SELECT app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type, - available_delta, frozen_delta, COALESCE(CAST(payload AS CHAR), '{}'), created_at_ms, - retry_count, COALESCE(last_error, ''), COALESCE(worker_id, ''), COALESCE(lock_until_ms, 0) - FROM wallet_outbox FORCE INDEX (idx_wallet_outbox_pending) - WHERE app_code = ? AND status = ? + query: walletOutboxClaimQuery(pendingIndex, ` AND next_retry_at_ms IS NULL - AND lock_until_ms IS NULL - ORDER BY created_at_ms ASC, event_id ASC - LIMIT ? - FOR UPDATE SKIP LOCKED`, - args: []any{appCode, outboxStatusPending}, + AND lock_until_ms IS NULL`, eventFilterSQL), + args: append([]any{appCode, outboxStatusPending}, eventFilterArgs...), }, { - query: ` - SELECT app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type, - available_delta, frozen_delta, COALESCE(CAST(payload AS CHAR), '{}'), created_at_ms, - retry_count, COALESCE(last_error, ''), COALESCE(worker_id, ''), COALESCE(lock_until_ms, 0) - FROM wallet_outbox FORCE INDEX (idx_wallet_outbox_pending) - WHERE app_code = ? AND status = ? + query: walletOutboxClaimQuery(pendingIndex, ` AND next_retry_at_ms <= ? - AND lock_until_ms IS NULL - ORDER BY created_at_ms ASC, event_id ASC - LIMIT ? - FOR UPDATE SKIP LOCKED`, - args: []any{appCode, outboxStatusRetryable, nowMS}, + AND lock_until_ms IS NULL`, eventFilterSQL), + args: append([]any{appCode, outboxStatusRetryable, nowMS}, eventFilterArgs...), }, { - query: ` - SELECT app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type, - available_delta, frozen_delta, COALESCE(CAST(payload AS CHAR), '{}'), created_at_ms, - retry_count, COALESCE(last_error, ''), COALESCE(worker_id, ''), COALESCE(lock_until_ms, 0) - FROM wallet_outbox FORCE INDEX (idx_wallet_outbox_claim) - WHERE app_code = ? AND status = ? - AND lock_until_ms <= ? - ORDER BY created_at_ms ASC, event_id ASC - LIMIT ? - FOR UPDATE SKIP LOCKED`, - args: []any{appCode, outboxStatusDelivering, nowMS}, + query: walletOutboxClaimQuery(claimIndex, ` + AND lock_until_ms <= ?`, eventFilterSQL), + args: append([]any{appCode, outboxStatusDelivering, nowMS}, eventFilterArgs...), }, } for _, branch := range claimBranches { @@ -2003,6 +1994,73 @@ func (r *Repository) ClaimPendingWalletOutbox(ctx context.Context, workerID stri return records, nil } +func walletOutboxClaimQuery(indexName string, statusCondition string, eventFilterSQL string) string { + return ` + SELECT app_code, event_id, event_type, transaction_id, command_id, user_id, asset_type, + available_delta, frozen_delta, COALESCE(CAST(payload AS CHAR), '{}'), created_at_ms, + retry_count, COALESCE(last_error, ''), COALESCE(worker_id, ''), COALESCE(lock_until_ms, 0) + FROM wallet_outbox FORCE INDEX (` + indexName + `) + WHERE app_code = ? AND status = ?` + statusCondition + eventFilterSQL + ` + ORDER BY created_at_ms ASC, event_id ASC + LIMIT ? + FOR UPDATE SKIP LOCKED` +} + +func walletOutboxEventFilterSQL(filter WalletOutboxClaimFilter) (string, []any) { + include := normalizeWalletOutboxEventTypes(filter.IncludeEventTypes) + if len(include) > 0 { + return " AND event_type IN (" + walletOutboxPlaceholders(len(include)) + ")", walletOutboxEventArgs(include) + } + exclude := normalizeWalletOutboxEventTypes(filter.ExcludeEventTypes) + if len(exclude) > 0 { + return " AND event_type NOT IN (" + walletOutboxPlaceholders(len(exclude)) + ")", walletOutboxEventArgs(exclude) + } + return "", nil +} + +func walletOutboxClaimIndexes(filter WalletOutboxClaimFilter) (string, string) { + if len(normalizeWalletOutboxEventTypes(filter.IncludeEventTypes)) > 0 { + return "idx_wallet_outbox_event_pending", "idx_wallet_outbox_event_claim" + } + return "idx_wallet_outbox_pending", "idx_wallet_outbox_claim" +} + +func normalizeWalletOutboxEventTypes(values []string) []string { + normalized := make([]string, 0, len(values)) + seen := make(map[string]struct{}, len(values)) + for _, value := range values { + value = strings.TrimSpace(value) + if value == "" { + continue + } + if _, exists := seen[value]; exists { + continue + } + seen[value] = struct{}{} + normalized = append(normalized, value) + } + return normalized +} + +func walletOutboxPlaceholders(count int) string { + if count <= 0 { + return "" + } + placeholders := make([]string, count) + for index := range placeholders { + placeholders[index] = "?" + } + return strings.Join(placeholders, ",") +} + +func walletOutboxEventArgs(values []string) []any { + args := make([]any, 0, len(values)) + for _, value := range values { + args = append(args, value) + } + return args +} + func queryWalletOutboxRecords(ctx context.Context, tx *sql.Tx, query string, args ...any) ([]WalletOutboxRecord, error) { rows, err := tx.QueryContext(ctx, query, args...) if err != nil { diff --git a/tools/grant-registration-level-badges/main.go b/tools/grant-registration-level-badges/main.go new file mode 100644 index 00000000..e39a946f --- /dev/null +++ b/tools/grant-registration-level-badges/main.go @@ -0,0 +1,158 @@ +package main + +import ( + "context" + "database/sql" + "flag" + "fmt" + "log" + "sort" + "strings" + "time" + + _ "github.com/go-sql-driver/mysql" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + walletv1 "hyapp.local/api/proto/wallet/v1" +) + +const registrationLevelBadgeReason = "growth_level_registration_badge" + +func main() { + var appCode string + var activityDSN string + var userDSN string + var walletAddr string + var dryRun bool + var onlyUserID int64 + flag.StringVar(&appCode, "app_code", "lalu", "app_code to backfill") + flag.StringVar(&activityDSN, "activity_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_activity?parseTime=true&charset=utf8mb4&loc=UTC", "activity mysql dsn") + flag.StringVar(&userDSN, "user_dsn", "hyapp:hyapp@tcp(127.0.0.1:23306)/hyapp_user?parseTime=true&charset=utf8mb4&loc=UTC", "user mysql dsn") + flag.StringVar(&walletAddr, "wallet_addr", "127.0.0.1:13004", "wallet-service grpc address") + flag.BoolVar(&dryRun, "dry_run", true, "print planned grants without calling wallet-service") + flag.Int64Var(&onlyUserID, "user_id", 0, "optional single user_id") + flag.Parse() + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + activityDB, err := sql.Open("mysql", activityDSN) + if err != nil { + log.Fatalf("open activity db: %v", err) + } + defer activityDB.Close() + userDB, err := sql.Open("mysql", userDSN) + if err != nil { + log.Fatalf("open user db: %v", err) + } + defer userDB.Close() + + badges, err := levelOneBadgeResources(ctx, activityDB, appCode) + if err != nil { + log.Fatalf("query level badges: %v", err) + } + users, err := targetUsers(ctx, userDB, appCode, onlyUserID) + if err != nil { + log.Fatalf("query users: %v", err) + } + + tracks := []string{"wealth", "game", "charm"} + for _, track := range tracks { + if badges[track] <= 0 { + log.Printf("skip track=%s: level 1 long badge is not configured", track) + } + } + if dryRun { + log.Printf("dry_run=true app_code=%s users=%d badges=%v", appCode, len(users), badges) + return + } + + conn, err := grpc.NewClient(walletAddr, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + log.Fatalf("dial wallet-service: %v", err) + } + defer conn.Close() + wallet := walletv1.NewWalletServiceClient(conn) + + total := 0 + for _, userID := range users { + for _, track := range tracks { + resourceID := badges[track] + if resourceID <= 0 { + continue + } + // wallet-service 用 command_id 做完整请求幂等;同一个用户同一轨道重复执行只会返回首次发放结果。 + commandID := fmt.Sprintf("registration_level_badges:%s:%d:%s", appCode, userID, track) + _, err := wallet.GrantResource(ctx, &walletv1.GrantResourceRequest{ + CommandId: commandID, + AppCode: appCode, + TargetUserId: userID, + ResourceId: resourceID, + Quantity: 1, + DurationMs: 0, + Reason: registrationLevelBadgeReason, + OperatorUserId: userID, + GrantSource: "growth_level", + }) + if err != nil { + log.Fatalf("grant failed user_id=%d track=%s resource_id=%d: %v", userID, track, resourceID, err) + } + total++ + } + } + log.Printf("done app_code=%s users=%d grant_calls=%d", appCode, len(users), total) +} + +func levelOneBadgeResources(ctx context.Context, db *sql.DB, appCode string) (map[string]int64, error) { + rows, err := db.QueryContext(ctx, ` + SELECT track, + COALESCE(CAST(JSON_UNQUOTE(JSON_EXTRACT(display_config_json, '$.long_badge_resource_id')) AS SIGNED), 0) AS resource_id + FROM growth_level_rules + WHERE app_code = ? AND status = 'active' AND level = 1 + ORDER BY track ASC`, appCode) + if err != nil { + return nil, err + } + defer rows.Close() + result := map[string]int64{} + for rows.Next() { + var track string + var resourceID int64 + if err := rows.Scan(&track, &resourceID); err != nil { + return nil, err + } + track = strings.ToLower(strings.TrimSpace(track)) + if resourceID > 0 { + result[track] = resourceID + } + } + return result, rows.Err() +} + +func targetUsers(ctx context.Context, db *sql.DB, appCode string, onlyUserID int64) ([]int64, error) { + if onlyUserID > 0 { + return []int64{onlyUserID}, nil + } + rows, err := db.QueryContext(ctx, ` + SELECT user_id + FROM users + WHERE app_code = ? AND status = 'active' + ORDER BY user_id ASC`, appCode) + if err != nil { + return nil, err + } + defer rows.Close() + users := make([]int64, 0) + for rows.Next() { + var userID int64 + if err := rows.Scan(&userID); err != nil { + return nil, err + } + users = append(users, userID) + } + if err := rows.Err(); err != nil { + return nil, err + } + sort.Slice(users, func(i, j int) bool { return users[i] < users[j] }) + return users, nil +}